#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 "pmDetrendDB.h"

// 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 *extra;
    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;
    }

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

    // determine the file name
    // free a name allocated earlier
    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")) {
        psFree (file->filename);
        extra = pmFPAfileNameFromRule (file->filextra, file, view);
        file->filename = psMetadataLookupStr (&status, file->names, extra);
        psFree (extra);
        if (file->filename == NULL) {
            psError(PS_ERR_IO, true, "filename lookup error (@FILES) for %s : %s\n", file->filextra, extra);
            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->extrule, file, view);
        psTrace ("pmFPAfile", 6, "looking for detrend (%s, %s)\n", file->filextra, extra);
        file->filename = pmDetrendFile (file->filextra, extra);
        psTrace ("pmFPAfile", 6, "got detrend file %s\n", file->filename);
        psFree (extra);
        if (file->filename == NULL) {
            psError(PS_ERR_IO, true, "filename lookup error (@DETBD) for %s : %s\n", file->filextra, extra);
            return false;
        }
    }
    // if () "neb://" -> get from neb...

    switch (file->type) {
        // open the FITS types:
    case PM_FPA_FILE_IMAGE:
        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;

        // 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) {
            pmChip *chip;
            pmCell *cell;
            bool addSource = false;
            switch (file->fileLevel) {
            case PM_FPA_LEVEL_FPA:
                addSource = !file->fpa->hdu;
                break;
            case PM_FPA_LEVEL_CHIP:
                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:
                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 {
                    pmConfigValidateCameraFormat (file->format, phu);
                }
                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) {
            switch (file->fileLevel) {
            case PM_FPA_LEVEL_FPA:
                pmFPAWrite (file->fpa, file->fits, NULL, true, false);
                break;
            case PM_FPA_LEVEL_CHIP: {
                    pmChip *chip = pmFPAviewThisChip(view, file->fpa);
                    pmChipWrite (chip, file->fits, NULL, true, false);
                    break;
                }
            case PM_FPA_LEVEL_CELL: {
                    pmCell *cell = pmFPAviewThisCell(view, file->fpa);
                    pmCellWrite(cell, file->fits, NULL, true);
                    break;
                }
            default:
                psAbort ("pmFPAfileIO", "fileLevel not correctly set");
                break;
            }
        }
        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:
        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;
    }

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

    // 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:
        if (pmFPAviewFreeFitsImage (view, file)) {
            psTrace ("pmFPAfile", 5, "freed %s (type: %d)\n", file->filename, 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:
        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;
    }

    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 (level != file->dataLevel) {
        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) {
        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);
        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:
        psTrace ("pmFPAfile", 5, "writing object %s (fpa: %p)\n", file->filename, file->fpa);
        if (!pmFPAviewWriteObjects (view, file)) {
            psError(PS_ERR_IO, false, "Failed to write object %s", file->filename);
            return false;
        }

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

    // 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) {
        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:

        // create FPA structure component based on view
        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:
        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;
    }

    // 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_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:
        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 &= 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 &= 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);

    bool status = true;
    psMetadata *files = config->files;

    // attempt to perform all create, read, write
    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 (!pmFPAfileRead (file, view, config)) {
                psError(PS_ERR_IO, false, "failed READ in FPA_BEFORE block for %s", file->name);
                status = false;
            }
            if (!pmFPAfileCreate(file, view)) {
                psError(PS_ERR_IO, false, "failed CREATE in FPA_BEFORE block for %s", file->name);
                status = false;
            }
            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);
                status = false;
            }
            if (!pmFPAfileClose(file, view)) {
                psError(PS_ERR_IO, false, "failed CLOSE in FPA_AFTER block for %s", file->name);
                status = false;
            }
            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)) {
                psError(PS_ERR_IO, false, "failed FREE in FPA_AFTER block for %s", file->name);
                status = false;
            }
            break;
        default:
            psAbort(PS_FILE_LINE, "You can't get here");
        }
    }
    psFree (iter);
    return status;
}

