/** @file  pmSourceIO.c
 *
 *  @author EAM, IfA
 *
 *  @version $Revision: 1.20 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2007-01-26 04:40:42 $
 *
 *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
 *
 */

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

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <pslib.h>
#include "pmHDU.h"
#include "pmFPA.h"
#include "pmFPAview.h"
#include "pmFPAfile.h"

#include "pmPeaks.h"
#include "pmMoments.h"
#include "pmGrowthCurve.h"
#include "pmModel.h"
#include "pmPSF.h"
#include "pmSource.h"
#include "pmModelGroup.h"
#include "pmSourceIO.h"

// translations between psphot object types and dophot object types
int pmSourceDophotType (pmSource *source)
{
    switch (source->type) {

    case PM_SOURCE_TYPE_DEFECT:
    case PM_SOURCE_TYPE_SATURATED:
        return (8);

    case PM_SOURCE_TYPE_STAR:
        if (source->mode & PM_SOURCE_MODE_SATSTAR)
            return (10);
        if (source->mode & PM_SOURCE_MODE_POOR)
            return (7);
        if (source->mode & PM_SOURCE_MODE_FAIL)
            return (4);
        return (1);

    case PM_SOURCE_TYPE_EXTENDED:
        return (2);

    default:
        return (0);
    }
    return (0);
}

// Given a FITS file pointer, write the table of object data
bool pmFPAviewWriteObjects (const pmFPAview *view, pmFPAfile *file, pmConfig *config)
{

    pmFPA *fpa = file->fpa;

    if (view->chip == -1) {
        if (!pmFPAWriteObjects (fpa, view, file, config)) {
            psError(PS_ERR_IO, false, "Failed to write objects from fpa");
            return false;
        }
        return true;
    }

    if (view->chip >= fpa->chips->n) {
        psError(PS_ERR_UNKNOWN, false, "Writing chip == %d (>= chips->n == %ld)", view->chip, fpa->chips->n);
        return false;
    }
    pmChip *chip = fpa->chips->data[view->chip];

    if (view->cell == -1) {
        if (!pmChipWriteObjects (chip, view, file, config)) {
            psError(PS_ERR_IO, false, "Failed to write objects from chip");
            return false;
        }
        return true;
    }

    if (view->cell >= chip->cells->n) {
        psError(PS_ERR_UNKNOWN, false, "Writing cell == %d (>= cells->n == %ld)",
                view->cell, chip->cells->n);
        return false;
    }
    pmCell *cell = chip->cells->data[view->cell];

    if (view->readout == -1) {
        if (!pmCellWriteObjects (cell, view, file, config)) {
            psError(PS_ERR_IO, false, "Failed to write objects from cell");
            return false;
        }

        return true;
    }

    if (view->readout >= cell->readouts->n) {
        psError(PS_ERR_UNKNOWN, false, "Writing readout == %d (>= readouts->n == %ld)",
                view->readout, cell->readouts->n);
        return false;
    }
    pmReadout *readout = cell->readouts->data[view->readout];

    if (!pmReadoutWriteObjects (readout, view, file, config)) {
        psError(PS_ERR_IO, false, "Failed to write objects from readout %d", view->readout);
        return false;
    }

    return true;
}

// read in all chip-level Objects files for this FPA
bool pmFPAWriteObjects (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
{

    for (int i = 0; i < fpa->chips->n; i++) {

        pmChip *chip = fpa->chips->data[i];
        if (!pmChipWriteObjects (chip, view, file, config)) {
            psError(PS_ERR_IO, false, "Failed to write %dth chip", i);
            return false;
        }
    }
    return true;
}

// read in all cell-level Objects files for this chip
bool pmChipWriteObjects (pmChip *chip, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
{

    for (int i = 0; i < chip->cells->n; i++) {

        pmCell *cell = chip->cells->data[i];
        if (!pmCellWriteObjects (cell, view, file, config)) {
            psError(PS_ERR_IO, false, "Failed to write %dth cell", i);
            return false;
        }
    }
    return true;
}

// read in all readout-level Objects files for this cell
bool pmCellWriteObjects (pmCell *cell, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
{

    for (int i = 0; i < cell->readouts->n; i++) {

        pmReadout *readout = cell->readouts->data[i];
        if (!pmReadoutWriteObjects (readout, view, file, config)) {
            psError(PS_ERR_IO, false, "Failed to write %dth readout", i);
            return false;
        }
    }
    return true;
}

// read in all readout-level Objects files for this cell
bool pmReadoutWriteObjects (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
{

    bool status;
    char *exttype;
    char *realname;
    char *filename;
    char *dataname;
    char *headname;
    pmHDU *hdu;
    pmHDU *phu;
    psMetadata *updates;
    psMetadata *outhead;

    psArray *sources = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.SOURCES");
    if (sources == NULL)
        return false;

    bool create = file->mode == PM_FPA_MODE_WRITE ? true : false;

    switch (file->type) {
    case PM_FPA_FILE_RAW:
        filename = pmFPAfileNameFromRule (file->filerule, file, view);
        realname = pmConfigConvertFilename (filename, config, create);
        pmSourcesWriteRAW (sources, realname);
        psFree (realname);
        psFree (filename);
        break;

    case PM_FPA_FILE_OBJ:
        filename = pmFPAfileNameFromRule (file->filerule, file, view);
        realname = pmConfigConvertFilename (filename, config, create);
        pmSourcesWriteOBJ (sources, realname);
        psFree (realname);
        psFree (filename);
        break;

    case PM_FPA_FILE_SX:
        filename = pmFPAfileNameFromRule (file->filerule, file, view);
        realname = pmConfigConvertFilename (filename, config, create);
        pmSourcesWriteSX (sources, realname);
        psFree (realname);
        psFree (filename);
        break;

    case PM_FPA_FILE_CMP:
        // a SPLIT format : only one header and object table per file
        hdu = pmFPAviewThisHDU (view, file->fpa);
        filename = pmFPAfileNameFromRule (file->filerule, file, view);
        realname = pmConfigConvertFilename (filename, config, create);

        // copy the header to an output header, add the output header data
        outhead = psMetadataCopy (NULL, hdu->header);

        // copy over the entries saved by PSPHOT
        updates = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.HEADER");
        if (updates) {
            psMetadataCopy (outhead, updates);
        }

        // copy over the entries saved by PSASTRO
        updates = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.HEADER");
        if (updates) {
            psMetadataCopy (outhead, updates);
        }

        bool status = pmSourcesWriteCMP (sources, realname, outhead);
        psFree (outhead);
        psFree (realname);
        psFree (filename);

        if (!status) {
            psError(PS_ERR_IO, false, "Failed to write CMP file\n");
            return false;
        }
        break;

    case PM_FPA_FILE_CMF:
        // write a PHU? (only if input image is MEF)
        // write a header? (only if this is the first readout for cell)
        //   note that the file->header is set to track the last hdu->header written
        // write the data? (always?)

        // get the current header
        hdu = pmFPAviewThisHDU (view, file->fpa);

        // if file does not yet have a PHU, attempt to write one to disk
        // we only need a PHU if chips->n > 1 and file->fileLevel == FPA
        // otherwise, the chip header fills the PHU location
        if ((file->fileLevel == PM_FPA_LEVEL_FPA) && (file->fpa->chips->n > 1) && !file->phu) {

            // find the FPA phu
            phu = pmFPAviewThisPHU (view, file->fpa);

            // if there is no PHU, this is a single header+image (extension-less) file
            // if there is a PHU, write it out as a 'blank'
            outhead = psMetadataAlloc();
            if (phu) {
                psMetadataCopy (outhead, phu->header);
            } else {
                psMetadata *fileMD = psMetadataLookupMetadata (NULL, file->format, "FILE");
                psMetadataCopy (outhead, fileMD);
            }

            // if we have mosaic-level astrometry information, add it here:
            updates = psMetadataLookupPtr (&status, file->fpa->analysis, "PSASTRO.HEADER");
            if (updates) {
                psMetadataCopy (outhead, updates);
            }

            psMetadataAddBool (outhead, PS_LIST_TAIL, "EXTEND", PS_META_REPLACE, "this file has extensions", true);
            psFitsWriteBlank (file->fits, outhead, "");
            file->phu = true;
            psTrace ("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
            psFree (outhead);
        }

        // EXTNAME values used to for table data and image header segments
        dataname = pmFPAfileNameFromRule (file->extrule, file, view);
        headname = pmFPAfileNameFromRule (file->extxtra, file, view);

        // this header block is new, write it to disk
        if (hdu->header != file->header) {
            outhead = psMetadataCopy (NULL, hdu->header);

            // add EXTNAME, EXTHEAD, EXTTYPE to header
            // psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTNAME", PS_META_REPLACE, "extension name", headname);
            psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTDATA", PS_META_REPLACE, "name of table extension", dataname);
            psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTTYPE", PS_META_REPLACE, "extension type", "IMAGE");
            if (!file->phu) {
                // this hdu->header acts as the PHU: set EXTEND to be true
                psMetadataAddBool (outhead, PS_LIST_TAIL, "EXTEND", PS_META_REPLACE, "this file has extensions", true);
            }

            // save psphot and psastro metadata in the image (not table) header
            updates = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.HEADER");
            if (updates) {
                psMetadataCopy (outhead, updates);
            }

            updates = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.HEADER");
            if (updates) {
                psMetadataCopy (outhead, updates);
            }

            psFitsWriteBlank (file->fits, outhead, headname);
            psTrace ("pmFPAfile", 5, "wrote ext head %s (type: %d)\n", file->filename, file->type);
            file->header = hdu->header;
            psFree (outhead);
        }

        // create a header to hold the output data
        outhead = psMetadataAlloc ();

        exttype = psStringCopy ("SMPDATA");  // XXX derive this from the recipes
        psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTHEAD", PS_META_REPLACE, "name of image extension w/", headname);
        psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTTYPE", PS_META_REPLACE, "extension type", exttype);
        psFree (exttype);

        if (!pmSourcesWriteCMF (file->fits, sources, outhead, dataname)) {
            psError(PS_ERR_IO, false, "writing CMF data to %s\n", file->filename);
            psFree (headname);
            psFree (dataname);
            psFree (outhead);
            return false;
        }

        psTrace ("pmFPAfile", 5, "wrote ext data %s (type: %d)\n", file->filename, file->type);

        psFree (headname);
        psFree (dataname);
        psFree (outhead);
        break;

    default:
        fprintf (stderr, "warning: type mismatch\n");
        break;
    }
    return true;
}
// a MEF CMF file has: PHU, CELL-HEAD, TABLE, CELL-HEAD, TABLE, TABLE, TABLE...

// Given a FITS file pointer, read the table of object data
bool pmFPAviewReadObjects (const pmFPAview *view, pmFPAfile *file, pmConfig *config)
{
    pmFPA *fpa = file->fpa;

    if (view->chip == -1) {
        pmFPAReadObjects (fpa, view, file, config);
        return true;
    }

    if (view->chip >= fpa->chips->n) {
        return false;
    }
    pmChip *chip = fpa->chips->data[view->chip];

    if (view->cell == -1) {
        pmChipReadObjects (chip, view, file, config);
        return true;
    }

    if (view->cell >= chip->cells->n) {
        return false;
    }
    pmCell *cell = chip->cells->data[view->cell];

    if (view->readout == -1) {
        pmCellReadObjects (cell, view, file, config);
        return true;
    }

    if (view->readout >= cell->readouts->n) {
        return false;
    }
    pmReadout *readout = cell->readouts->data[view->readout];

    pmReadoutReadObjects (readout, view, file, config);
    return true;
}

// read in all chip-level Objects files for this FPA
bool pmFPAReadObjects (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
{
    for (int i = 0; i < fpa->chips->n; i++) {
        pmChip *chip = fpa->chips->data[i];
        pmChipReadObjects (chip, view, file, config);
    }
    return true;
}

// read in all cell-level Objects files for this chip
bool pmChipReadObjects (pmChip *chip, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
{
    for (int i = 0; i < chip->cells->n; i++) {
        pmCell *cell = chip->cells->data[i];
        pmCellReadObjects (cell, view, file, config);
    }
    return true;
}

// read in all readout-level Objects files for this cell
bool pmCellReadObjects (pmCell *cell, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
{
    // multiple readout mode is not yet defined for CMP or CMF files
    // if they have not been allocated, allocate a single readout
    if (!cell->readouts || !cell->readouts->n) {
        pmReadout *readout = pmReadoutAlloc (cell);
        psFree (readout);
    }

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

// read in all readout-level Objects files for this cell
bool pmReadoutReadObjects (pmReadout *readout, const pmFPAview *view, pmFPAfile *file, pmConfig *config)
{

    bool status;
    psArray *sources = NULL;
    pmHDU *hdu;

    switch (file->type) {
    case PM_FPA_FILE_OBJ:
        fprintf (stderr, "warning: OBJ is not supported as an input object format\n");
        break;

    case PM_FPA_FILE_SX:
        fprintf (stderr, "warning: SX is not supported as an input object format\n");
        break;

    case PM_FPA_FILE_CMP:
        // a SPLIT format : only one header and object table per file

        // read in header, if not yet loaded
        hdu = pmFPAviewThisHDU (view, file->fpa);
        if (file->filename)
            psFree (file->filename);
        file->filename = pmFPAfileNameFromRule (file->filerule, file, view);

        // indirect filenames
        if (!strcasecmp (file->filename, "@FILES")) {
            psFree (file->filename);
            char *extra = pmFPAfileNameFromRule (file->filextra, file, view);
            file->filename = psMetadataLookupStr (&status, file->names, extra);
            psFree (extra);
            if (file->filename == NULL)
                return false;
            // psMetadataLookupStr just returns a view, file->filename must be protected
            psMemIncrRefCounter (file->filename);
        }

        bool create = file->mode == PM_FPA_MODE_WRITE ? true : false;
        psString realname = pmConfigConvertFilename (file->filename, config, create);

        file->fits = psFitsOpen (realname, "r");
        if (hdu->header != NULL) {
            psFree (hdu->header);
        }
        hdu->header = psFitsReadHeader (NULL, file->fits);
        psFitsClose (file->fits);
        file->fits = NULL;

        sources = pmSourcesReadCMP (realname, hdu->header);
        psFree (realname);
        break;

    case PM_FPA_FILE_CMF:
        // read in header, if not yet loaded
        hdu = pmFPAviewThisHDU (view, file->fpa);
        if (hdu->header == NULL) {
            char *headname = pmFPAfileNameFromRule (file->extxtra, file, view);
            psFitsMoveExtName (file->fits, headname);
            hdu->header = psFitsReadHeader (NULL, file->fits);
            psFree (headname);
        }

        // we need to find the corresponding table EXTNAME.
        // first check the header
        char *dataname = psMetadataLookupStr (NULL, hdu->header, "EXTDATA");
        psMemIncrRefCounter (dataname); // so free below is safe
        if (!dataname) {
            // if it is not defined in the header, try the value in the filerule
            dataname = pmFPAfileNameFromRule (file->extrule, file, view);
            if (!dataname)
                psAbort ("pmSourceIO", "cannot determine data EXTNAME");
        }
        if (!psFitsMoveExtName (file->fits, dataname)) {
            psAbort ("pmSourceIO", "cannot find data extension %s in %s", dataname, file->filename);
        }
        sources = pmSourcesReadCMF (file->fits, hdu->header);
        psFree (dataname);
        break;

    default:
        fprintf (stderr, "warning: type mismatch\n");
        break;
    }
    readout->data_exists = true;
    status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY, "psphot sources", sources);
    psFree (sources);
    return true;
}


