#include <stdio.h>
#include "pslib.h"
#include "psAdditionals.h"
#include "pmConfig.h"
#include "pmHDU.h"
#include "pmFPA.h"
#include "pmFPAConstruct.h"
#include "pmFPAview.h"
#include "pmFPAfile.h"
#include "pmFPACopy.h"
#include "pmFPARead.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 == NULL)
        return;

    psFree (file->fpa);
    psFree (file->readout);
    psFree (file->names);

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

    // these are just views ??
    // psFree (file->phu);
    // psFree (file->header);

    return;
}

pmFPAfile *pmFPAfileAlloc ()
{

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

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

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

    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;

    return (file);
}

pmFPAfile *pmFPAfileDefine (psMetadata *files, psMetadata *camera, pmFPA *fpa, char *name)
{

    bool status;
    char *depth;
    char *type;

    // select the FILERULES from the camera config
    psMetadata *filerules = psMetadataLookupPtr (&status, camera, "FILERULES");
    if (filerules == NULL) {
        psErrorStackPrint(stderr, "Can't find FILERULES in the CAMERA configuration!\n");
        return NULL;
    }

    // select the name from the FILERULES
    // check for alias name (type == STR, name is aliased name)
    char *realname = psMetadataLookupStr (&status, filerules, name);
    if (realname == NULL)
        realname = name;

    psMetadata *data = psMetadataLookupPtr (&status, filerules, realname);
    if (data == NULL) {
        psErrorStackPrint(stderr, "Can't find file concept %s!\n", name);
        return NULL;
    }

    pmFPAfile *file = pmFPAfileAlloc ();

    // save the name of this pmFPAfile
    file->name = psStringCopy (name);

    file->filerule = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "FILENAME.RULE"));
    file->filextra = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "FILENAME.XTRA"));
    file->extrule  = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "EXTNAME.RULE"));
    file->extxtra  = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "EXTNAME.XTRA"));

    file->fileDepth = PM_FPA_DEPTH_NONE;
    depth = psMetadataLookupStr (&status, data, "FILE.DEPTH");
    if (depth != NULL) {
        if (!strcasecmp (depth, "FPA"))     {
            file->fileDepth = PM_FPA_DEPTH_FPA;
        }
        if (!strcasecmp (depth, "CHIP"))    {
            file->fileDepth = PM_FPA_DEPTH_CHIP;
        }
        if (!strcasecmp (depth, "CELL"))    {
            file->fileDepth = PM_FPA_DEPTH_CELL;
        }
        if (!strcasecmp (depth, "READOUT")) {
            file->fileDepth = PM_FPA_DEPTH_READOUT;
        }
    }
    if (file->fileDepth == PM_FPA_DEPTH_NONE) {
        psLogMsg (__func__, 3, "warning: FILE.DEPTH is not set for %s\n", name);
    }

    file->dataDepth = PM_FPA_DEPTH_NONE;
    depth = psMetadataLookupStr (&status, data, "DATA.DEPTH");
    if (depth != NULL) {
        if (!strcasecmp (depth, "FPA"))     {
            file->dataDepth = PM_FPA_DEPTH_FPA;
        }
        if (!strcasecmp (depth, "CHIP"))    {
            file->dataDepth = PM_FPA_DEPTH_CHIP;
        }
        if (!strcasecmp (depth, "CELL"))    {
            file->dataDepth = PM_FPA_DEPTH_CELL;
        }
        if (!strcasecmp (depth, "READOUT")) {
            file->dataDepth = PM_FPA_DEPTH_READOUT;
        }
    }
    if (file->dataDepth == PM_FPA_DEPTH_NONE) {
        psLogMsg (__func__, 3, "warning: DATA.DEPTH is not set for %s\n", name);
    }

    file->type = PM_FPA_FILE_NONE;
    type = psMetadataLookupStr (&status, data, "FILE.TYPE");
    if (type != NULL) {
        if (!strcasecmp (type, "SX"))     {
            file->type = PM_FPA_FILE_SX;
        }
        if (!strcasecmp (type, "OBJ"))     {
            file->type = PM_FPA_FILE_OBJ;
        }
        if (!strcasecmp (type, "CMP"))     {
            file->type = PM_FPA_FILE_CMP;
        }
        if (!strcasecmp (type, "CMF"))     {
            file->type = PM_FPA_FILE_CMF;
        }
        if (!strcasecmp (type, "RAW"))     {
            file->type = PM_FPA_FILE_RAW;
        }
        if (!strcasecmp (type, "IMAGE"))     {
            file->type = PM_FPA_FILE_IMAGE;
        }
        if (!strcasecmp (type, "PSF"))     {
            file->type = PM_FPA_FILE_PSF;
        }
        if (!strcasecmp (type, "JPEG"))     {
            file->type = PM_FPA_FILE_JPEG;
        }
    }
    if (file->type == PM_FPA_FILE_NONE) {
        psLogMsg (__func__, 3, "warning: FILE.TYPE is not defined for %s\n", name);
    }

    file->mode = PM_FPA_MODE_NONE;
    type = psMetadataLookupStr (&status, data, "FILE.MODE");
    if (type != NULL) {
        if (!strcasecmp (type, "READ"))     {
            file->mode = PM_FPA_MODE_READ;
        }
        if (!strcasecmp (type, "WRITE"))     {
            file->mode = PM_FPA_MODE_WRITE;
        }
    }
    if (file->mode == PM_FPA_MODE_NONE) {
        psLogMsg (__func__, 3, "warning: FILE.MODE is not defined for %s\n", name);
    }

    if (fpa != NULL) {
        file->fpa = psMemIncrRefCounter(fpa);
    }

    // for WRITE type of data, the output format needs to be determined
    // this is only needed if the output file is not identical in structure to the input
    char *formatName = psMetadataLookupStr (&status, data, "FILE.FORMAT");
    if (formatName && strcasecmp (formatName, "NONE")) {
        psMetadata *formats = psMetadataLookupMD(&status, camera, "FORMATS"); // List of formats
        char *formatFile = psMetadataLookupStr (&status, formats, formatName);
        readConfig (&file->format, formatFile, formatName);
    }

    psMetadataAddPtr (files, PS_LIST_TAIL, name, PS_DATA_UNKNOWN, "", file);

    // we free this copy of file, but 'files' still has a copy
    psFree (file);

    // the returned value is a view into the version on 'files'
    return (file);
}

/*
pmFPAfile *pmFPAfileConstruct (psMetadata *files, psMetadata *format, psMetadata *camera, char *name)
{
    pmFPA *fpa = pmFPAConstruct (camera);
    pmFPAfile *file = pmFPAfileDefine (files, format, fpa, name);
    psFree (fpa);
    return file;
}
*/

// open file (if not already opened)
bool pmFPAfileOpen (pmFPAfile *file, const pmFPAview *view)
{

    bool status;
    char *extra;
    char *mode = NULL;
    char *readMode = "r";
    char *writeMode = "w";

    if (file->mode == PM_FPA_MODE_NONE) {
        return false;
    }
    if (file->mode == PM_FPA_MODE_INTERNAL) {
        return false;
    }
    if (file->mode == PM_FPA_MODE_READ) {
        mode = readMode;
    }
    if (file->mode == PM_FPA_MODE_WRITE) {
        mode = writeMode;
    }

    if (file->state == PM_FPA_STATE_OPEN) {
        return true;
    }

    // determine the file name
    file->filename = pmFPAfileNameFromRule (file->filerule, file, view);
    if (file->filename == NULL)
        return false;

    // indirect filenames
    if (!strcasecmp (file->filename, "@FILES")) {
        psFree (file->filename);
        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);
    }
    if (!strcasecmp (file->filename, "@DETDB")) {
        psFree (file->filename);
        extra = pmFPAfileNameFromRule (file->filextra, file, view);
        // file->filename = pmDetrendSelect (extra);
        psFree (extra);
        if (file->filename == NULL)
            return false;
        psMemIncrRefCounter (file->filename);
    }

    switch (file->type) {
        // open the FITS types:
    case PM_FPA_FILE_IMAGE:
    case PM_FPA_FILE_CMF:
        psTrace ("pmFPAfile", 5, "opening %s (type: %d)\n", file->filename, file->type);
        file->fits = psFitsOpen (file->filename, mode);
        if (file->fits == NULL)
            psAbort (__func__, "error opening file %s\n", file->filename);
        file->state = PM_FPA_STATE_OPEN;
        break;

        // defer opening TEXT types:
    case PM_FPA_FILE_SX:
    case PM_FPA_FILE_OBJ:
    case PM_FPA_FILE_CMP:
    case PM_FPA_FILE_RAW:
    case PM_FPA_FILE_PSF:
    case PM_FPA_FILE_JPEG:
        psTrace ("pmFPAfile", 5, "not opening %s (type: %d)\n", file->filename, file->type);
        break;

    default:
        fprintf (stderr, "warning: type mismatch for %s : %d\n", file->filename, file->type);
        return false;
    }
    return true;
}

bool pmFPAfileRead (pmFPAfile *file, const pmFPAview *view)
{
    if (file->mode != PM_FPA_MODE_READ)
        return false;

    // get the current depth
    pmFPAdepth depth = pmFPAviewDepth (view);

    // do we need to open this file?
    if (depth == file->fileDepth) {
        pmFPAfileOpen (file, view);
    }

    // do we need to read this file?
    if (depth != file->dataDepth)
        return false;

    switch (file->type) {
    case PM_FPA_FILE_IMAGE:
        if (pmFPAviewReadFitsImage (view, file)) {
            psTrace ("pmFPAfile", 5, "reading %s (type: %d)\n", file->filename, file->type);
        } else {
            psTrace ("pmFPAfile", 5, "skipping %s (type: %d)\n", file->filename, file->type);
        }
        break;

    case PM_FPA_FILE_SX:
    case PM_FPA_FILE_RAW:
    case PM_FPA_FILE_OBJ:
    case PM_FPA_FILE_CMP:
    case PM_FPA_FILE_CMF:
        pmFPAviewReadObjects (view, file);
        psTrace ("pmFPAfile", 5, "reading %s (type: %d)\n", file->filename, file->type);
        break;

    case PM_FPA_FILE_PSF:
        pmFPAviewReadPSFmodel (view, file);
        psTrace ("pmFPAfile", 5, "reading %s (type: %d)\n", file->filename, file->type);
        break;

    case PM_FPA_FILE_JPEG:
        break;

    default:
        fprintf (stderr, "warning: type mismatch\n");
        return false;
    }
    return true;
}

bool pmFPAfileWrite (pmFPAfile *file, const pmFPAview *view)
{
    if (file->mode != PM_FPA_MODE_WRITE)
        return false;

    // get the current depth
    pmFPAdepth depth = pmFPAviewDepth (view);

    // do we need to write this file?
    if (depth != file->dataDepth)
        return false;

    // do we need to open this file?
    if (depth >= file->fileDepth) {
        pmFPAfileOpen (file, view);
    }

    switch (file->type) {
    case PM_FPA_FILE_IMAGE:
        pmFPAviewWriteFitsImage (view, file);
        psTrace ("pmFPAfile", 5, "wrote image %s (fpa: %p)\n", file->filename, file->fpa);
        break;

    case PM_FPA_FILE_SX:
    case PM_FPA_FILE_RAW:
    case PM_FPA_FILE_OBJ:
    case PM_FPA_FILE_CMP:
    case PM_FPA_FILE_CMF:
        pmFPAviewWriteObjects (view, file);
        psTrace ("pmFPAfile", 5, "wrote object %s (fpa: %p)\n", file->filename, file->fpa);
        break;

    case PM_FPA_FILE_PSF:
        pmFPAviewWritePSFmodel (view, file);
        psTrace ("pmFPAfile", 5, "wrote PSF %s (fpa: %p)\n", file->filename, file->fpa);
        break;

    case PM_FPA_FILE_JPEG:
        pmFPAviewWriteJPEG (view, file);
        psTrace ("pmFPAfile", 5, "wrote PSF %s (fpa: %p)\n", file->filename, file->fpa);
        break;

    default:
        fprintf (stderr, "warning: type mismatch\n");
        return false;
    }
    return true;
}

// create the data elements (headers, images) appropriate for this view
bool pmFPAfileCreate (pmFPAfile *file, const pmFPAview *view)
{
    if (file->mode != PM_FPA_MODE_WRITE)
        return false;

    // get the current depth
    pmFPAdepth depth = pmFPAviewDepth (view);

    // do we need to write this file?
    if (depth != file->dataDepth)
        return false;

    // XXX is this a sufficient check to avoid creating elements?
    if (file->src == NULL)
        return false;

    switch (file->type) {
    case PM_FPA_FILE_IMAGE:
        /* create a PHU for thie file, if it does not exist */
        pmFPAfileCopyStructureView (file->fpa, file->src, file->format, file->xBin, file->yBin, view);
        psTrace ("pmFPAfile", 5, "created fpa data elements for %s (fpa: %p)\n", file->filename, file->fpa);
        break;

    case PM_FPA_FILE_SX:
    case PM_FPA_FILE_RAW:
    case PM_FPA_FILE_OBJ:
    case PM_FPA_FILE_CMP:
    case PM_FPA_FILE_CMF:
    case PM_FPA_FILE_PSF:
    case PM_FPA_FILE_JPEG:
        break;

    default:
        fprintf (stderr, "warning: type mismatch\n");
        return false;
    }
    return true;
}

bool pmFPAfileClose (pmFPAfile *file, const pmFPAview *view)
{
    if (file->state == PM_FPA_STATE_CLOSED) {
        return true;
    }

    // is current depth == open depth?
    pmFPAdepth depth = pmFPAviewDepth (view);
    if (file->fileDepth != depth) {
        return false;
    }

    // check if we are actually open
    switch (file->type) {
        // check the FITS types
    case PM_FPA_FILE_IMAGE:
    case PM_FPA_FILE_CMF:
        psFitsClose (file->fits);
        psTrace ("pmFPAfile", 5, "closing %s (type: %d)\n", file->filename, file->type);
        file->fits = NULL;
        file->phu = NULL;
        file->header = NULL;
        file->state = PM_FPA_STATE_CLOSED;
        break;

        // ignore the TEXT types
    case PM_FPA_FILE_SX:
    case PM_FPA_FILE_RAW:
    case PM_FPA_FILE_OBJ:
    case PM_FPA_FILE_CMP:
    case PM_FPA_FILE_PSF:
    case PM_FPA_FILE_JPEG:
        break;

    default:
        fprintf (stderr, "warning: type mismatch\n");
        return false;
    }
    return true;
}

// attempt open, read, write, or close pmFPAfiles in files
bool pmFPAfileIOChecks (psMetadata *files, const pmFPAview *view, pmFPAfilePlace place)
{
    // recipe override values (command-line options):
    psMetadataItem *item = NULL;
    psMetadataIterator *iter = psMetadataIteratorAlloc (files, PS_LIST_HEAD, NULL);
    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
        pmFPAfile *file = item->data.V;

        if (place == PM_FPA_BEFORE) {
            pmFPAfileRead (file, view);
            pmFPAfileCreate (file, view);
        } else {
            pmFPAfileWrite (file, view);
            pmFPAfileClose (file, view);
        }
    }
    psFree (iter);
    return true;
}

// create a file with the given name, assign it type "INTERNAL", and supply it with an image
// of the requested dimensions. (image only, mask and weight are ignored)
pmReadout *pmFPAfileCreateInternal (psMetadata *files, char *name, int Nx, int Ny, int type)
{
    pmReadout *readout = pmReadoutAlloc (NULL);
    readout->image = psImageAlloc (Nx, Ny, type);

    // I want an image from the
    pmFPAfile *file = pmFPAfileAlloc();
    file->mode = PM_FPA_MODE_INTERNAL;

    file->readout = readout;
    psMetadataAddPtr (files, PS_LIST_TAIL, name, PS_DATA_UNKNOWN, "", file);
    psFree (file);
    // we free this copy of file, but 'files' still has a copy

    return (readout);
}

bool pmFPAfileDropInternal (psMetadata *files, char *name)
{
    bool status;

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

    if (file->mode != PM_FPA_MODE_INTERNAL)
        return false;

    psMetadataRemoveKey (files, name);
    return true;
}

// select the readout from the named pmFPAfile; if the named file does not exist,
pmReadout *pmFPAfileThisReadout (psMetadata *files, const pmFPAview *view, const char *name)
{
    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;
}

// given an already-opened fits file, read the components corresponding
// to the specified view
bool pmFPAviewReadFitsImage (const pmFPAview *view, pmFPAfile *file)
{
    bool status;
    pmFPA *fpa = file->fpa;
    psFits *fits = file->fits;

    if (view->chip == -1) {
        status = pmFPARead (fpa, fits, NULL);
        return status;
    }

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

    if (view->cell == -1) {
        status = pmChipRead (chip, fits, NULL);
        return status;
    }

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

    if (view->readout == -1) {
        status = pmCellRead (cell, fits, NULL);
        return status;
    }
    return false;

    // XXX pmReadoutRead, pmReadoutReadSegement disabled for now
    # if (0)

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

    if (view->nRows == 0) {
        pmReadoutRead (readout, fits, NULL);
    } else {
        pmReadoutReadSegment (readout, fits, view->nRows, view->iRows, NULL, NULL);
    }
    return true;
    # endif
}

// given an already-opened fits file, write the components corresponding
// to the specified view
bool pmFPAviewWriteFitsImage (const pmFPAview *view, pmFPAfile *file)
{

    pmFPA *fpa = file->fpa;
    psFits *fits = file->fits;

    // pmFPAWrite takes care of all PHUs as needed
    if (view->chip == -1) {
        pmFPAWrite (fpa, fits, NULL);
        return true;
    }

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

    // do we need to write out a PHU for this entry?
    if (file->phu == NULL) {
        pmHDU *hdu = pmFPAviewThisHDU (view, file->fpa);
        pmHDU *phu = pmFPAviewThisPHU (view, file->fpa);
        // if this hdu is the phu, the write function below will create the phu
        if (hdu != phu) {
            // we assume that the PHU is just a header
            psMetadata *outhead = psMetadataCopy (NULL, phu->header);
            psMetadataAdd (outhead, PS_LIST_TAIL, "EXTEND", PS_DATA_BOOL | PS_META_REPLACE, "this file has extensions", true);
            psFitsWriteHeaderNotImage (file->fits, outhead);
            file->phu = phu->header;
            psTrace ("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
            psFree (outhead);
        }
    }

    if (view->cell == -1) {
        pmChipWrite (chip, fits, NULL);
        return true;
    }

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

    if (view->readout == -1) {
        pmCellWrite (cell, fits, NULL);
        return true;
    }
    return false;

    // XXX disable readout write for now
    # if (0)

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

    if (view->nRows == 0) {
        pmReadoutWrite (readout, fits, NULL, NULL);
    } else {
        pmReadoutWriteSegment (readout, fits, view->nRows, view->iRows, NULL, NULL);
    }
    return true;
    # endif
}

// look for the given name on the argument list.
// returns the file (a view to the one saved on config->files)
pmFPAfile *pmFPAfileFromArgs (bool *found, pmConfig *config, char *filename, char *argname)
{
    bool status;
    pmFPA *fpa = NULL;
    psFits *fits = NULL;
    pmFPAfile *file = NULL;
    psMetadata *phu = NULL;
    psMetadata *format = NULL;

    if (*found)
        return NULL;

    // we search the argument data for the named fileset (argname)
    psArray *infiles = psMetadataLookupPtr(&status, config->arguments, argname);
    if (!status)
        return NULL;
    if (infiles->n < 1)
        return NULL;

    // determine the current format from the header
    // if no camera has been specified, use the first image as a template for the rest.
    fits = psFitsOpen (infiles->data[0], "r");
    phu = psFitsReadHeader (NULL, fits);
    format = pmConfigCameraFormatFromHeader (config, phu);
    psFitsClose (fits);

    // build the template fpa, set up the basic view
    fpa = pmFPAConstruct (config->camera);

    // load the given filerule (from config->camera) and associate it with the fpa
    // the output file is just a view to the file on config->files
    file = pmFPAfileDefine (config->files, config->camera, fpa, filename);
    if (!file) {
        psErrorStackPrint(stderr, "file %s not defined\n", filename);
        psFree (fpa);
        psFree (format);
        return NULL;
    }

    // this file is (by virtue of being supplied in the argument list) coming from the fileset
    psFree (file->filerule);
    psFree (file->filextra);

    // adjust the rules to identify these files in the file->names data
    file->filerule = psStringCopy ("@FILES");
    // XXX this rule does not work in general
    file->filextra = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");

    // examine the list of input files and validate their cameras
    for (int i = 0; i < infiles->n; i++) {
        if (i > 0) {
            fits = psFitsOpen (infiles->data[i], "r");
            phu = psFitsReadHeader (NULL, fits);
            pmConfigValidateCameraFormat (format, phu);
            psFitsClose (fits);
        }

        // set the view to the corresponding entry for this phu
        pmFPAview *view = pmFPAAddSourceFromHeader (fpa, phu, format);

        // XXX is this the correct psMD to save the filename?
        char *name = pmFPAfileNameFromRule (file->filextra, file, view);
        psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[i]);

        psFree (view);
        psFree (name);
        psFree (phu);
    }
    psFree (fpa);
    psFree (format);
    *found = true;

    return file;
}

// XXX this this function through, then finish
# if 0
// look for the given name on the argument list.
// returns the file (a view to the one saved on config->files)
// in this case, each file should correspond to the same view
// (except at the readout level), of multiple FPAs
// XXX think this through a bit more:
//  - do we create multiple input fpas, or assign the same files to the single fpa?
//  - do we save the multiple pmFPAfiles in the config->files psMD?
//  - do we save the multiple filenames in the file->names psMD?
//  - if we have a single FPA and multiple names, we need a method to
//    turn on a specific name for the I/O actions.  probably true if we
//    have multiple FPAs as well.
pmFPAfile *pmFPAfileSetFromArgs (bool *found, pmConfig *config, char *filename, char *argname)
{
    bool status;
    pmFPA *fpa = NULL;
    psFits *fits = NULL;
    pmFPAfile *file = NULL;
    psMetadata *phu = NULL;
    psMetadata *format = NULL;

    if (*found)
        return NULL;

    // we search the argument data for the named fileset (argname)
    psArray *infiles = psMetadataLookupPtr(&status, config->arguments, argname);
    if (!status)
        return NULL;
    if (infiles->n < 1)
        return NULL;

    // determine the current format from the header
    // if no camera has been specified, use the first image as a template for the rest.
    fits = psFitsOpen (infiles->data[0], "r");
    phu = psFitsReadHeader (NULL, fits);
    format = pmConfigCameraFormatFromHeader (config, phu);
    psFitsClose (fits);

    // build the template fpa, set up the basic view
    fpa = pmFPAConstruct (config->camera);

    // load the given filerule (from config->camera) and associate it with the fpa
    // the output file is just a view to the file on config->files
    // XXX the filenames placed on config->files should include the seq number
    file = pmFPAfileDefine (config->files, config->camera, fpa, filename);
    if (!file) {
        psErrorStackPrint(stderr, "file %s not defined\n", filename);
        psFree (fpa);
        psFree (format);
        return NULL;
    }

    // this file is (by virtue of being supplied in the argument list) coming from the fileset
    psFree (file->filerule);
    psFree (file->filextra);

    // adjust the rules to identify these files in the file->names data
    file->filerule = psStringCopy ("@FILES");
    // XXX this rule does not work in general
    file->filextra = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");

    // examine the list of input files and validate their cameras
    for (int i = 0; i < infiles->n; i++) {
        if (i > 0) {
            fits = psFitsOpen (infiles->data[i], "r");
            phu = psFitsReadHeader (NULL, fits);
            pmConfigValidateCameraFormat (format, phu);
            psFitsClose (fits);
        }

        // set the view to the corresponding entry for this phu
        pmFPAview *view = pmFPAAddSourceFromHeader (fpa, phu, format);

        // XXX is this the correct psMD to save the filename?
        char *name = pmFPAfileNameFromRule (file->filextra, file, view);
        psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[i]);

        psFree (view);
        psFree (name);
        psFree (phu);
    }
    psFree (fpa);
    psFree (format);
    *found = true;

    return file;
}
# endif

// create a new output pmFPAfile based on an existing FPA
pmFPAfile *pmFPAfileFromFPA (pmConfig *config, pmFPA *src, int xBin, int yBin, char *filename)
{
    // XXX pmFPAConstruct has many leaks (6919)
    pmFPA *fpa = pmFPAConstruct (config->camera);
    pmFPAfile *file = pmFPAfileDefine (config->files, config->camera, fpa, filename);
    file->src = src; // inherit output elements from this source pmFPA
    file->xBin = xBin;
    file->yBin = yBin;

    return file;
}

// look for the given name on the argument list.
pmFPAfile *pmFPAfileFromConf (bool *found, pmConfig *config, char *filename, pmFPA *input)
{
    psFits *fits = NULL;
    pmFPAfile *file = NULL;
    psMetadata *phu = NULL;
    psMetadata *format = NULL;
    psArray *infiles = NULL;

    if (*found)
        return NULL;

    // a camera config is needed (as source of file rule)
    if (config->camera == NULL) {
        psErrorStackPrint (stderr, "camera is not defined\n");
        return NULL;
    }

    // expect @DETDB in the config filerule
    file = pmFPAfileDefine (config->files, config->camera, NULL, filename);
    if (!file) {
        psErrorStackPrint(stderr, "file %s not defined\n", filename);
        return NULL;
    }

    // image names come from the file->name list?
    if (!strcasecmp (file->filerule, "@FILES"))
        psAbort ("pmFPAfileFromConfig", "programming error");

    // image needs to come from the detrend database
    if (!strcasecmp (file->filerule, "@DETDB")) {
        // char *extra = pmFPAfileNameFromRule (file->filextra, file, view);
        // psArray *infiles = pmDetrendSelect (extra, input);
        psAbort ("pmFPAfileFromConfig", "@DETDB not yet defined");
    } else {
        infiles = psArrayAlloc(1);
        infiles->n = 1;
        infiles->data[0] = psStringCopy (file->filerule);
    }
    if (infiles == NULL)
        return NULL;
    if (infiles->n < 1) {
        psFree (infiles);
        return NULL;
    }

    // determine the current format from the header
    // if no camera has been specified, use the first image as a template for the rest.
    fits = psFitsOpen (infiles->data[0], "r");
    phu = psFitsReadHeader (NULL, fits);
    format = pmConfigCameraFormatFromHeader (config, phu);
    psFitsClose (fits);

    // build the template fpa, set up the basic view
    file->fpa = pmFPAConstruct (config->camera);

    // this file is (by virtue of being supplied in the argument list) coming from the fileset
    psFree (file->filerule);
    psFree (file->filextra);

    // adjust the rules to identify these files in the file->names data
    file->filerule = psStringCopy ("@FILES");
    // XXX this rule does not work in general
    file->filextra = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");

    // examine the list of input files and validate their cameras
    for (int i = 0; i < infiles->n; i++) {
        if (i > 0) {
            fits = psFitsOpen (infiles->data[i], "r");
            phu = psFitsReadHeader (NULL, fits);
            pmConfigValidateCameraFormat (format, phu);
            psFitsClose (fits);
        }

        // set the view to the corresponding entry for this phu
        pmFPAview *view = pmFPAAddSourceFromHeader (file->fpa, phu, format);

        // XXX is this the correct psMD to save the filename?
        char *name = pmFPAfileNameFromRule (file->filextra, file, view);
        psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[i]);

        psFree (view);
        psFree (name);
        psFree (phu);
    }
    psFree (format);
    psFree (infiles);
    *found = true;
    return file;
}

bool pmFPAfileAddFileNames (psMetadata *files, char *name, char *value, int mode)
{

    // add the output names to the output-type files
    psMetadataItem *item = NULL;
    psMetadataIterator *iter = psMetadataIteratorAlloc (files, PS_LIST_HEAD, NULL);
    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
        pmFPAfile *file = item->data.V;

        if (file->mode == mode) {
            psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", value);
        }
    }
    psFree (iter);
    return true;
}

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

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

    newName = psStringCopy (rule);

    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) {
                newName = psStringSubstitute (newName, name, "{CHIP.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) {
                newName = psStringSubstitute (newName, name, "{CELL.NAME}");
            }
        }
    }
    if (strstr (newName, "{EXTNAME}") != NULL) {
        pmHDU *hdu = pmFPAviewThisHDU (view, file->fpa);
        if (hdu->extname != NULL) {
            newName = psStringSubstitute (newName, hdu->extname, "{EXTNAME}");
        }
    }
    if (strstr (newName, "{OUTPUT}") != NULL) {
        char *name = psMetadataLookupStr (NULL, file->names, "OUTPUT");
        if (name != NULL) {
            newName = psStringSubstitute (newName, name, "{OUTPUT}");
        }
    }
    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)
{
    // pmFPAWrite takes care of all PHUs as needed
    if (view->chip == -1) {
        pmFPACopy (out, in);
        return true;
    }
    if (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) {
        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;
    }
    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, psMetadata *format, int xBin, int yBin, const pmFPAview *view)
{
    // pmFPAWrite takes care of all PHUs as needed
    if (view->chip == -1) {
        pmFPAAddSourceFromView (out, view, format);
        pmFPACopyStructure (out, in, xBin, yBin);
        return true;
    }
    if (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) {
        pmFPAAddSourceFromView (out, view, format);
        pmChipCopyStructure (outChip, inChip, xBin, yBin);
        return true;
    }
    if (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) {
        pmFPAAddSourceFromView (out, view, format);
        pmCellCopyStructure (outCell, inCell, xBin, yBin);
        return true;
    }
    return false;

    // XXX add readout / segment equivalents
}
