/** @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.12 $ $Name: not supported by cvs2svn $
*  @date $Date: 2006-09-15 09:49:01 $
*
*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/******************************************************************************/
/*  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"

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

static void readoutFree(pmReadout *readout)
{
    // if this readout has a parent, drop that instance
    if (readout->parent) {
        psTrace("psModules.camera", 9, "Removing readout %zd from cell %zd...\n", (size_t)readout, (size_t)readout->parent);
        psArray *readouts = readout->parent->readouts;
        for (int i = 0; i < readouts->n; i++) {
            if (readouts->data[i] == readout) {
                readouts->data[i] = NULL;
            }
        }
    }
    psTrace("psModules.camera", 9, "Freeing readout %zd\n", (size_t) readout);
    psFree(readout->image);
    psFree(readout->mask);
    psFree(readout->weight);
    psFree(readout->analysis);
    psFree(readout->bias);
}

static void cellFree(pmCell *cell)
{

    // if this cell has a parent, drop that instance
    if (cell->parent) {
        psTrace("psModules.camera", 9, "Removing cell %zd from chip %zd...\n", (size_t)cell, (size_t)cell->parent);
        psArray *cells = cell->parent->cells;
        for (int i = 0; i < cells->n; i++) {
            if (cells->data[i] == cell) {
                cells->data[i] = NULL;
            }
        }
    }
    psTrace("psModules.camera", 9, "Freeing cell %zd\n", (size_t)cell);
    pmCellFreeReadouts(cell);
    psFree(cell->readouts);

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

static void chipFree(pmChip* chip)
{
    // if this chip has a parent, drop that instance
    if (chip->parent) {
        psTrace("psModules.camera", 9, "Removing chip %zd from fpa %zd...\n", (size_t)chip, (size_t)chip->parent);
        psArray *chips = chip->parent->chips;
        for (int i = 0; i < chips->n; i++) {
            if (chips->data[i] == chip) {
                chips->data[i] = NULL;
            }
        }
    }

    psTrace("psModules.camera", 9, "Freeing chip %zd\n", (size_t)chip);
    pmChipFreeCells(chip);
    psFree(chip->cells);

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

    # if FPA_ASTROM

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


static void FPAFree(pmFPA *fpa)
{
    psTrace("psModules.camera", 9, "Freeing fpa %zd\n", (size_t)fpa);

    // NULL the parent pointers
    psArray *chips = fpa->chips;
    for (int i = 0 ; i < chips->n ; i++) {
        pmChip *tmpChip = chips->data[i];
        if (! tmpChip) {
            continue;
        }
        tmpChip->parent = NULL;
    }
    psFree(fpa->chips);
    psFree(fpa->concepts);
    psFree(fpa->analysis);
    psFree(fpa->camera);
    psFree(fpa->hdu);

    # if FPA_ASTROM

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

void pmCellFreeReadouts(pmCell *cell)
{
    PS_ASSERT_PTR_NON_NULL(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;
        }
        tmpReadout->parent = NULL;
        psTrace("psModules.camera", 9, "Will now free readout %zd...\n", (size_t)tmpReadout);
    }
    cell->readouts = psArrayRealloc(cell->readouts, 0);
    cell->readouts->n = 0;
}


void pmChipFreeCells(pmChip *chip)
{
    PS_ASSERT_PTR_NON_NULL(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;
        }
        tmpCell->parent = NULL;
        pmCellFreeReadouts(tmpCell);// Drop all readouts the cell holds
    }
    chip->cells = psArrayRealloc(chip->cells, 0);
    chip->cells->n = 0;
}

void pmReadoutFreeData (pmReadout *readout)
{
    psFree(readout->image);
    psFree(readout->mask);
    psFree(readout->weight);
    psFree(readout->bias);

    readout->image = NULL;
    readout->weight = NULL;
    readout->mask = NULL;
    readout->bias = psListAlloc(NULL);
}

void pmCellFreeData(pmCell *cell)
{
    PS_ASSERT_PTR_NON_NULL(cell,);

    for (int i = 0; i < cell->readouts->n; i++) {
        pmReadoutFreeData(cell->readouts->data[i]);
    }
    if (cell->hdu) {
        psFree(cell->hdu->images);
        psFree(cell->hdu->weights);
        psFree(cell->hdu->masks);
        // psFree(cell->hdu->header);

        cell->hdu->images = NULL;
        cell->hdu->weights = NULL;
        cell->hdu->masks = NULL;
        // cell->hdu->header = NULL;
    }
}

void pmChipFreeData(pmChip *chip)
{
    PS_ASSERT_PTR_NON_NULL(chip,);

    for (int i = 0; i < chip->cells->n; i++) {
        pmCellFreeData(chip->cells->data[i]);
    }
    if (chip->hdu) {
        psFree(chip->hdu->images);
        psFree(chip->hdu->weights);
        psFree(chip->hdu->masks);
        // psFree(chip->hdu->header);

        chip->hdu->images = NULL;
        chip->hdu->weights = NULL;
        chip->hdu->masks = NULL;
        // chip->hdu->header = NULL;
    }
}

void pmFPAFreeData(pmFPA *fpa)
{
    PS_ASSERT_PTR_NON_NULL(fpa,);

    for (int i = 0; i < fpa->chips->n; i++) {
        pmChipFreeData(fpa->chips->data[i]);
    }
    if (fpa->hdu) {
        psFree(fpa->hdu->images);
        psFree(fpa->hdu->weights);
        psFree(fpa->hdu->masks);
        // psFree(fpa->hdu->header);

        fpa->hdu->images = NULL;
        fpa->hdu->weights = NULL;
        fpa->hdu->masks = NULL;
        // fpa->hdu->header = NULL;
    }
}

pmReadout *pmReadoutAlloc(pmCell *cell)
{
    pmReadout *tmpReadout = (pmReadout *)psAlloc(sizeof(pmReadout));
    psMemSetDeallocator(tmpReadout, (psFreeFunc) readoutFree);

    tmpReadout->image = NULL;
    tmpReadout->mask = NULL;
    tmpReadout->weight = NULL;
    tmpReadout->bias = psListAlloc(NULL);
    tmpReadout->analysis = psMetadataAlloc();
    tmpReadout->parent = cell;
    if (cell) {
        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

    tmpReadout->row0 = 0;
    tmpReadout->col0 = 0;

    return(tmpReadout);
}

pmCell *pmCellAlloc(
    pmChip *chip,
    const char *name
)
{
    pmCell *tmpCell = (pmCell *) psAlloc(sizeof(pmCell));
    psMemSetDeallocator(tmpCell, (psFreeFunc) cellFree);

    tmpCell->config = NULL;
    tmpCell->analysis = psMetadataAlloc();
    tmpCell->readouts = psArrayAlloc(0);
    tmpCell->parent = chip;
    if (chip) {
        chip->cells = psArrayAdd(chip->cells, 1, (psPtr) tmpCell);
    }
    tmpCell->hdu = NULL;
    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->concepts = psMetadataAlloc();
    tmpCell->conceptsRead = PM_CONCEPT_SOURCE_NONE;
    if (!psMetadataAddStr(tmpCell->concepts, PS_LIST_HEAD, "CELL.NAME", 0, NULL, name)) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CELL.NAME to metadata.\n");
    }
    pmConceptsBlankCell(tmpCell);

    return tmpCell;
}

pmChip *pmChipAlloc(
    pmFPA *fpa,
    const char *name)
{
    pmChip *tmpChip = (pmChip*)psAlloc(sizeof(pmChip));
    psMemSetDeallocator(tmpChip, (psFreeFunc) chipFree);

    #if FPA_ASTROM

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

    tmpChip->analysis = psMetadataAlloc();
    tmpChip->cells = psArrayAlloc(0);
    tmpChip->parent = fpa;
    if (fpa) {
        psArrayAdd(fpa->chips, 1, (psPtr)tmpChip);
    }
    tmpChip->hdu = NULL;
    tmpChip->mosaic = NULL;
    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->concepts = psMetadataAlloc();
    tmpChip->conceptsRead = PM_CONCEPT_SOURCE_NONE;
    if (!psMetadataAddStr(tmpChip->concepts, PS_LIST_HEAD, "CHIP.NAME", 0, NULL, name)) {
        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CHIP.NAME %s to concepts.\n", name);
    }
    pmConceptsBlankChip(tmpChip);
    return tmpChip;
}

pmFPA *pmFPAAlloc(const psMetadata *camera)
{
    pmFPA *tmpFPA = (pmFPA *) psAlloc(sizeof(pmFPA));
    psMemSetDeallocator(tmpFPA, (psFreeFunc) FPAFree);

    #if FPA_ASTROM

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

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

    tmpFPA->concepts = psMetadataAlloc();
    tmpFPA->conceptsRead = PM_CONCEPT_SOURCE_NONE;
    pmConceptsBlankFPA(tmpFPA);

    return tmpFPA;
}

static bool cellCheckParents(pmCell *cell)
{
    PS_ASSERT_PTR_NON_NULL(cell, true);

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

static bool chipCheckParents(pmChip *chip)
{
    PS_ASSERT_PTR_NON_NULL(chip, true);

    bool flag = true;
    for (long i = 0; i < chip->cells->n ; i++) {
        pmCell *tmpCell = (pmCell *) chip->cells->data[i];
        if (!tmpCell) {
            continue;
        }
        if (tmpCell->parent != chip) {
            tmpCell->parent = chip;
            flag = false;
        }

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

psBool pmFPACheckParents(pmFPA *fpa)
{
    PS_ASSERT_PTR_NON_NULL(fpa, true);

    bool flag = true;
    for (long i = 0; i < fpa->chips->n ; i++) {
        pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
        if (!tmpChip) {
            continue;
        }
        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)
{
    PS_ASSERT_PTR_NON_NULL(fpa, false);

    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)
{
    PS_ASSERT_PTR_NON_NULL(chip, false);

    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)
{
    PS_ASSERT_PTR_NON_NULL(cell, false);

    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;
}

bool pmFPACheckFileStatus(const pmFPA *fpa)
{
    PS_ASSERT_PTR_NON_NULL(fpa, false);

    for (int i = 0; i < fpa->chips->n; i++) {
        pmChip *chip = fpa->chips->data[i];
        if (!pmChipCheckFileStatus(chip)) {
            return false;
        }
    }
    return true;
}

bool pmChipCheckFileStatus(const pmChip *chip)
{
    PS_ASSERT_PTR_NON_NULL(chip, false);
    if (!chip->file_exists) {
        return false;
    }

    for (int i = 0; i < chip->cells->n; i++) {
        pmCell *cell = chip->cells->data[i];
        if (!pmCellCheckFileStatus(cell)) {
            return false;
        }
    }
    return true;
}

bool pmCellCheckFileStatus(const pmCell *cell)
{
    PS_ASSERT_PTR_NON_NULL(cell, false);
    if (!cell->file_exists) {
        return false;
    }

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

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

    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)
{
    PS_ASSERT_PTR_NON_NULL(chip, false);

    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)
{
    PS_ASSERT_PTR_NON_NULL(cell, false);

    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)
{
    PS_ASSERT_PTR_NON_NULL(chip, false);

    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, -1);

    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
                     )
{
    PS_ASSERT_PTR_NON_NULL(chip, -1);

    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;
}

static char *NameNONE = "NONE";
static char *NameFPA = "FPA";
static char *NameCHIP = "CHIP";
static char *NameCELL = "CELL";
static char *NameREADOUT = "READOUT";

char *pmFPALevelToName(pmFPALevel level)
{

    switch (level) {
    case PM_FPA_LEVEL_NONE:
        return NameNONE;
    case PM_FPA_LEVEL_FPA:
        return NameFPA;
    case PM_FPA_LEVEL_CHIP:
        return NameCHIP;
    case PM_FPA_LEVEL_CELL:
        return NameCELL;
    case PM_FPA_LEVEL_READOUT:
        return NameREADOUT;
    default:
        psAbort(PS_FILE_LINE, "You can't get here; level = %d", level);
    }
    return NULL;
}

pmFPALevel pmFPALevelFromName(const char *name)
{
    pmFPALevel val;

    if (name == NULL) {
        val = PM_FPA_LEVEL_NONE;
    } else if (!strcasecmp(name, "FPA"))     {
        val = PM_FPA_LEVEL_FPA;
    } else if (!strcasecmp(name, "CHIP"))    {
        val = PM_FPA_LEVEL_CHIP;
    } else if (!strcasecmp(name, "CELL"))    {
        val = PM_FPA_LEVEL_CELL;
    } else if (!strcasecmp(name, "READOUT")) {
        val = PM_FPA_LEVEL_READOUT;
    } else {
        val = PM_FPA_LEVEL_NONE;
    }

    return val;
}

