# include "psphotStandAlone.h"

static pmFPAfile *defineFile(pmConfig *config, pmFPAfile *bind, const char *name, const char *filename, pmFPAfileType type);

// define the needed / desired I/O files
bool psphotStackParseCamera (pmConfig *config) {

    bool status = false;

    // the input images are defined as a set of metadatas in the INPUTS metadata folder
    psMetadata *inputs = psMetadataLookupMetadata(&status, config->arguments, "INPUTS"); // The inputs info
    if (!inputs) {
	psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find inputs.");
	return false;
    }

    int nRaw = 0;
    int nCnv = 0;
    int nInputs = inputs->list->n;
    for (int i = 0; i < nInputs; i++) {
	psMetadataItem *item = psMetadataGet(inputs, i);
	if (item->type != PS_DATA_METADATA) {
	    psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Component %s of the input metadata is not of type METADATA", item->name);
	    return false;
	}

	psMetadata *input = item->data.md; // The input metadata of interest

	pmFPAfile *rawInputFile = NULL;
	pmFPAfile *cnvInputFile = NULL;

	// RAW (unconvolved) input data (RAW:IMAGE, RAW:MASK, RAW:VARIANCE, RAW:PSF)
	psString rawImage = psMetadataLookupStr(&status, input, "RAW:IMAGE"); // Name of image
	if (rawImage && strlen(rawImage) > 0) {
	    rawInputFile = defineFile(config, NULL, "PSPHOT.STACK.INPUT.RAW", rawImage, PM_FPA_FILE_IMAGE); // File for image
	    if (!rawInputFile) {
		psError(PS_ERR_UNKNOWN, false, "Unable to define file from image %d (%s)", i, rawImage);
		return false;
	    }
	    psString mask = psMetadataLookupStr(&status, input, "RAW:MASK"); // Name of mask
	    if (mask && strlen(mask) > 0) {
		if (!defineFile(config, rawInputFile, "PSPHOT.STACK.MASK.RAW", mask, PM_FPA_FILE_MASK)) {
		    psError(PS_ERR_UNKNOWN, false, "Unable to define file from mask %d (%s)", i, mask);
		    return false;
		}
	    }
	    psString variance = psMetadataLookupStr(&status, input, "RAW:VARIANCE"); // Name of variance map
	    if (variance && strlen(variance) > 0) {
		if (!defineFile(config, rawInputFile, "PSPHOT.STACK.VARIANCE.RAW", variance, PM_FPA_FILE_VARIANCE)) {
		    psError(PS_ERR_UNKNOWN, false, "Unable to define file from variance %d (%s)", i, variance);
		    return false;
		}
	    }
	    psString psf = psMetadataLookupStr(&status, input, "RAW:PSF"); // Name of mask
	    if (psf && strlen(psf) > 0) {
		if (!defineFile(config, rawInputFile, "PSPHOT.STACK.PSF.RAW", psf, PM_FPA_FILE_PSF)) {
		    psError(PS_ERR_UNKNOWN, false, "Unable to define file from psf %d (%s)", i, psf);
		    return false;
		}
	    }
	    psString expnum = psMetadataLookupStr(&status, input, "RAW:EXPNUM"); // Name of expnum image
	    if (expnum && strlen(expnum) > 0) {
		if (!defineFile(config, rawInputFile, "PSPHOT.STACK.EXPNUM.RAW", expnum, PM_FPA_FILE_EXPNUM)) {
		    psError(PS_ERR_UNKNOWN, false, "Unable to define file from expnum %d (%s)", i, expnum);
		    return false;
		}
	    }
	    nRaw ++;
	}

	// CNV (convolved) input data (CNV:IMAGE, CNV:MASK, CNV:VARIANCE, CNV:PSF)
	psString cnvImage = psMetadataLookupStr(&status, input, "CNV:IMAGE"); // Name of image
	if (cnvImage && strlen(cnvImage) > 0) {
	    cnvInputFile = defineFile(config, NULL, "PSPHOT.STACK.INPUT.CNV", cnvImage, PM_FPA_FILE_IMAGE); // File for image
	    if (!cnvInputFile) {
		psError(PS_ERR_UNKNOWN, false, "Unable to define file from image %d (%s)", i, cnvImage);
		return false;
	    }
	    psString mask = psMetadataLookupStr(&status, input, "CNV:MASK"); // Name of mask
	    if (mask && strlen(mask) > 0) {
		if (!defineFile(config, cnvInputFile, "PSPHOT.STACK.MASK.CNV", mask, PM_FPA_FILE_MASK)) {
		    psError(PS_ERR_UNKNOWN, false, "Unable to define file from mask %d (%s)", i, mask);
		    return false;
		}
	    }
	    psString variance = psMetadataLookupStr(&status, input, "CNV:VARIANCE"); // Name of variance map
	    if (variance && strlen(variance) > 0) {
		if (!defineFile(config, cnvInputFile, "PSPHOT.STACK.VARIANCE.CNV", variance, PM_FPA_FILE_VARIANCE)) {
		    psError(PS_ERR_UNKNOWN, false, "Unable to define file from variance %d (%s)", i, variance);
		    return false;
		}
	    }
	    psString expnum = psMetadataLookupStr(&status, input, "CNV:EXPNUM"); // Name of EXPNUM image
	    if (expnum && strlen(expnum) > 0) {
		if (!defineFile(config, cnvInputFile, "PSPHOT.STACK.EXPNUM.CNV", expnum, PM_FPA_FILE_EXPNUM)) {
		    psError(PS_ERR_UNKNOWN, false, "Unable to define file from expnum %d (%s)", i, expnum);
		    return false;
		}
	    }
	    psString psf = psMetadataLookupStr(&status, input, "CNV:PSF"); // Name of mask
	    if (psf && strlen(psf) > 0) {
		if (!defineFile(config, cnvInputFile, "PSPHOT.STACK.PSF.CNV", psf, PM_FPA_FILE_PSF)) {
		    psError(PS_ERR_UNKNOWN, false, "Unable to define file from psf %d (%s)", i, psf);
		    return false;
		}
	    }
	    nCnv ++;
	}

	if (!rawInputFile && !cnvInputFile) {
	    psError(PSPHOT_ERR_CONFIG, true, "Component %s (%d) lacks both RAW:IMAGE and CNV:IMAGE of type STR", item->name, i);
	    return false;
	}

	// XXX what if they do not match in length
	if (nCnv && nRaw) {
	    if (nCnv != nRaw) {
		psError (PSPHOT_ERR_CONFIG, true, "if both RAW and CNV images are supplied, the number must match");
		return false;
	    }
	}

	psString sources = psMetadataLookupStr(&status, input, "SOURCES"); // Name of mask
	if (sources && strlen(sources) > 0) {
	    if (!defineFile(config, NULL, "PSPHOT.STACK.SOURCES", sources, PM_FPA_FILE_CMF)) {
		psError(PS_ERR_UNKNOWN, false, "Unable to define file from sources %d (%s)", i, sources);
		return false;
	    }
	}

        psS64 stack_id = psMetadataLookupS64(&status, input, "STACK_ID");
        if (!status) {
            psError(PS_ERR_UNKNOWN, false, "Unable to find STACK_ID from sources %d", i);
            return false;
        }
	// generate an pmFPAimage for the output convolved image
	// XXX output of these files should be optional
	{
	    // pmFPAfile *srcInputFile = rawInputFile ? rawInputFile : cnvInputFile;
	    pmFPAfile *outputImage = pmFPAfileDefineOutput(config, NULL, "PSPHOT.STACK.OUTPUT.IMAGE");
	    if (!outputImage) {
		psError(PSPHOT_ERR_CONFIG, false, "Trouble defining PSPHOT.STACK.OUTPUT.IMAGE");
		return false;
	    }
	    outputImage->save = true;
	    outputImage->fileID = stack_id;		// this is used to generate output names

	    pmFPAfile *outputMask = pmFPAfileDefineOutput(config, outputImage->fpa, "PSPHOT.STACK.OUTPUT.MASK");
	    if (!outputMask) {
		psError(PS_ERR_IO, false, _("Unable to generate output file from PSPHOT.STACK.OUTPUT.MASK"));
		return NULL;
	    }
	    if (outputMask->type != PM_FPA_FILE_MASK) {
		psError(PS_ERR_IO, true, "PSPHOT.STACK.OUTPUT.MASK is not of type MASK");
		return NULL;
	    }
	    outputMask->save = true;
	    outputMask->fileID = stack_id;		// this is used to generate output names

	    pmFPAfile *outputVariance = pmFPAfileDefineOutput(config, outputImage->fpa, "PSPHOT.STACK.OUTPUT.VARIANCE");
	    if (!outputVariance) {
		psError(PS_ERR_IO, false, _("Unable to generate output file from PSPHOT.STACK.OUTPUT.VARIANCE"));
		return NULL;
	    }
	    if (outputVariance->type != PM_FPA_FILE_VARIANCE) {
		psError(PS_ERR_IO, true, "PSPHOT.STACK.OUTPUT.VARIANCE is not of type VARIANCE");
		return NULL;
	    }
	    outputVariance->save = true;
	    outputVariance->fileID = stack_id;		// this is used to generate output names

	    // the output sources are carried on the outputImage->fpa structures
	    pmFPAfile *outsources = pmFPAfileDefineOutputFromFile (config, outputImage, "PSPHOT.STACK.OUTPUT");
	    if (!outsources) {
		psError(PSPHOT_ERR_CONFIG, false, "Cannot find a rule for PSPHOT.STACK.OUTPUT");
		return false;
	    }
	    outsources->save = true;
	    outsources->fileID = stack_id;		// this is used to generate output names
	}
    }
    psMetadataRemoveKey(config->arguments, "FILENAMES");
    psMetadataAddS32 (config->arguments, PS_LIST_TAIL, "PSPHOT.STACK.INPUT.RAW.NUM", PS_META_REPLACE, "number of inputs", nRaw);
    psMetadataAddS32 (config->arguments, PS_LIST_TAIL, "PSPHOT.STACK.INPUT.CNV.NUM", PS_META_REPLACE, "number of inputs", nCnv);
    psMetadataAddS32 (config->arguments, PS_LIST_TAIL, "PSPHOT.STACK.OUTPUT.IMAGE.NUM", PS_META_REPLACE, "number of inputs", nInputs);
    psMetadataAddS32 (config->arguments, PS_LIST_TAIL, "PSPHOT.INPUT.NUM", PS_META_REPLACE, "number of inputs", nInputs);

    if (!psphotSetMaskBits (config)) {
        psError (PS_ERR_UNKNOWN, false, "failed to set mask bit values");
        return NULL;
    }

    // select the appropriate recipe information
    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSPHOT_RECIPE);
    bool saveCnv = psMetadataLookupBool(&status, recipe, "SAVE.CNV");
    bool saveChisq = psMetadataLookupBool(&status, recipe, "SAVE.CHISQ");

    // loop over the available readouts
    for (int i = 0; i < nInputs; i++) {
	pmFPAfile *file = NULL;

	file = pmFPAfileSelectSingle(config->files, "PSPHOT.STACK.OUTPUT.IMAGE", i);
	file->save = saveCnv;

	file = pmFPAfileSelectSingle(config->files, "PSPHOT.STACK.OUTPUT.MASK", i);
	file->save = saveCnv;

	file = pmFPAfileSelectSingle(config->files, "PSPHOT.STACK.OUTPUT.VARIANCE", i);
	file->save = saveCnv;
    }

    // generate an pmFPAimage for the chisqImage
    // XXX output of these files should be optional
    {
	pmFPAfile *chisqImage = pmFPAfileDefineOutput(config, NULL, "PSPHOT.CHISQ.IMAGE");
	if (!chisqImage) {
	    psError(PSPHOT_ERR_CONFIG, false, "Trouble defining PSPHOT.CHISQ.IMAGE");
	    return false;
	}
	chisqImage->save = saveChisq;

	pmFPAfile *chisqMask = pmFPAfileDefineOutput(config, chisqImage->fpa, "PSPHOT.CHISQ.MASK");
	if (!chisqMask) {
	    psError(PS_ERR_IO, false, _("Unable to generate output file from PSPHOT.CHISQ.MASK"));
	    return NULL;
	}
	if (chisqMask->type != PM_FPA_FILE_MASK) {
	    psError(PS_ERR_IO, true, "PSPHOT.CHISQ.MASK is not of type MASK");
	    return NULL;
	}
	chisqMask->save = saveChisq;

	pmFPAfile *chisqVariance = pmFPAfileDefineOutput(config, chisqImage->fpa, "PSPHOT.CHISQ.VARIANCE");
	if (!chisqVariance) {
	    psError(PS_ERR_IO, false, _("Unable to generate output file from PSPHOT.CHISQ.VARIANCE"));
	    return NULL;
	}
	if (chisqVariance->type != PM_FPA_FILE_VARIANCE) {
	    psError(PS_ERR_IO, true, "PSPHOT.CHISQ.VARIANCE is not of type VARIANCE");
	    return NULL;
	}
	chisqVariance->save = saveChisq;
    }

    psTrace("psphot", 1, "Done with psphotStackParseCamera...\n");

    psErrorClear();                     // some metadata lookup may have failed
    return true;
}

// Define a file
static pmFPAfile *defineFile(pmConfig *config, // Configuration
                             pmFPAfile *bind, // File to which to bind
                             const char *name, // Name of file rule
                             const char *filename, // Name of file
                             pmFPAfileType type // Type of file
                             )
{

    psArray *dummy = psArrayAlloc(1);   // Dummy array of filenames for this FPA
    dummy->data[0] = psStringCopy(filename);
    psMetadataAddArray(config->arguments, PS_LIST_TAIL, "FILENAMES", PS_META_REPLACE,
                       "Filenames for file rule definition", dummy);
    psFree(dummy);

    bool found = false;             // Found the file?
    pmFPAfile *file = bind ? pmFPAfileBindFromArgs(&found, bind, config, name, "FILENAMES") :
        pmFPAfileDefineFromArgs(&found, config, name, "FILENAMES");
    if (!file || !found) {
        psError(PS_ERR_UNKNOWN, false, "Unable to define file %s from %s", name, filename);
        return NULL;
    }
    if (file->type != type) {
        psError(PS_ERR_IO, true, "%s is not of type %s", name, pmFPAfileStringFromType(type));
        return NULL;
    }

    return file;
}


/***
 *
 *  psphotStack :

 *    * inputs:
 *      * unconvolved images
 *      * raw convolved images
 *      * psfs (unconvolved or convolved?)
 *      * sources
 
 * optionally convolve the unconvolved or the raw inputs
 * optionally perform no convolutions
 * optionally save the psf-matched images

 */

    
# if (0)    
    // define the additional input/output files associated with psphot
    // XXX figure out which files are needed by psphotStack
    if (false && !psphotDefineFiles (config, input)) {
        psError(PSPHOT_ERR_CONFIG, false, "Trouble defining the additional input/output files");
        return false;
    }
# endif

