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

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

#include "ppMerge.h"

const char *allFiles[] = { "PPMERGE.INPUT", "PPMERGE.INPUT.MASK", "PPMERGE.INPUT.WEIGHT",
                           "PPMERGE.OUTPUT", "PPMERGE.OUTPUT.COUNT", "PPMERGE.OUTPUT.SIGMA",
                           NULL };      // All files
const char *inputFiles[] = { "PPMERGE.INPUT", "PPMERGE.INPUT.MASK", "PPMERGE.INPUT.WEIGHT",
                             NULL };    // Input files
const char *outputFiles[] = { "PPMERGE.OUTPUT", "PPMERGE.OUTPUT.COUNT", "PPMERGE.OUTPUT.SIGMA",
                              NULL };   // Output files

// Select file list based on enum
static const char **selectFiles(ppMergeFiles files)
{
    switch (files) {
      case PPMERGE_FILES_ALL:    return allFiles;
      case PPMERGE_FILES_INPUT:  return inputFiles;
      case PPMERGE_FILES_OUTPUT: return outputFiles;
      default:
        psAbort("Invalid file option");
    }
    return NULL;
}

bool ppMergeFileReadInput(pmConfig *config, pmReadout *readout, int num, int rows)
{
    pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT", num);
    if (!pmReadoutReadChunk(readout, file->fits, 0, rows, 0, config)) {
        psError(PS_ERR_UNKNOWN, false, "Unable to read readout.");
        return false;
    }
    bool mdok;          // Status of MD lookup
    if (psMetadataLookupBool(&mdok, config->arguments, "INPUTS.MASKS")) {
        pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT.MASK", num);
        if (!pmReadoutReadChunkMask(readout, file->fits, 0, rows, 0, config)) {
            psError(PS_ERR_UNKNOWN, false, "Unable to read readout mask.");
            return false;
        }
    }
    if (psMetadataLookupBool(&mdok, config->arguments, "INPUTS.WEIGHTS")) {
        pmFPAfile *file = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT.WEIGHT", num);
        if (!pmReadoutReadChunkWeight(readout, file->fits, 0, rows, 0, config)) {
            psError(PS_ERR_UNKNOWN, false, "Unable to read readout weight.");
            return false;
        }
    }
    return true;
}

bool ppMergeFileOpenInput(pmConfig *config, const pmFPAview *view, int num)
{
    {
        pmFPAfile *input = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT", num);
        pmFPAview *fileView = pmFPAviewForLevel(input->fileLevel, view);
        if (!pmFPAfileOpen(input, fileView, config)) {
            psError(PS_ERR_UNKNOWN, false, "Unable to open image file %d", num);
            psFree(fileView);
            return false;
        }
        psFree(fileView);
    }
    bool mdok;          // Status of MD lookup
    if (psMetadataLookupBool(&mdok, config->arguments, "INPUTS.MASKS")) {
        pmFPAfile *mask = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT.MASK", num); // Mask file
        pmFPAview *fileView = pmFPAviewForLevel(mask->fileLevel, view);
        if (!pmFPAfileOpen(mask, fileView, config)) {
            psError(PS_ERR_UNKNOWN, false, "Unable to open mask file %d", num);
            psFree(fileView);
            return false;
        }
        psFree(fileView);
    }
    if (psMetadataLookupBool(&mdok, config->arguments, "INPUTS.WEIGHTS")) {
        pmFPAfile *weight = pmFPAfileSelectSingle(config->files, "PPMERGE.INPUT.WEIGHT", num); // Weight file
        pmFPAview *fileView = pmFPAviewForLevel(weight->fileLevel, view);
        if (!pmFPAfileOpen(weight, fileView, config)) {
            psError(PS_ERR_UNKNOWN, false, "Unable to open weight file %d", num);
            psFree(fileView);
            return false;
        }
        psFree(fileView);
    }
    return true;
}

psArray *ppMergeFileDataLevel(const pmConfig *config, const char *name, pmFPALevel level)
{
    assert(config);
    assert(name);

    int numFiles = psMetadataLookupS32(NULL, config->arguments, "INPUTS.NUM"); // Number of input files
    assert(numFiles > 0);
    psArray *files = psArrayAlloc(numFiles); // Files of interest
    for (int i = 0; i < numFiles; i++) {
        pmFPAfile *file = pmFPAfileSelectSingle(config->files, name, i); // Image file
        file->dataLevel = level;
        file->freeLevel = level;
        files->data[i] = psMemIncrRefCounter(file);
    }
    return files;
}

bool ppMergeFileActivate(const pmConfig *config, ppMergeFiles files, bool state)
{
    assert(config);

    bool mdok;                          // Status of MD lookup
    bool haveMasks = psMetadataLookupBool(&mdok, config->arguments, "INPUTS.MASKS"); // Do we have masks?
    bool haveWeights = psMetadataLookupBool(&mdok, config->arguments, "INPUTS.WEIGHTS"); // Got weights?

    const char **fileList = selectFiles(files); // Files to activate
    for (int i = 0; fileList[i] != NULL; i++) {
        if (!haveMasks && strcmp(fileList[i], "PPMERGE.INPUT.MASK") == 0) {
            continue;
        }
        if (!haveWeights && strcmp(fileList[i], "PPMERGE.INPUT.WEIGHT") == 0) {
            continue;
        }
        psString name = NULL;           // Name of file
        if (strcmp(fileList[i], "PPMERGE.OUTPUT") == 0) {
            name = ppMergeOutputFile(config);
        } else {
            name = psStringCopy(fileList[i]);
        }

        if (!pmFPAfileActivate(config->files, state, name)) {
            psError(PS_ERR_UNKNOWN, false, "Unable to activate file %s", name);
            psFree(name);
            return false;
        }
        psFree(name);
    }

    return true;
}



psArray *ppMergeFileActivateSingle(const pmConfig *config, ppMergeFiles files, bool state, int num)
{
    assert(config);

    bool mdok;                          // Status of MD lookup
    bool haveMasks = psMetadataLookupBool(&mdok, config->arguments, "INPUTS.MASKS"); // Do we have masks?
    bool haveWeights = psMetadataLookupBool(&mdok, config->arguments, "INPUTS.WEIGHTS"); // Got weights?

    psList *list = psListAlloc(NULL);   // List of files
    const char **fileList = selectFiles(files); // Files to activate
    for (int i = 0; fileList[i] != NULL; i++) {
        if (!haveMasks && strcmp(fileList[i], "PPMERGE.INPUT.MASK") == 0) {
            continue;
        }
        if (!haveWeights && strcmp(fileList[i], "PPMERGE.INPUT.WEIGHT") == 0) {
            continue;
        }

        psString name = NULL;           // Name of file
        if (strcmp(fileList[i], "PPMERGE.OUTPUT") == 0) {
            name = ppMergeOutputFile(config);
        } else {
            name = psStringCopy(fileList[i]);
        }

        pmFPAfile *file = pmFPAfileActivateSingle(config->files, state, name, num); // Activated file
        psFree(name);
        psListAdd(list, PS_LIST_TAIL, file);
    }

    psArray *array = psListToArray(list);
    psFree(list);

    return array;
}


psString ppMergeOutputFile(const pmConfig *config)
{
    ppMergeType type = psMetadataLookupS32(NULL, config->arguments, "TYPE"); // Type of frame
    const char *outSuffix = NULL;         // Suffix for output file
    switch (type) {
      case PPMERGE_TYPE_BIAS:
        outSuffix = "BIAS";
        break;
      case PPMERGE_TYPE_DARK:
        outSuffix = "DARK";
        break;
      case PPMERGE_TYPE_MASK:
        outSuffix = "MASK";
        break;
      case PPMERGE_TYPE_SHUTTER:
        outSuffix = "SHUTTER";
        break;
      case PPMERGE_TYPE_FLAT:
        outSuffix = "FLAT";
        break;
      case PPMERGE_TYPE_FRINGE:
        outSuffix = "FRINGE";
      default:
        psAbort("Unknown frame type: %x", type);
    }

    psString outName = NULL;            // Name of output file
    psStringAppend(&outName, "PPMERGE.OUTPUT.%s", outSuffix);

    return outName;
}
