#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 "pmFPARead.h"
#include "pmFPAWrite.h"
#include "pmFPAfileFitsIO.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"
#include "pmFPA_MANAPLOT.h"
#include "pmSourcePlots.h"
#include "pmDetrendDB.h"

// Open a file that contains an image
static bool openImage(pmFPAfile *file,  // File to open
                      const pmFPAview *view, // View pointing to open level
                      const char *mode,  // Open mode (for psFitsOpen)
                      pmConfig *config  // Configuration
                     )
{
    file->fits = psFitsOpen (file->filename, mode);
    if (file->fits == NULL) {
        psError(PS_ERR_IO, false, "error opening file %s\n", file->filename);
        return false;
    }
    file->state = PM_FPA_STATE_OPEN;

    // In some cases, I need to call pmFPAAddSourceFromHeader after I've opened the file,
    // specifically if I have not called this function on startup.  This happens for the
    // images supplied by the detrend database, which are only identified here (above).
    // this is never true for the output images, which are constructed later

    if (file->mode == PM_FPA_MODE_READ) {
        bool addSource = false;
        switch (file->fileLevel) {
        case PM_FPA_LEVEL_FPA:
            addSource = !file->fpa->hdu;
            break;
        case PM_FPA_LEVEL_CHIP: {
                pmChip *chip = pmFPAviewThisChip(view, file->fpa);
                if (!chip) {
                    psError (PS_ERR_IO, true, "inconsistent file/fpa: fileLevel is CHIP, view is FPA");
                    return false;
                }
                addSource = !chip->hdu;
                break;
            }
        case PM_FPA_LEVEL_CELL: {
                pmCell *cell = pmFPAviewThisCell(view, file->fpa);
                if (!cell) {
                    psError (PS_ERR_IO, true, "inconsistent file/fpa: fileLevel is CELL, view is FPA");
                    return false;
                }
                addSource = !cell->hdu;
                break;
            }
        default:
            psAbort ("pmFPAfileIO", "fileLevel not correctly set");
            break;
        }

        if (addSource) {
            psMetadata *phu = psFitsReadHeader (NULL, file->fits);
            if (!file->format) {
                file->format = pmConfigCameraFormatFromHeader (config, phu);
                if (!file->format) {
                    psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", file->filename);
                    psFree(phu);
                    return NULL;
                }
            } else {
                bool valid;
                if (!pmConfigValidateCameraFormat (&valid, file->format, phu)) {
                    psError (PS_ERR_UNKNOWN, false, "Error in config scripts\n");
                    psFree (phu);
                    return NULL;
                }
                if (!valid) {
                    psError(PS_ERR_IO, false, "file %s is not from the required camera", file->filename);
                    psFree (phu);
                    return NULL;
                }
            }
            pmFPAview *thisView = pmFPAAddSourceFromHeader (file->fpa, phu, file->format);
            psFree (thisView);
            psFree (phu);
            // XXX we can check the output view to be sure it corresponds to our current view
        }
    }
    if (file->mode == PM_FPA_MODE_WRITE) {
        // Want to write out any potential blank
        pmFPA *fpa = pmFPAfileSuitableFPA(file, view, config);

        switch (file->fileLevel) {
        case PM_FPA_LEVEL_FPA:
            pmFPAWrite(fpa, file->fits, NULL, true, false);
            break;
        case PM_FPA_LEVEL_CHIP: {
                pmChip *chip = pmFPAviewThisChip(view, fpa);
                pmChipWrite(chip, file->fits, NULL, true, false);
                break;
            }
        case PM_FPA_LEVEL_CELL: {
                pmCell *cell = pmFPAviewThisCell(view, fpa);
                pmCellWrite(cell, file->fits, NULL, true);
                break;
            }
        default:
            psAbort ("pmFPAfileIO", "fileLevel not correctly set");
            break;
        }

        psFree(fpa);
    }

    return true;

}


// open file (if not already opened)
bool pmFPAfileOpen (pmFPAfile *file, const pmFPAview *view, pmConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(file, false);
    PS_ASSERT_PTR_NON_NULL(view, false);

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

    if (file->state & PM_FPA_STATE_INACTIVE) {
        psTrace("psModules.camera", 6, "skip open for %s, files is inactive", file->name);
        return true;
    }

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

    // get the current level
    pmFPALevel level = pmFPAviewLevel (view);

    // do we need to open this file?
    if (level != file->fileLevel) {
        psTrace("psModules.camera", 6, "skip open of %s at this level %s: fileLevel is %s",
                file->name, pmFPALevelToName(level), pmFPALevelToName(file->fileLevel));
        return true;
    }

    if (file->mode == PM_FPA_MODE_NONE) {
        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_NONE");
        return false;
    }
    if (file->mode == PM_FPA_MODE_INTERNAL) {
        psError(PS_ERR_IO, true, "File is 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->mode == PM_FPA_MODE_WRITE) && !file->save) {
        psTrace("psModules.camera", 6, "skip open for %s, output file not requested", file->name);
        return true;
    }

    // determine the file name
    // free a name allocated earlier
    // XXX for CMP files which defer opening, we have an inconsistent value
    // for file->extra here and in the pmFPAfileDefine fucntions.
    //
    psFree (file->filename);
    file->filename = pmFPAfileNameFromRule (file->filerule, file, view);
    if (file->filename == NULL) {
        psError(PS_ERR_IO, true, "Filename is NULL");
        return false;
    }

    // indirect filenames
    if (!strcasecmp (file->filename, "@FILES")) {
        char *extra = pmFPAfileNameFromRule (file->filextra, file, view);
        if (extra == NULL) {
            psError(PS_ERR_IO, false, "error converting extrule to name %s\n", file->extrule);
            return false;
        }

        psFree (file->filename);
        file->filename = psMetadataLookupStr (&status, file->names, extra);

        if (file->filename == NULL) {
            psError(PS_ERR_IO, true, "filename lookup error (@FILES) for %s : %s\n", file->filextra, extra);
            psFree (extra);
            return false;
        }
        // psMetadataLookupStr just returns a view, file->filename must be protected
        psMemIncrRefCounter (file->filename);
        psFree (extra);
    }

    // get name from detrend database
    // extrule expands to yield the class_id
    // filextra contains the desired -det_id detID -iteration iter string
    if (!strcasecmp (file->filename, "@DETDB")) {
        char *extra = pmFPAfileNameFromRule (file->extrule, file, view);
        if (extra == NULL) {
            psError(PS_ERR_IO, false, "error converting extrule to name %s\n", file->extrule);
            return false;
        }
        psTrace ("pmFPAfile", 6, "looking for detrend (%s, %s)\n", file->filextra, extra);

        psFree (file->filename);
        file->filename = pmDetrendFile(file->filextra, extra, config);
        if (file->filename == NULL) {
            psError(PS_ERR_IO, false, "failed to find a valid detrend image for detID %s : classID %s\n", file->filextra, extra);
            psFree (extra);
            return false;
        }

        psTrace ("pmFPAfile", 6, "got detrend file %s\n", file->filename);
        psFree (extra);
    }

    // apply filename mangling rules (file://, path://, neb://)
    bool create = file->mode == PM_FPA_MODE_WRITE ? true : false;
    psString tmpName = pmConfigConvertFilename (file->filename, config, create);
    psFree (file->filename);
    file->filename = tmpName;

    switch (file->type) {
        // open the FITS types:
    case PM_FPA_FILE_IMAGE:
    case PM_FPA_FILE_MASK:
    case PM_FPA_FILE_WEIGHT:
    case PM_FPA_FILE_FRINGE:
        psTrace ("pmFPAfile", 5, "opening %s (type: %d)\n", file->filename, file->type);
        if (!openImage(file, view, mode, config)) {
            psError(PS_ERR_UNKNOWN, false, "Unable to open image %s (type %d)\n", file->filename, file->type);
            return false;
        }
        break;
        // open the FITS object files
    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) {
            psError(PS_ERR_IO, false, "error opening file %s\n", file->filename);
            return false;
        }
        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:
    case PM_FPA_FILE_KAPA:
    case PM_FPA_FILE_MANAPLOT:
        psTrace ("pmFPAfile", 5, "defer opening %s\n", file->filename);
        break;

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

bool pmFPAfileRead(pmFPAfile *file, const pmFPAview *view, pmConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(file, false);
    PS_ASSERT_PTR_NON_NULL(view, false);

    if (file->state & PM_FPA_STATE_INACTIVE) {
        psTrace("psModules.camera", 6, "skip read for %s, files is inactive", file->name);
        return true;
    }

    if (file->mode != PM_FPA_MODE_READ) {
        psTrace("psModules.camera", 6, "skip read for %s, mode is not READ", file->name);
        return true;
    }

    // an internal file should not be returned to here
    if (file->mode == PM_FPA_MODE_INTERNAL) {
        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
        return false;
    }

    // get the current level
    pmFPALevel level = pmFPAviewLevel (view);

    // do we need to open this file?
    if (level == file->fileLevel) {
        if (!pmFPAfileOpen (file, view, config)) {
            psError(PS_ERR_IO, false, "failed to open file %s", file->name);
            return false;
        }
    }

    // do we need to read this file?
    if (level != file->dataLevel) {
        psTrace("psModules.camera", 6, "skip reading of %s at this level %s: dataLevel is %s",
                file->name, pmFPALevelToName(level), pmFPALevelToName(file->dataLevel));
        return true;
    }

    // We need to read it --- double-check it's open!
    if (file->state == PM_FPA_STATE_CLOSED) {
        file->fileLevel = level;
        if (!pmFPAfileOpen (file, view, config)) {
            psError(PS_ERR_IO, false, "failed to open file %s when attempting to read", file->name);
            return false;
        }
    }

    switch (file->type) {
    case PM_FPA_FILE_IMAGE:
        if (!pmFPAviewReadFitsImage(view, file)) {
            psError(PS_ERR_UNKNOWN, false, "skipping %s (type: %d)\n", file->filename, file->type);
            return false;
        }
        psTrace ("pmFPAfile", 5, "reading %s (type: %d)\n", file->filename, file->type);
        break;
    case PM_FPA_FILE_MASK:
        if (!pmFPAviewReadFitsMask(view, file)) {
            psError(PS_ERR_UNKNOWN, false, "skipping %s (type: %d)\n", file->filename, file->type);
            return false;
        }
        psTrace ("pmFPAfile", 5, "reading %s (type: %d)\n", file->filename, file->type);
        break;
    case PM_FPA_FILE_WEIGHT:
        if (!pmFPAviewReadFitsWeight(view, file)) {
            psError(PS_ERR_UNKNOWN, false, "skipping %s (type: %d)\n", file->filename, file->type);
            return false;
        }
        psTrace ("pmFPAfile", 5, "reading %s (type: %d)\n", file->filename, file->type);
        break;
    case PM_FPA_FILE_FRINGE:
        if (!pmFPAviewReadFitsImage (view, file)) {
            psError(PS_ERR_UNKNOWN, false, "skipping %s (type: %d)\n", file->filename, file->type);
            return false;
        }
        psTrace ("pmFPAfile", 5, "reading %s (type: %d)\n", file->filename, file->type);

        if (!pmFPAviewReadFitsTable(view, file, "FRINGE")) {
            psError(PS_ERR_UNKNOWN, false, "Unable to read fringe data from %s.\n", file->filename);
            return false;
        }

        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, config);
        psTrace ("pmFPAfile", 5, "reading %s (type: %d)\n", file->filename, file->type);
        break;

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

    case PM_FPA_FILE_JPEG:
    case PM_FPA_FILE_KAPA:
    case PM_FPA_FILE_MANAPLOT:
        break;

    default:
        psError(PS_ERR_IO, true, "warning: type mismatch; saw type %d", file->type);
        return false;
    }
    return true;
}

bool pmFPAfileFreeData(pmFPAfile *file, const pmFPAview *view)
{
    PS_ASSERT_PTR_NON_NULL(file, false);
    PS_ASSERT_PTR_NON_NULL(view, false);

    if (file->state & PM_FPA_STATE_INACTIVE) {
        psTrace("psModules.camera", 6, "skip free for %s, files is inactive", file->name);
        return true;
    }

    // an internal file should not be returned to here
    if (file->mode == PM_FPA_MODE_INTERNAL) {
        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
        return false;
    }

    // get the current level
    pmFPALevel level = pmFPAviewLevel (view);

    // do we need to read this file?
    if (level != file->freeLevel) {
        psTrace("psModules.camera", 6, "skip free of %s at this level %s: freeLevel is %s",
                file->name, pmFPALevelToName(level), pmFPALevelToName(file->freeLevel));
        return true;
    }

    switch (file->type) {
    case PM_FPA_FILE_IMAGE:
    case PM_FPA_FILE_MASK:
    case PM_FPA_FILE_WEIGHT:
    case PM_FPA_FILE_FRINGE:
        if (pmFPAviewFreeData(view, file)) {
            psTrace ("pmFPAfile", 5, "freed %s for %s (type: %d)\n", file->filename, file->name, file->type);
            if (file->filename == NULL) {
                psTrace ("pmFPAfile", 5, "filename is not defined for %s\n", file->name);
            }
        } else {
            psError(PS_ERR_UNKNOWN, false, "skipping %s (type: %d)\n", file->filename, file->type);
            return false;
        }
        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:
        // pmFPAviewFreeObjects (view, file);
        psTrace ("pmFPAfile", 5, "NOT freeing %s (type: %d)\n", file->filename, file->type);
        break;

        // XXX not certain what I need to free here
    case PM_FPA_FILE_PSF:
    case PM_FPA_FILE_JPEG:
    case PM_FPA_FILE_KAPA:
    case PM_FPA_FILE_MANAPLOT:
        break;

    default:
        psError(PS_ERR_IO, true, "warning: type mismatch; saw type %d", file->type);
        return false;
    }
    return true;
}

bool pmFPAfileWrite(pmFPAfile *file, const pmFPAview *view, pmConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(file, false);
    PS_ASSERT_PTR_NON_NULL(view, false);

    if (file->state & PM_FPA_STATE_INACTIVE) {
        psTrace("psModules.camera", 6, "skip write for %s, files is inactive", file->name);
        return true;
    }

    if (file->mode != PM_FPA_MODE_WRITE) {
        psTrace("psModules.camera", 6, "skip write for %s, mode is not WRITE", file->name);
        return true;
    }

    // an internal file should not be returned to here
    if (file->mode == PM_FPA_MODE_INTERNAL) {
        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
        return false;
    }

    if (!file->save) {
        psTrace("psModules.camera", 6, "skip write for %s, save is FALSE", file->name);
        return true;
    }

    // get the current level
    pmFPALevel level = pmFPAviewLevel (view);

    // do we need to write this file?
    if ((file->mosaicLevel == PM_FPA_LEVEL_NONE && level != file->dataLevel) || // No mosaicking happening
            (file->mosaicLevel != PM_FPA_LEVEL_NONE && level != file->mosaicLevel)) { // Mosaicking happening
        psTrace("psModules.camera", 6, "skip writing of %s at this level %s: dataLevel is %s",
                file->name, pmFPALevelToName(level), pmFPALevelToName(file->dataLevel));
        return true;
    }

    // do we need to open this file?
    if (level >= file->fileLevel &&
            (file->mosaicLevel == PM_FPA_LEVEL_NONE ||
             (file->mosaicLevel != PM_FPA_LEVEL_NONE && level >= file->mosaicLevel))) {
        if (!pmFPAfileOpen (file, view, config)) {
            psError(PS_ERR_IO, false, "failed to open %s", file->filename);
            return false;
        }
    }

    switch (file->type) {
    case PM_FPA_FILE_IMAGE:
        pmFPAviewWriteFitsImage(view, file, config);
        psTrace ("pmFPAfile", 5, "wrote image %s (fpa: %p)\n", file->filename, file->fpa);
        break;
    case PM_FPA_FILE_MASK:
        pmFPAviewWriteFitsMask(view, file, config);
        psTrace ("pmFPAfile", 5, "wrote mask %s (fpa: %p)\n", file->filename, file->fpa);
        break;
    case PM_FPA_FILE_WEIGHT:
        pmFPAviewWriteFitsWeight(view, file, config);
        psTrace ("pmFPAfile", 5, "wrote weight %s (fpa: %p)\n", file->filename, file->fpa);
        break;
    case PM_FPA_FILE_FRINGE:
        pmFPAviewWriteFitsImage (view, file, config);
        psTrace ("pmFPAfile", 5, "wrote image %s (fpa: %p)\n", file->filename, file->fpa);
        pmFPAviewWriteFitsTable(view, file, "FRINGE");
        psTrace ("pmFPAfile", 5, "wrote fringe table %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:
        psTrace ("pmFPAfile", 5, "writing object %s (fpa: %p)\n", file->filename, file->fpa);
        if (!pmFPAviewWriteObjects (view, file, config)) {
            psError(PS_ERR_IO, false, "Failed to write object %s", file->filename);
            return false;
        }

        break;

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

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

    case PM_FPA_FILE_MANAPLOT:
        pmFPAviewWriteMANAPLOT (view, file, config);
        psTrace ("pmFPAfile", 5, "wrote MANAPLOT %s (fpa: %p)\n", file->filename, file->fpa);
        break;

    case PM_FPA_FILE_KAPA:
        pmFPAviewWriteSourcePlot (view, file, config);
        psTrace ("pmFPAfile", 5, "wrote KAPA %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, const pmConfig *config)
{
    PS_ASSERT_PTR_NON_NULL(file, false);
    PS_ASSERT_PTR_NON_NULL(view, false);

    // these are not error conditions; these are state tests
    if (file->state & PM_FPA_STATE_INACTIVE) {
        psTrace("psModules.camera", 6, "skip create for inactive file %s", file->name);
        return true;
    }
    if (file->mode != PM_FPA_MODE_WRITE) {
        psTrace("psModules.camera", 6, "skip create for non-write file %s", file->name);
        return true;
    }

    // an internal file should not be returned to here
    if (file->mode == PM_FPA_MODE_INTERNAL) {
        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
        return false;
    }

    // get the current level
    pmFPALevel level = pmFPAviewLevel (view);

    // don't create the file if the src (FPA) is not defined
    if (file->src == NULL) {
        psTrace("psModules.camera", 6, "skip create for FPA without src FPA for %s", file->name);
        return true;
    }

    // do we need to write this file?
    if (level != file->fileLevel || file->mosaicLevel != PM_FPA_LEVEL_NONE) {
        psTrace("psModules.camera", 6, "skip creation of %s at this level %s: fileLevel is %s",
                file->name, pmFPALevelToName(level), pmFPALevelToName(file->fileLevel));
        return true;
    }

    switch (file->type) {
    case PM_FPA_FILE_IMAGE:
    case PM_FPA_FILE_MASK:
    case PM_FPA_FILE_WEIGHT:
    case PM_FPA_FILE_FRINGE: {
            // create FPA structure component based on view
            #if 0
            psMetadata *format = file->format; // Camera format configuration
            if (!format) {
                // It's likely a mosaic, for which we don't yet know the appropriate format
                const psMetadata *camera = file->fpa->camera; // Camera configuration
                psMetadata *formats = psMetadataLookupMetadata(NULL, camera, "FORMATS"); // The FORMATS
                if (!formats) {
                    psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find FORMATS in camera configuration.\n");
                    return false;
                }
                format = psMetadataLookupMetadata(NULL, formats, config->formatName);
                if (!format) {
                    psError(PS_ERR_UNEXPECTED_NULL, false,
                            "Unable to find format %s in camera configuration.\n", config->formatName);
                    return false;
                }
                file->format = psMemIncrRefCounter(format);
            }
            #endif
            pmFPAAddSourceFromView (file->fpa, view, file->format);
            psTrace ("pmFPAfile", 5, "created fpa data elements for %s (fpa: %p)\n", file->name, 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:
    case PM_FPA_FILE_KAPA:
    case PM_FPA_FILE_MANAPLOT:
        break;

    default:
        psError(PS_ERR_IO, true, "Unsupported type for %s: %d", file->name, file->type);
        return false;
    }
    return true;
}

bool pmFPAfileClose (pmFPAfile *file, const pmFPAview *view)
{
    PS_ASSERT_PTR_NON_NULL(file, false);
    PS_ASSERT_PTR_NON_NULL(view, false);

    if (file->state & PM_FPA_STATE_INACTIVE) {
        psTrace("psModules.camera", 6, "skip close for %s, files is inactive", file->name);
        return true;
    }
    if (file->state == PM_FPA_STATE_CLOSED) {
        return true;
    }

    // an internal file should not be returned to here
    if (file->mode == PM_FPA_MODE_INTERNAL) {
        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
        return false;
    }

    // is current level == open level?
    pmFPALevel level = pmFPAviewLevel (view);
    if (file->fileLevel != level) {
        psTrace("psModules.camera", 6, "skip closing of %s at this level %s: dataLevel is %s",
                file->name, pmFPALevelToName(level), pmFPALevelToName(file->dataLevel));
        return true;
    }

    // check if we are actually open
    switch (file->type) {
        // check the FITS types
    case PM_FPA_FILE_IMAGE:
    case PM_FPA_FILE_MASK:
    case PM_FPA_FILE_WEIGHT:
    case PM_FPA_FILE_FRINGE:
    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:
    case PM_FPA_FILE_KAPA:
    case PM_FPA_FILE_MANAPLOT:
        break;

    default:
        psError(PS_ERR_IO, true, "type mismatch: %d", file->type);
        return false;
    }
    return true;
}

// set the state of the specified pmFPAfile to active (state == true) or inactive
// if name is NULL, set the state for all pmFPAfiles
bool pmFPAfileActivate (psMetadata *files, bool state, char *name)
{
    PS_ASSERT_PTR_NON_NULL(files, false);

    if (!name) {
        psMetadataItem *item = NULL;
        psMetadataIterator *iter = psMetadataIteratorAlloc (files, PS_LIST_HEAD, NULL);
        while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
            pmFPAfile *file = item->data.V;
            if (state) {
                file->state &= PS_NOT_U8(PM_FPA_STATE_INACTIVE);
            } else {
                file->state |= PM_FPA_STATE_INACTIVE;
            }
        }
        psFree (iter);
        return true;
    }

    bool status = false;
    pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
    if (!status) {
        psTrace("psModules.camera", 6, "%s is not a defined IO file", name);
        return false;
    }
    if (!file) {
        psError(PS_ERR_IO, true, "file %s is NULL", name);
        return false;
    }
    if (state) {
        file->state &= PS_NOT_U8(PM_FPA_STATE_INACTIVE);
    } else {
        file->state |= PM_FPA_STATE_INACTIVE;
    }
    return true;
}

// attempt create, read, write, close, or free pmFPAfiles available in files
// files are automatically opened before they are read
bool pmFPAfileIOChecks (pmConfig *config, const pmFPAview *view, pmFPAfilePlace place)
{
    PS_ASSERT_PTR_NON_NULL(config, false);
    PS_ASSERT_PTR_NON_NULL(config->files, false);
    PS_ASSERT_PTR_NON_NULL(view, false);

    psMetadata *files = config->files;

    // attempt to perform all create, read, write, close operations
    psMetadataItem *item = NULL;
    psMetadataIterator *iter = psMetadataIteratorAlloc (files, PS_LIST_HEAD, NULL);
    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
        pmFPAfile *file = item->data.V;

        switch (place) {
        case PM_FPA_BEFORE:
            if (!pmFPAfileOpen(file, view, config)) {
                psError(PS_ERR_IO, false, "failed OPEN in FPA_BEFORE block for %s", file->name);
                goto failure;
            }
            if (!pmFPAfileRead (file, view, config)) {
                psError(PS_ERR_IO, false, "failed READ in FPA_BEFORE block for %s", file->name);
                goto failure;
            }
            if (!pmFPAfileCreate(file, view, config)) {
                psError(PS_ERR_IO, false, "failed CREATE in FPA_BEFORE block for %s", file->name);
                goto failure;
            }
            break;
        case PM_FPA_AFTER:
            if (!pmFPAfileWrite (file, view, config)) {
                psError(PS_ERR_IO, false, "failed WRITE in FPA_AFTER block for %s", file->name);
                goto failure;
            }
            if (!pmFPAfileClose(file, view)) {
                psError(PS_ERR_IO, false, "failed CLOSE in FPA_AFTER block for %s", file->name);
                goto failure;
            }
            break;
        default:
            psAbort(PS_FILE_LINE, "You can't get here");
        }
    }

    // attempt to free data that is no longer needed
    psMetadataIteratorSet (iter, PS_LIST_HEAD);
    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
        pmFPAfile *file = item->data.V;

        switch (place) {
        case PM_FPA_BEFORE:
            break;
        case PM_FPA_AFTER:
            if (!pmFPAfileFreeData(file, view)) {
                if (!psMetadataRemoveKey(files, file->name)) {
                    psError(PS_ERR_IO, false, "failed to remove %s in FPA_AFTER block", file->name);
                    goto failure;
                }
            }
            break;
        default:
            psAbort(PS_FILE_LINE, "You can't get here");
        }
    }
    psFree (iter);
    return true;

failure:
    psFree (iter);
    return false;
}

