/** @file  pmFPA.c
*
*  @brief This file defines the basic types for the FPA hierarchy
*
*  @ingroup AstroImage
*
*  @author GLG, MHPCC
*
* XXX: We should review the extent of the warning messages on these functions
* when the transformations are not successful.
*
* XXX: Should we implement non-linear cell->chip transforms?
*
*  @version $Revision: 1.1.4.8 $ $Name: not supported by cvs2svn $
*  @date $Date: 2006-04-14 21:43:59 $
*
*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
*/

/******************************************************************************/
/*  INCLUDE FILES                                                             */
/******************************************************************************/
#include <string.h>
#include <math.h>
#include <assert.h>
#include "pslib.h"

#include "pmHDU.h"
#include "pmFPA.h"
#include "pmConcepts.h"
#include "pmMaskBadPixels.h"

/******************************************************************************
 *****************************************************************************/
#define PS_FREE_HIERARCHY 1
#define PARENT_LINKS 0

static void readoutFree(pmReadout *readout)
{
    psTrace(__func__, 9, "Removing readout %lx from cell %lx...\n", (size_t) readout, (size_t) readout->parent);
    if (readout->parent) {
        psArray *readouts = readout->parent->readouts;
        for (int i = 0; i < readouts->n; i++) {
            if (readouts->data[i] == readout) {
                //                    pmReadout *tmpReadout = readouts->data[i];
                readouts->data[i] = NULL;
                #if PARENT_LINKS

                psFree(tmpReadout);
                #endif

                break;
            }
        }
    }
    psTrace(__func__, 9, "Freeing readout %lx\n", (size_t) readout);

    #if PARENT_LINKS

    psFree(readout->parent);
    #endif

    psFree(readout->image);
    psFree(readout->mask);
    psFree(readout->weight);
    psFree(readout->analysis);
    psFree(readout->bias);
}

static void cellFree(pmCell *cell)
{
    psTrace(__func__, 9, "Removing cell %lx from chip %lx...\n", (size_t)cell, (size_t)cell->parent);
    if (cell->parent) {
        psArray *cells = cell->parent->cells;
        for (int i = 0; i < cells->n; i++) {
            if (cells->data[i] == cell) {
                //                    pmCell *tmpCell = cells->data[i];
                cells->data[i] = NULL;
                #if PARENT_LINKS

                psFree(tmpCell);
                #endif

                break;
            }
        }
    }
    psTrace(__func__, 9, "Freeing cell %lx\n", (size_t)cell);

    pmCellFreeReadouts(cell);
    psFree(cell->readouts);
    #if PARENT_LINKS

    psFree(cell->parent);
    #endif

    psFree(cell->concepts);
    psFree(cell->analysis);
    psFree(cell->config);
    psFree(cell->hdu);
}

static void chipFree(pmChip* chip)
{
    psTrace(__func__, 9, "Removing chip %lx from fpa %lx...\n", (size_t)chip, (size_t)chip->parent);
    if (chip->parent) {
        psArray *chips = chip->parent->chips;
        for (int i = 0; i < chips->n; i++) {
            if (chips->data[i] == chip) {
                //                    pmChip *tmpChip = chips->data[i];
                chips->data[i] = NULL;
                #if PARENT_LINKS

                psFree(tmpChip);
                #endif

                break;
            }
        }
    }
    psTrace(__func__, 9, "Freeing chip %lx\n", (size_t)chip);

    pmChipFreeCells(chip);
    psFree(chip->cells);
    #if PARENT_LINKS

    psFree(chip->parent);
    #endif

    #if 0

    psFree(chip->toFPA);
    psFree(chip->fromFPA);
    #endif

    psFree(chip->concepts);
    psFree(chip->analysis);
    psFree(chip->hdu);
}


static void FPAFree(pmFPA *fpa)
{
    psTrace(__func__, 9, "Freeing fpa %lx\n", (size_t)fpa);
    #if 0

    psFree(fpa->fromTangentPlane);
    psFree(fpa->toTangentPlane);
    psFree(fpa->projection);
    #endif

    psFree(fpa->concepts);
    psFree(fpa->analysis);
    psFree(fpa->camera);
    //
    // Set the parent to NULL in all fpa->chips before psFree(fpa->chips)
    // in order to avoid memory reference counter problems.
    //
    psArray *chips = fpa->chips;
    for (psS32 i = 0 ; i < chips->n ; i++) {
        pmChip *tmpChip = chips->data[i];
        if (! tmpChip) {
            continue;
        }
        chips->data[i] = NULL;
        tmpChip->parent = NULL;
        if (PS_FREE_HIERARCHY == 1) {
            psFree(tmpChip);
        }
    }
    psFree(fpa->chips);
    psFree(fpa->hdu);
}

void pmCellFreeReadouts(pmCell *cell)
{
    //
    // Set the parent to NULL in all cell->readouts before psFree(cell->readouts)
    // in order to avoid memory reference counter problems.
    //
    psArray *readouts = cell->readouts;
    for (psS32 i = 0 ; i < readouts->n ; i++) {
        pmReadout *tmpReadout = readouts->data[i];
        if (! tmpReadout) {
            continue;
        }
        readouts->data[i] = NULL;
        tmpReadout->parent = NULL;
        psTrace(__func__, 9, "Will now free readout %lx...\n", (size_t)tmpReadout);
        psFree(tmpReadout); // Drop the readout
        #if PARENT_LINKS

        psFree(cell); // Decrement reference counter on cell, since readout->parent isn't held any more
        #endif

    }
    cell->readouts = psArrayRealloc(cell->readouts, 0);
    cell->readouts->n = 0;
}


void pmChipFreeCells(pmChip *chip)
{
    //
    // Set the parent to NULL in all chip->cells before psFree(chip->cells)
    // in order to avoid memory reference counter problems.
    //
    psArray *cells = chip->cells;
    for (int i = 0 ; i < cells->n ; i++) {
        pmCell *tmpCell = cells->data[i];
        if (! tmpCell) {
            continue;
        }
        cells->data[i] = NULL;
        tmpCell->parent = NULL;
        pmCellFreeReadouts(tmpCell);// Drop all readouts the cell holds
        psFree(tmpCell);            // Drop the cell
        #if PARENT_LINKS

        psFree(chip); // Decrement reference counter on chip, since cell->parent isn't held any more
        #endif

    }
    chip->cells = psArrayRealloc(chip->cells, 0);
    chip->cells->n = 0;
}


pmReadout *pmReadoutAlloc(pmCell *cell)
{
    pmReadout *tmpReadout = (pmReadout *) psAlloc(sizeof(pmReadout));
    tmpReadout->row0 = 0;

    tmpReadout->image = NULL;
    tmpReadout->mask = NULL;
    tmpReadout->weight = NULL;
    tmpReadout->bias = psListAlloc(NULL);
    tmpReadout->analysis = psMetadataAlloc();
    #if PARENT_LINKS

    tmpReadout->parent = psMemIncrRefCounter(cell);
    #else

    tmpReadout->parent = cell;
    #endif

    if (cell != NULL) {
        cell->readouts = psArrayAdd(cell->readouts, 1, (psPtr) tmpReadout);
    }

    tmpReadout->process = true;            // All cells are processed by default
    tmpReadout->file_exists = false;       // file not yet identified
    tmpReadout->data_exists = false;       // data yet read in

    psMemSetDeallocator(tmpReadout, (psFreeFunc) readoutFree);
    return(tmpReadout);
}

// XXX: Verify these default values for row0, col0.
// PAP: These values may disappear in the future in favour of values in the "concepts"?
pmCell *pmCellAlloc(
    pmChip *chip,
    const char *name)
{
    pmCell *tmpCell = (pmCell *) psAlloc(sizeof(pmCell));

    tmpCell->concepts = psMetadataAlloc();
    psBool rc = psMetadataAddStr(tmpCell->concepts, PS_LIST_HEAD, "CELL.NAME", 0, NULL, name);
    if (rc == false) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CELL.NAME to metadata.\n");
    }
    tmpCell->config = NULL;
    tmpCell->analysis = psMetadataAlloc();
    tmpCell->readouts = psArrayAlloc(0);
    #if PARENT_LINKS

    tmpCell->parent = psMemIncrRefCounter(chip);
    #else

    tmpCell->parent = chip;
    #endif

    if (chip != NULL) {
        chip->cells = psArrayAdd(chip->cells, 1, (psPtr) tmpCell);
    }
    tmpCell->process = true;            // All cells are processed by default
    tmpCell->file_exists = false;       // Not yet identified
    tmpCell->data_exists = false;       // Not yet read in
    tmpCell->hdu = NULL;

    pmConceptsBlankCell(tmpCell);

    psMemSetDeallocator(tmpCell, (psFreeFunc) cellFree);
    return(tmpCell);
}

// XXX: Verify these default values for row0, col0.
// PAP: row0, col0 may disappear in the future in favour of storing values in the "concepts".
pmChip *pmChipAlloc(
    pmFPA *fpa,
    const char *name)
{
    pmChip *tmpChip = (pmChip *) psAlloc(sizeof(pmChip));

    #if 0

    tmpChip->col0 = 0;
    tmpChip->row0 = 0;
    tmpChip->toFPA = NULL;
    tmpChip->fromFPA = NULL;
    #endif

    tmpChip->concepts = psMetadataAlloc();
    psBool rc = psMetadataAddStr(tmpChip->concepts, PS_LIST_HEAD, "CHIP.NAME", 0, NULL, name);
    if (rc == false) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CHIP.NAME to metadata.\n");
    }
    tmpChip->analysis = psMetadataAlloc();
    tmpChip->cells = psArrayAlloc(0);
    #if PARENT_LINKS

    tmpChip->parent = psMemIncrRefCounter(fpa);
    #else

    tmpChip->parent = fpa;
    #endif

    if (fpa != NULL) {
        fpa->chips = psArrayAdd(fpa->chips, 1, (psPtr) tmpChip);
    }
    tmpChip->process = true;            // Work on all chips, by default
    tmpChip->file_exists = false;       // Not yet identified
    tmpChip->data_exists = false;       // Not yet read in
    tmpChip->hdu = NULL;

    pmConceptsBlankChip(tmpChip);

    psMemSetDeallocator(tmpChip, (psFreeFunc) chipFree);
    return(tmpChip);
}

pmFPA *pmFPAAlloc(const psMetadata *camera)
{
    pmFPA *tmpFPA = (pmFPA *) psAlloc(sizeof(pmFPA));

    #if 0

    tmpFPA->fromTangentPlane = NULL;
    tmpFPA->toTangentPlane = NULL;
    tmpFPA->projection = NULL;
    #endif

    tmpFPA->concepts = psMetadataAlloc();
    tmpFPA->analysis = NULL;
    tmpFPA->camera = psMemIncrRefCounter((psPtr)camera);
    tmpFPA->chips = psArrayAlloc(0);
    tmpFPA->hdu = NULL;

    pmConceptsBlankFPA(tmpFPA);

    psMemSetDeallocator(tmpFPA, (psFreeFunc) FPAFree);
    return(tmpFPA);
}

static psBool cellCheckParents(pmCell *cell)
{
    if (cell == NULL) {
        return(true);
    }
    psBool flag = true;

    for (psS32 i = 0 ; i < cell->readouts->n ; i++) {
        pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpReadout, false);
        if (tmpReadout->parent != cell) {
            tmpReadout->parent = cell;
            flag = false;
        }
    }
    return(flag);
}

static psBool chipCheckParents(pmChip *chip)
{
    if (chip == NULL) {
        return(true);
    }
    psBool flag = true;

    for (psS32 i = 0 ; i < chip->cells->n ; i++) {
        pmCell *tmpCell = (pmCell *) chip->cells->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpCell, false);
        if (tmpCell->parent != chip) {
            tmpCell->parent = chip;
            flag = false;
        }

        flag &= cellCheckParents(tmpCell);
    }
    return(flag);
}

psBool pmFPACheckParents(pmFPA *fpa)
{
    if (fpa == NULL) {
        return(true);
    }
    psBool flag = true;

    for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
        pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpChip, false);
        if (tmpChip->parent != fpa) {
            tmpChip->parent = fpa;
            flag = false;
        }

        flag &= chipCheckParents(tmpChip);
    }
    return(flag);
}

/** functions to turn on/off the file_exists flag **/
bool pmFPASetFileStatus (pmFPA *fpa, bool status)
{

    for (int i = 0; i < fpa->chips->n; i++) {
        pmChip *chip = fpa->chips->data[i];
        pmChipSetFileStatus (chip, status);
    }
    return true;
}

bool pmChipSetFileStatus (pmChip *chip, bool status)
{

    chip->file_exists = status;
    for (int i = 0; i < chip->cells->n; i++) {
        pmCell *cell = chip->cells->data[i];
        pmCellSetFileStatus (cell, status);
    }
    return true;
}

bool pmCellSetFileStatus (pmCell *cell, bool status)
{

    cell->file_exists = status;
    for (int i = 0; i < cell->readouts->n; i++) {
        pmReadout *readout = cell->readouts->data[i];
        readout->file_exists = status;
    }
    return true;
}

/** functions to turn on/off the data_exists flag **/
bool pmFPASetDataStatus (pmFPA *fpa, bool status)
{

    for (int i = 0; i < fpa->chips->n; i++) {
        pmChip *chip = fpa->chips->data[i];
        pmChipSetDataStatus (chip, status);
    }
    return true;
}

bool pmChipSetDataStatus (pmChip *chip, bool status)
{

    chip->data_exists = status;
    for (int i = 0; i < chip->cells->n; i++) {
        pmCell *cell = chip->cells->data[i];
        pmCellSetDataStatus (cell, status);
    }
    return true;
}

bool pmCellSetDataStatus (pmCell *cell, bool status)
{

    cell->data_exists = status;
    for (int i = 0; i < cell->readouts->n; i++) {
        pmReadout *readout = cell->readouts->data[i];
        readout->data_exists = status;
    }
    return true;
}

/*****************************************************************************
 *****************************************************************************/

// Set cells within a chip to be processed or not
static bool setCellsProcess(const pmChip *chip, // Chip of interest
                            bool process  // Process this chip?
                           )
{
    PS_ASSERT_PTR_NON_NULL(chip, false);

    psArray *cells = chip->cells;       // Component cells
    if (! cells) {
        return false;
    }
    for (int i = 0; i < cells->n; i++) {
        pmCell *tmpCell = cells->data[i]; // Cell of interest
        if (tmpCell) {
            tmpCell->process = process;
        }
    }

    return true;
}

/*****************************************************************************
XXX EAM : I've added the 'exclusive' option.  if true all other chips are de-selected
XXX EAM : a negative value is valid and, in combinations with exclusive, de-selects all chips
 *****************************************************************************/
bool pmFPASelectChip(pmFPA *fpa, int chipNum, bool exclusive)
{
    PS_ASSERT_PTR_NON_NULL(fpa, false);

    psArray *chips = fpa->chips;        // Component chips
    if ((chips == NULL) || (chipNum >= chips->n)) {
        return(false);
    }

    for (int i = 0 ; i < chips->n ; i++) {
        pmChip *tmpChip = (pmChip *) chips->data[i];
        if (tmpChip == NULL) {
            continue;
        }
        if (i == chipNum) {
            tmpChip->process = true;
            setCellsProcess(tmpChip, true);
        } else {
            if (exclusive) {
                tmpChip->process = false;
                setCellsProcess(tmpChip, false);
            }
        }

    }

    return true;
}

/*****************************************************************************
XXX EAM : I've added the 'exclusive' option.  if true all other chips are de-selected
XXX EAM : a negative value is valid and, in combinations with exclusive, de-selects all cells
XXX this function should probably be re-defined to merge with 'setCellsProcess'
 *****************************************************************************/
bool pmChipSelectCell(pmChip *chip, int cellNum, bool exclusive)
{
    assert(chip);

    psArray *cells = chip->cells;       // Component cells
    if (!cells || cellNum > cells->n) {
        return false;
    }

    for (int i = 0; i < cells->n; i++) {
        pmCell *cell = cells->data[i];
        if (!cell) {
            continue;
        }
        if (i == cellNum) {
            cell->process = true;
        } else {
            if (exclusive) {
                cell->process = false;
            }
        }
    }
    return true;
}

/*****************************************************************************
XXX: The SDRS is ambiguous on a few things:
    Whether or not the other chips should be set process=true. [PAP: No]
    Should we return the number of chip process=true before or after they're set, [PAP: After]
 *****************************************************************************/
/**
 *
 * pmFPAExcludeChip shall set process to false only for the specified chip
 * number (chipNum). In the event that the specified chip number does not exist
 * within the fpa, the function shall generate a warning, and perform no action.
 * The function shall return the number of chips within the fpa that have process
 * set to true.
 *
 */
int pmFPAExcludeChip(
    pmFPA *fpa,
    int chipNum)
{
    PS_ASSERT_PTR_NON_NULL(fpa, false);

    psArray *chips = fpa->chips;        // Component chips
    if (chips == NULL) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: fpa->chips == NULL\n");
        return(0);
    }
    if ((chipNum >= chips->n) || (NULL == (pmChip *) chips->data[chipNum])) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: the specified chip (%d) does not exist.\n", chipNum);
        return(0);
    }

    int numChips = 0;                   // Number of chips to be processed
    for (int i = 0 ; i < chips->n ; i++) {
        pmChip *tmpChip = (pmChip *) chips->data[i]; // Chip of interest
        if (tmpChip != NULL) {
            if (i == chipNum) {
                tmpChip->process = false;
                setCellsProcess(tmpChip, false); // Wipe out the cell as well
            } else if (tmpChip->process) {
                numChips++;
            }
        }
    }

    return(numChips);
}

int pmChipExcludeCell(pmChip *chip,
                      int cellNum
                     )
{
    assert(chip);

    psArray *cells = chip->cells;       // The component cells
    if (!cells || cellNum > cells->n) {
        return 0;
    }

    int numCells = 0;                   // Number of cells to be processed
    for (int i = 0; i < cells->n; i++) {
        pmCell *cell = cells->data[i];
        if (!cell) {
            continue;
        }
        if (i == cellNum) {
            cell->process = false;
        } else {
            numCells++;
        }
    }

    return numCells;
}


