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

#include <stdio.h>
#include <string.h>
#include <pslib.h>

#include "pmConfig.h"
#include "pmHDU.h"
#include "pmFPA.h"
// #include "pmFPAMaskWeight.h"
#include "pmFPAConstruct.h"
#include "pmFPAview.h"
#include "pmFPAfile.h"
#include "pmFPACopy.h"
#include "pmConceptsPhotcode.h"
// #include "pmFPAWrite.h"
// #include "pmPeaks.h"
// #include "pmMoments.h"
// #include "pmModel.h"
// #include "pmSource.h"
// #include "pmSourceIO.h"
// #include "pmGrowthCurve.h"
// #include "pmPSF.h"
// #include "pmPSF_IO.h"
// #include "pmFPA_JPEG.h"

static void pmFPAfileFree(pmFPAfile *file)
{
    if (!file) {
        return;
    }

    psTrace ("pmFPAfileFree", 5, "freeing %s\n", file->name);
    psFree (file->fpa);
    psFree (file->readout);
    psFree (file->names);

    psFree (file->camera);
    psFree (file->format);
    psFree (file->name);

    if (file->fits != NULL) {
        psFitsClose (file->fits);
    }

    psFree (file->filerule);
    psFree (file->filextra);
    psFree (file->extrule);
    psFree (file->extxtra);

    psFree (file->filename);
    psFree (file->extname);

    return;
}

pmFPAfile *pmFPAfileAlloc()
{
    pmFPAfile *file = psAlloc(sizeof(pmFPAfile));
    psMemSetDeallocator(file, (psFreeFunc) pmFPAfileFree);

    file->phu = false;
    file->readout = NULL;
    file->header = NULL;

    file->fileLevel = PM_FPA_LEVEL_NONE;
    file->dataLevel = PM_FPA_LEVEL_NONE;
    file->freeLevel = PM_FPA_LEVEL_NONE;
    file->mosaicLevel = PM_FPA_LEVEL_NONE;

    file->fpa = NULL;
    file->fits = NULL;
    file->names = psMetadataAlloc();

    file->camera = NULL;
    file->format = NULL;
    file->name = NULL;

    file->filerule = NULL;
    file->filextra = NULL;
    file->extrule  = NULL;
    file->extxtra  = NULL;

    file->filename = NULL;
    file->extname  = NULL;

    file->type = PM_FPA_FILE_NONE;
    file->mode = PM_FPA_MODE_NONE;
    file->state = PM_FPA_STATE_CLOSED;

    file->xBin = 1;
    file->yBin = 1;
    file->src = NULL;

    file->save = false;

    return file;
}

// select the readout from the named pmFPAfile; if the named file does not exist,
pmReadout *pmFPAfileThisReadout (psMetadata *files, const pmFPAview *view, const char *name)
{
    PS_ASSERT_PTR_NON_NULL(files, false);
    PS_ASSERT_PTR_NON_NULL(view, false);
    PS_ASSERT_PTR_NON_NULL(name, false);
    PS_ASSERT_INT_POSITIVE(strlen(name), false);

    bool status;

    pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
    if (file == NULL) {
        return NULL;
    }

    // internal files have the readout as a separate element:
    if (file->mode == PM_FPA_MODE_INTERNAL) {
        return file->readout;
    }

    pmReadout *readout = pmFPAviewThisReadout (view, file->fpa);
    return readout;
}

// select the cell from the named pmFPAfile; if the named file does not exist,
pmCell *pmFPAfileThisCell (psMetadata *files, const pmFPAview *view, const char *name)
{
    PS_ASSERT_PTR_NON_NULL(files, false);
    PS_ASSERT_PTR_NON_NULL(view, false);
    PS_ASSERT_PTR_NON_NULL(name, false);
    PS_ASSERT_INT_POSITIVE(strlen(name), false);

    bool status;

    pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
    if (file == NULL) {
        return NULL;
    }

    // internal files have the readout as a separate element:
    if (file->mode == PM_FPA_MODE_INTERNAL) {
        return NULL;
    }

    pmCell *cell = pmFPAviewThisCell(view, file->fpa);
    return cell;
}

// select the readout from the named pmFPAfile; if the named file does not exist,
pmChip *pmFPAfileThisChip (psMetadata *files, const pmFPAview *view, const char *name)
{
    PS_ASSERT_PTR_NON_NULL(files, false);
    PS_ASSERT_PTR_NON_NULL(view, false);
    PS_ASSERT_PTR_NON_NULL(name, false);
    PS_ASSERT_INT_POSITIVE(strlen(name), false);

    bool status;

    pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
    if (file == NULL) {
        return NULL;
    }

    // internal files have the readout as a separate element:
    if (file->mode == PM_FPA_MODE_INTERNAL) {
        return NULL;
    }

    pmChip *chip = pmFPAviewThisChip (view, file->fpa);
    return chip;
}

// select the rule from the camera configuration, perform substitutions as needed
char *pmFPAfileNameFromRule (char *rule, pmFPAfile *file, const pmFPAview *view)
{
    PS_ASSERT_PTR_NON_NULL(rule, NULL);
    PS_ASSERT_INT_POSITIVE(strlen(rule), NULL);
    PS_ASSERT_PTR_NON_NULL(file, NULL);
    PS_ASSERT_PTR_NON_NULL(view, NULL);

    char *newName = NULL;     // destination for resulting name

    newName = psStringCopy (rule);

    if (strstr (newName, "{OUTPUT}") != NULL) {
        char *name = psMetadataLookupStr (NULL, file->names, "OUTPUT");
        if (name != NULL) {
            psStringSubstitute(&newName, name, "{OUTPUT}");
        }
    }
    if (strstr (newName, "{CHIP.NAME}") != NULL) {
        pmChip *chip = pmFPAviewThisChip (view, file->fpa);
        if (chip != NULL) {
            char *name = psMetadataLookupStr (NULL, chip->concepts, "CHIP.NAME");
            if (name != NULL) {
                psStringSubstitute(&newName, name, "{CHIP.NAME}");
            }
        }
    }
    if (strstr (newName, "{CHIP.N}") != NULL) {
        char *name = NULL;
        if (view->chip < 0) {
            psStringAppend (&name, "XX");
        } else {
            psStringAppend (&name, "%02d", view->chip);
        }
        psStringSubstitute(&newName, name, "{CHIP.N}");
        psFree (name);
    }
    if (strstr (newName, "{CELL.NAME}") != NULL) {
        pmCell *cell = pmFPAviewThisCell (view, file->fpa);
        if (cell != NULL) {
            char *name = psMetadataLookupStr (NULL, cell->concepts, "CELL.NAME");
            if (name != NULL) {
                psStringSubstitute(&newName, name, "{CELL.NAME}");
            }
        }
    }
    if (strstr (newName, "{CELL.N}") != NULL) {
        char *name = NULL;
        if (view->cell < 0) {
            psStringAppend (&name, "XX");
        } else {
            psStringAppend (&name, "%02d", view->cell);
        }
        psStringSubstitute(&newName, name, "{CELL.N}");
    }
    if (strstr (newName, "{EXTNAME}") != NULL) {
        pmHDU *hdu = pmFPAviewThisHDU (view, file->fpa);
        if (hdu->extname && *hdu->extname) {
            psStringSubstitute(&newName, hdu->extname, "{EXTNAME}");
        }
    }
    if (strstr (newName, "{FILTER}") != NULL) {
        if (file->fpa != NULL) {
            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.FILTER");
            if (name && *name) {
                psStringSubstitute(&newName, name, "{FILTER}");
            }
        }
    }
    if (strstr (newName, "{FILTER.ID}") != NULL) {
        if (file->fpa != NULL) {
            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.FILTERID");
            if (name && *name) {
                psStringSubstitute(&newName, name, "{FILTER.ID}");
            }
        }
    }
    if (strstr (newName, "{CAMERA}") != NULL) {
        if (file->fpa != NULL) {
            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.INSTRUMENT");
            if (name && *name) {
                psStringSubstitute(&newName, name, "{CAMERA}");
            }
        }
    }
    if (strstr (newName, "{INSTRUMENT}") != NULL) {
        if (file->fpa != NULL) {
            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.INSTRUMENT");
            if (name && *name) {
                psStringSubstitute(&newName, name, "{INSTRUMENT}");
            }
        }
    }
    if (strstr (newName, "{DETECTOR}") != NULL) {
        if (file->fpa != NULL) {
            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.DETECTOR");
            if (name && *name) {
                psStringSubstitute(&newName, name, "{DETECTOR}");
            }
        }
    }
    if (strstr (newName, "{TELESCOPE}") != NULL) {
        if (file->fpa != NULL) {
            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.TELESCOPE");
            if (name && *name) {
                psStringSubstitute(&newName, name, "{TELESCOPE}");
            }
        }
    }
    return newName;
}

// given an already-opened fits file, write the components corresponding
// to the specified view
bool pmFPAfileCopyView (pmFPA *out, pmFPA *in, const pmFPAview *view)
{
    PS_ASSERT_PTR_NON_NULL(out, false);
    PS_ASSERT_PTR_NON_NULL(in, false);
    PS_ASSERT_PTR_NON_NULL(view, false);

    // pmFPAWrite takes care of all PHUs as needed
    if (view->chip == -1) {
        pmFPACopy (out, in);
        return true;
    }
    if (view->chip >= in->chips->n) {
        psError(PS_ERR_IO, true, "Requested chip == %d >= in->chips->n == %ld", view->chip, in->chips->n);
        return false;
    }
    pmChip *inChip = in->chips->data[view->chip];
    pmChip *outChip = out->chips->data[view->chip];

    if (view->cell == -1) {
        pmChipCopy (outChip, inChip);
        return true;
    }
    if (view->cell >= inChip->cells->n) {
        psError(PS_ERR_IO, true, "Requested cell == %d>= inChip->cells->n == %ld",
                view->cell, inChip->cells->n);
        return false;
    }
    pmCell *inCell = inChip->cells->data[view->cell];
    pmCell *outCell = outChip->cells->data[view->cell];

    if (view->readout == -1) {
        pmCellCopy (outCell, inCell);
        return true;
    }
    psError(PS_ERR_UNKNOWN, true, "Returning false");
    return false;

    // XXX add readout / segment equivalents
}

// given an already-opened fits file, write the components corresponding
// to the specified view
bool pmFPAfileCopyStructureView (pmFPA *out, pmFPA *in, int xBin, int yBin, const pmFPAview *view)
{
    bool status;
    PS_ASSERT_PTR_NON_NULL(out, false);
    PS_ASSERT_PTR_NON_NULL(in, false);
    PS_ASSERT_PTR_NON_NULL(view, false);

    // pmFPAWrite takes care of all PHUs as needed
    if ((view->chip == -1) || in->hdu) {
        status = pmFPACopyStructure (out, in, xBin, yBin);
        return status;
    }
    if (view->chip >= in->chips->n) {
        psError(PS_ERR_IO, true, "Requested chip == %d >= in->chips->n == %ld", view->chip, in->chips->n);
        return false;
    }
    pmChip *inChip = in->chips->data[view->chip];
    pmChip *outChip = out->chips->data[view->chip];

    if ((view->cell == -1) || inChip->hdu) {
        status = pmChipCopyStructure (outChip, inChip, xBin, yBin);
        return status;
    }
    if (view->cell >= inChip->cells->n) {
        psError(PS_ERR_IO, true, "Requested cell == %d>= inChip->cells->n == %ld",
                view->cell, inChip->cells->n);
        return false;
    }
    pmCell *inCell = inChip->cells->data[view->cell];
    pmCell *outCell = outChip->cells->data[view->cell];

    if ((view->readout == -1) || inCell->hdu) {
        status = pmCellCopyStructure (outCell, inCell, xBin, yBin);
        return status;
    }
    psError(PS_ERR_UNKNOWN, true, "Returning false");
    return false;
}


pmFPAfileType pmFPAfileTypeFromString(const char *type)
{
    PS_ASSERT_STRING_NON_EMPTY(type, PM_FPA_FILE_NONE);

    if (!strcasecmp (type, "SX"))     {
        return PM_FPA_FILE_SX;
    }
    if (!strcasecmp (type, "OBJ"))     {
        return PM_FPA_FILE_OBJ;
    }
    if (!strcasecmp (type, "CMP"))     {
        return PM_FPA_FILE_CMP;
    }
    if (!strcasecmp (type, "CMF"))     {
        return PM_FPA_FILE_CMF;
    }
    if (!strcasecmp (type, "RAW"))     {
        return PM_FPA_FILE_RAW;
    }
    if (!strcasecmp (type, "IMAGE"))     {
        return PM_FPA_FILE_IMAGE;
    }
    if (!strcasecmp (type, "PSF"))     {
        return PM_FPA_FILE_PSF;
    }
    if (!strcasecmp (type, "JPEG"))     {
        return PM_FPA_FILE_JPEG;
    }
    if (!strcasecmp (type, "KAPA"))     {
        return PM_FPA_FILE_KAPA;
    }
    if (!strcasecmp (type, "MASK"))     {
        return PM_FPA_FILE_MASK;
    }
    if (!strcasecmp (type, "WEIGHT"))     {
        return PM_FPA_FILE_WEIGHT;
    }
    if (!strcasecmp (type, "FRINGE")) {
        return PM_FPA_FILE_FRINGE;
    }
    if (!strcasecmp (type, "HEADER"))     {
        return PM_FPA_FILE_HEADER;
    }

    return PM_FPA_FILE_NONE;
}
