/** @file ppSubCamera.c
 *
 *  @brief
 *
 *  @ingroup ppSub
 *
 *  @author IfA
 *  @version $Revision: 1.34 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2009-02-18 00:31:20 $
 *  Copyright 2009 Institute for Astronomy, University of Hawaii
 */

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

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

#include "ppSub.h"

// Define an input file
static pmFPAfile *defineInputFile(pmConfig *config,// Configuration
                                  pmFPAfile *bind,    // File to which to bind, or NULL
                                  char *filerule,     // Name of file rule
                                  char *argname,      // Argument name
                                  pmFPAfileType fileType // Type of file
    )
{
    bool status;

    // look for the file on the RUN metadata
    pmFPAfile *file = pmFPAfileDefineFromRun(&status, bind, config, filerule); // File to return
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "Failed to load file definition for %s", filerule);
        return NULL;
    }
    if (!file) {
        // look for the file on the argument list
        if (bind) {
            file = pmFPAfileBindFromArgs(&status, bind, config, filerule, argname);
        } else {
            file = pmFPAfileDefineFromArgs(&status, config, filerule, argname);
        }
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "Failed to load file definition for %s", filerule);
            return false;
        }
    }

    if (!file) {
        return NULL;
    }

    if (file->type != fileType) {
        psError(PS_ERR_UNKNOWN, true, "%s is not of type %s", filerule, pmFPAfileStringFromType(fileType));
        return NULL;
    }

    return file;
}

// Define an output file
static pmFPAfile *defineOutputFile(pmConfig *config, // Configuration
                                   pmFPAfile *template,    // File to use as basis for definition
                                   bool source, // Is template a source (T), or for binding (F)?
                                   char *filerule,     // Name of file rule
                                   pmFPAfileType fileType // Type of file
    )
{

    pmFPAfile *file = source ? pmFPAfileDefineFromFile(config, template, 1, 1, filerule) :
        pmFPAfileDefineOutput(config, template->fpa, filerule);
    if (!file) {
        psError(PS_ERR_IO, false, _("Unable to generate output file from %s"), filerule);
        return NULL;
    }
    if (file->type != fileType) {
        psError(PS_ERR_IO, true, "%s is not of type %s", filerule, pmFPAfileStringFromType(fileType));
        return NULL;
    }

    return file;
}


// Define an output file that will be used in a calculation
// This means it might already be available in the RUN metadata
static pmFPAfile *defineCalcFile(pmConfig *config, // Configuration
                                 pmFPAfile *bind,    // File to which to bind, or NULL
                                 char *filerule,     // Name of file rule
                                 pmFPAfileType fileType // Type of file
    )
{
    bool status;

    // look for the file on the RUN metadata
    pmFPAfile *file = pmFPAfileDefineFromRun(&status, bind, config, filerule); // File to return
    if (!status) {
        psError(PS_ERR_UNKNOWN, false, "Failed to load file definition for %s", filerule);
        return NULL;
    }
    if (file) {
        // It's an input
        file->save = false;
    }

    // define new version of file
    if (!file) {
        file = pmFPAfileDefineOutput(config, bind ? bind->fpa : NULL, filerule);
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "Failed to load file definition for %s", filerule);
            return false;
        }
        if (file) {
            // It's an output
            file->save = true;
        }
    }

    if (!file) {
        return NULL;
    }

    if (file->type != fileType) {
        psError(PS_ERR_UNKNOWN, true, "%s is not of type %s", filerule, pmFPAfileStringFromType(fileType));
        return NULL;
    }

    return file;
}


bool ppSubCamera(ppSubData *data)
{
    psAssert(data, "Require processing data");
    pmConfig *config = data->config;
    psAssert(config, "Require configuration");

    // Input image
    pmFPAfile *input = defineInputFile(config, NULL, "PPSUB.INPUT", "INPUT", PM_FPA_FILE_IMAGE);
    if (!input) {
        psError(PS_ERR_IO, false, "Failed to build FPA from PPSUB.INPUT");
        return false;
    }
    defineInputFile(config, input, "PPSUB.INPUT.MASK", "INPUT.MASK", PM_FPA_FILE_MASK);
    pmFPAfile *inVar = defineInputFile(config, input, "PPSUB.INPUT.VARIANCE", "INPUT.VARIANCE",
                                       PM_FPA_FILE_VARIANCE);
    defineInputFile(config, NULL, "PPSUB.INPUT.SOURCES", "INPUT.SOURCES", PM_FPA_FILE_CMF);

    // Reference image
    pmFPAfile *ref = defineInputFile(config, NULL, "PPSUB.REF", "REF", PM_FPA_FILE_IMAGE);
    if (!ref) {
        psError(PS_ERR_IO, false, "Failed to build FPA from PPSUB.REF");
        return false;
    }
    defineInputFile(config, ref, "PPSUB.REF.MASK", "REF.MASK", PM_FPA_FILE_MASK);
    pmFPAfile *refVar = defineInputFile(config, ref, "PPSUB.REF.VARIANCE", "REF.VARIANCE",
                                        PM_FPA_FILE_VARIANCE);
    defineInputFile(config, NULL, "PPSUB.REF.SOURCES", "REF.SOURCES", PM_FPA_FILE_CMF);


    // Convolved input image
    pmFPAfile *inConvImage = defineOutputFile(config, input, true, "PPSUB.INPUT.CONV", PM_FPA_FILE_IMAGE);
    pmFPAfile *inConvMask = defineOutputFile(config, inConvImage, false, "PPSUB.INPUT.CONV.MASK",
                                             PM_FPA_FILE_MASK);
    if (!inConvImage || !inConvMask) {
        psError(PS_ERR_UNKNOWN, false, "Unable to define output files");
        return false;
    }
    if (inVar) {
        pmFPAfile *inConvVar = defineOutputFile(config, inConvImage, false, "PPSUB.INPUT.CONV.VARIANCE",
                                                PM_FPA_FILE_VARIANCE);
        if (!inConvVar) {
            psError(PS_ERR_UNKNOWN, false, "Unable to define output files");
            return false;
        }
    }

    // Convolved ref image
    pmFPAfile *refConvImage = defineOutputFile(config, input, true, "PPSUB.REF.CONV", PM_FPA_FILE_IMAGE);
    pmFPAfile *refConvMask = defineOutputFile(config, refConvImage, false, "PPSUB.REF.CONV.MASK",
                                              PM_FPA_FILE_MASK);
    if (!refConvImage || !refConvMask) {
        psError(PS_ERR_UNKNOWN, false, "Unable to define output files");
        return false;
    }
    if (refVar) {
        pmFPAfile *refConvVar = defineOutputFile(config, refConvImage, false, "PPSUB.REF.CONV.VARIANCE",
                                                 PM_FPA_FILE_VARIANCE);
        if (!refConvVar) {
            psError(PS_ERR_UNKNOWN, false, "Unable to define output files");
            return false;
        }
    }


    // Now that the camera has been determined, we can read the recipe
    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, PPSUB_RECIPE); // Recipe for ppSim
    if (!recipe) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find recipe %s", PPSUB_RECIPE);
        return false;
    }
    if (psMetadataLookupBool(NULL, config->arguments, "-photometry")) {
        psMetadataAddBool(recipe, PS_LIST_TAIL, "PHOTOMETRY", PS_META_REPLACE,
                          "Perform photometry?", true);
    }
    if (psMetadataLookupBool(NULL, config->arguments, "-inverse")) {
        psMetadataAddBool(recipe, PS_LIST_TAIL, "INVERSE", PS_META_REPLACE,
                          "Generate inverse subtractions?", true);
    }

    data->inverse = psMetadataLookupBool(NULL, recipe, "INVERSE");
    data->photometry = psMetadataLookupBool(NULL, recipe, "PHOTOMETRY");


    // Output image
    pmFPAfile *output = defineOutputFile(config, inConvImage, true, "PPSUB.OUTPUT", PM_FPA_FILE_IMAGE);
    pmFPAfile *outMask = defineOutputFile(config, output, false, "PPSUB.OUTPUT.MASK", PM_FPA_FILE_MASK);
    if (!output || !outMask) {
        psError(PS_ERR_UNKNOWN, false, "Unable to define output files");
        return false;
    }
    output->save = true;
    outMask->save = true;
    if (inVar && refVar) {
        pmFPAfile *outVar = defineOutputFile(config, output, false, "PPSUB.OUTPUT.VARIANCE",
                                             PM_FPA_FILE_VARIANCE);
        if (!outVar) {
            psError(PS_ERR_UNKNOWN, false, "Unable to define output files");
            return false;
        }
        outVar->save = true;
    }

    pmFPAfile *inverse = NULL;          // Inverse output image
    if (data->inverse) {
        // Inverse output image
        inverse = defineOutputFile(config, output, true, "PPSUB.INVERSE", PM_FPA_FILE_IMAGE);
        pmFPAfile *invMask = defineOutputFile(config, inverse, false, "PPSUB.INVERSE.MASK",
                                              PM_FPA_FILE_MASK);
        if (!inverse || !invMask) {
            psError(PS_ERR_UNKNOWN, false, "Unable to define output files");
            return false;
        }
        inverse->save = true;
        invMask->save = true;
        if (inVar && refVar) {
            pmFPAfile *invVar = defineOutputFile(config, inverse, false, "PPSUB.INVERSE.VARIANCE",
                                                 PM_FPA_FILE_VARIANCE);
            if (!invVar) {
                psError(PS_ERR_UNKNOWN, false, "Unable to define output files");
                return false;
            }
            invVar->save = true;
        }
    }


    // Output JPEGs
    pmFPAfile *jpeg1 = pmFPAfileDefineOutput(config, NULL, "PPSUB.OUTPUT.JPEG1");
    if (!jpeg1) {
        psError(PS_ERR_IO, false, _("Unable to generate output file from PPSUB.OUTPUT.JPEG1"));
        return false;
    }
    if (jpeg1->type != PM_FPA_FILE_JPEG) {
        psError(PS_ERR_IO, true, "PPSUB.OUTPUT.JPEG1 is not of type JPEG");
        return false;
    }
    jpeg1->save = true;
    pmFPAfile *jpeg2 = pmFPAfileDefineOutput(config, NULL, "PPSUB.OUTPUT.JPEG2");
    if (!jpeg2) {
        psError(PS_ERR_IO, false, _("Unable to generate output file from PPSUB.OUTPUT.JPEG2"));
        return false;
    }
    if (jpeg2->type != PM_FPA_FILE_JPEG) {
        psError(PS_ERR_IO, true, "PPSUB.OUTPUT.JPEG2 is not of type JPEG");
        return false;
    }
    jpeg2->save = true;

    // Output subtraction kernel
    pmFPAfile *kernel = defineCalcFile(config, output, "PPSUB.OUTPUT.KERNELS", PM_FPA_FILE_SUBKERNEL);
    if (!kernel) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to define file PPSUB.OUTPUT.KERNELS");
        return false;
    }

    // psPhot input
    if (data->photometry) {
        psphotModelClassInit();        // load implementation-specific models

        pmFPAfile *psphot = pmFPAfileDefineFromFPA(config, output->fpa, 1, 1, "PSPHOT.INPUT");
        if (!psphot) {
            psError(PS_ERR_IO, false, "Failed to build FPA from PSPHOT.INPUT");
            return false;
        }
        if (psphot->type != PM_FPA_FILE_IMAGE) {
            psError(PS_ERR_IO, true, "PSPHOT.INPUT is not of type IMAGE");
            return false;
        }
        pmFPAfileActivate(config->files, false, "PSPHOT.INPUT");

        // Internal-ish file for getting the PSF from the minuend
        pmFPAfile *psf = pmFPAfileDefineOutputFromFile(config, psphot, "PSPHOT.PSF.LOAD");
        if (!psf) {
            psError(PS_ERR_IO, false, "Failed to build FPA from PSPHOT.PSF.LOAD");
            return false;
        }
        if (psf->type != PM_FPA_FILE_PSF) {
            psError(PS_ERR_IO, true, "PSPHOT.PSF.LOAD is not of type PSF");
            return false;
        }
        pmFPAfileActivate(config->files, false, "PSPHOT.PSF.LOAD");

        if (!psphotDefineFiles(config, psphot)) {
            psError(PS_ERR_UNKNOWN, false, "Unable to set up psphot files.");
            return false;
        }

        // Deactivate psphot output sources --- we want to define output source files of our own
        pmFPAfile *psphotOutput = pmFPAfileSelectSingle(config->files, "PSPHOT.OUTPUT", 0);
        psphotOutput->save = false;

        pmFPAfile *outSources = defineOutputFile(config, output, false, "PPSUB.OUTPUT.SOURCES",
                                                 PM_FPA_FILE_CMF);
        if (!outSources) {
            psError(PS_ERR_UNKNOWN, false, "Unable to set up output source file.");
            return false;
        }
        outSources->save = true;

        if (data->inverse) {
            pmFPAfile *invSources = defineOutputFile(config, inverse, false, "PPSUB.INVERSE.SOURCES",
                                                     PM_FPA_FILE_CMF);
            if (!invSources) {
                psError(PS_ERR_UNKNOWN, false, "Unable to set up inverse source file.");
                return false;
            }
            invSources->save = true;
        }
    }

    return true;
}


