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

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

#include "ppStack.h"

// XXX add in the version info as in ppImage

// Print usage information and die
static void usage(const char *program,  // Name of the program
                  psMetadata *arguments, // Command-line arguments
                  pmConfig *config      // Configuration
    )
{
    fprintf(stderr, "\nPan-STARRS Image combination\n\n");
    fprintf(stderr,
            "Usage: %s INPUTS.mdc OUTPUT_ROOT [-sources STAMPS.cmf | -stamps STAMPS.dat]\n"
            "where INPUTS.mdc contains various METADATAs, each with:\n"
            "\tIMAGE(STR):     Image filename\n"
            "\tMASK(STR):      Mask filename\n"
            "\tWEIGHT(STR)     Weight map filename\n"
            "\tPSF(STR)        PSF filename\n"
            "\tWEIGHTING(F32): Relative weighting to be applied\n",
            program);
    fprintf(stderr, "\n");
    psArgumentHelp(arguments);
    psFree(arguments);
    psFree(config);
    pmConfigDone();
    psLibFinalize();
    exit(PS_EXIT_CONFIG_ERROR);
}

// Get a float-point value from the command-line or recipe, and add it to the arguments
#define VALUE_ARG_RECIPE_FLOAT(ARGNAME, RECIPENAME, TYPE) { \
    ps##TYPE value = psMetadataLookup##TYPE(NULL, arguments, ARGNAME); \
    if (isnan(value)) { \
        bool mdok; \
        value = psMetadataLookup##TYPE(&mdok, recipe, RECIPENAME); \
        if (!mdok) { \
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find %s in recipe %s", \
                RECIPENAME, PPSTACK_RECIPE); \
            goto ERROR; \
        } \
    } \
    psMetadataAdd##TYPE(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, value); \
}

// Get an integer value from the command-line or recipe, and add it to the arguments
#define VALUE_ARG_RECIPE_INT(ARGNAME, RECIPENAME, TYPE, UNSET) { \
    ps##TYPE value = psMetadataLookup##TYPE(NULL, arguments, ARGNAME); \
    if (value == UNSET) { \
        bool mdok; \
        value = psMetadataLookup##TYPE(&mdok, recipe, RECIPENAME); \
        if (!mdok) { \
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find %s in recipe %s", \
                RECIPENAME, PPSTACK_RECIPE); \
            goto ERROR; \
        } \
    } \
    psMetadataAdd##TYPE(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, value); \
}

// Get a mask value from the command-line or recipe, and add it to the arguments
#define VALUE_ARG_RECIPE_MASK(ARGNAME, RECIPENAME) { \
    bool mdok; \
    const char *name = psMetadataLookupStr(&mdok, arguments, ARGNAME); \
    if (!mdok || !name || strlen(name) == 0) { \
        name = psMetadataLookupStr(NULL, recipe, RECIPENAME); \
        if (!name) { \
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find %s in recipe %s", \
                RECIPENAME, PPSTACK_RECIPE); \
            goto ERROR; \
        } \
    } \
    psMaskType value = pmConfigMask(name, config); \
    psMetadataAddU8(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, value); \
}

// Get a statistic name from the command-line or recipe, and add the enum to the arguments
#define VALUE_ARG_RECIPE_STAT(ARGNAME, RECIPENAME) { \
    const char *stat = psMetadataLookupStr(NULL, arguments, ARGNAME); \
    if (!stat) { \
        stat = psMetadataLookupStr(NULL, recipe, RECIPENAME); \
        if (!stat) { \
            psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Unable to find %s in recipe %s", \
                    RECIPENAME, PPSTACK_RECIPE); \
            goto ERROR; \
        } \
    } \
    psMetadataAddS32(config->arguments, PS_LIST_TAIL, RECIPENAME, 0, NULL, psStatsOptionFromString(stat)); \
}

// Get a string value from the command-line and add it to the target
static bool valueArgStr(psMetadata *arguments, // Command-line arguments
                        const char *argName, // Argument name in the command-line arguments
                        const char *mdName, // Name for value in the metadata
                        psMetadata *target // Target metadata to which to add value
                        )
{
    psString value = psMetadataLookupStr(NULL, arguments, argName); // Value of interest
    if (value && strlen(value) > 0) {
        return psMetadataAddStr(target, PS_LIST_TAIL, mdName, 0, NULL, value);
    }
    return false;
}

// Get a string value from the command-line or recipe and add it to the target
static bool valueArgRecipeStr(psMetadata *arguments, // Command-line arguments
                              psMetadata *recipe, // Recipe
                              const char *argName, // Argument name in the command-line arguments
                              const char *mdName, // Name for value in the metadata and recipe
                              psMetadata *target // Target metadata to which to add value
                              )
{
    psString value = psMetadataLookupStr(NULL, arguments, argName); // Value of interest
    if (!value) {
        value = psMetadataLookupStr(NULL, recipe, mdName);
        if (!value) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Unable to find %s in recipe %s",
                    mdName, PPSTACK_RECIPE);
            return false;
        }
    }
    return psMetadataAddStr(target, PS_LIST_TAIL, mdName, 0, NULL, value);
}

bool ppStackArguments(int argc, char *argv[], pmConfig *config)
{
    assert(config);
    // bool mdok;                          // Status of MD lookup

    pmConfigFileSetsMD(config->arguments, &argc, argv, "PPSTACK.SOURCES", "-sources", NULL);

    psMetadata *arguments = psMetadataAlloc(); // Command-line arguments
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-stamps", 0, "Stamps file with x,y,flux per line", NULL);
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-stats", 0, "Statistics file", NULL);
    psMetadataAddS32(arguments, PS_LIST_TAIL, "-iter", 0, "Number of rejection iterations", 0);
    psMetadataAddF32(arguments, PS_LIST_TAIL, "-combine-rej", 0, "Combination rejection thresold (sigma)", NAN);
    psMetadataAddU8(arguments,  PS_LIST_TAIL, "-mask-bad", 0, "Mask value for bad pixels", 0);
    psMetadataAddU8(arguments,  PS_LIST_TAIL, "-mask-blank", 0, "Mask value for blank region", 0);
    psMetadataAddF32(arguments, PS_LIST_TAIL, "-threshold-mask", 0, "Threshold for mask deconvolution", NAN);
    psMetadataAddF32(arguments, PS_LIST_TAIL, "-image-rej", 0, "Pixel rejection fraction threshold for rejecting entire image", NAN);
    psMetadataAddS32(arguments, PS_LIST_TAIL, "-rows", 0, "Rows to read at once", 128);
    psMetadataAddBool(arguments, PS_LIST_TAIL, "-photometry", 0, "Do photometry on stacked image?", false);
    psMetadataAddS32(arguments, PS_LIST_TAIL, "-psf-instances", 0, "Number of instances for PSF generation", 5);
    psMetadataAddF32(arguments, PS_LIST_TAIL, "-psf-radius", 0, "Radius for PSF generation", 20.0);
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-psf-model", 0, "Model name for PSF generation", "PS_MODEL_RGAUSS");
    psMetadataAddS32(arguments, PS_LIST_TAIL, "-psf-order", 0, "Spatial order for PSF generation", 3);
    psMetadataAddBool(arguments, PS_LIST_TAIL, "-variance", 0, "Use variance for rejection?", false);
    psMetadataAddBool(arguments, PS_LIST_TAIL, "-safe", 0, "Play safe with small numbers of pixels to combine?", false);
    psMetadataAddBool(arguments, PS_LIST_TAIL, "-renorm", 0, "Renormalise variance maps?", false);
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-renorm-mean", 0, "Statistic for mean in renormalisation", NULL);
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-renorm-stdev", 0, "Statistic for stdev in renormalisation", NULL);
    psMetadataAddS32(arguments, PS_LIST_TAIL, "-renorm-width", 0, "Width of renormalisation boxes", 0);
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-temp-image", 0, "Suffix for temporary images", NULL);
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-temp-mask", 0, "Suffix for temporary masks", NULL);
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-temp-weight", 0, "Suffix for temporary weight maps", NULL);
    psMetadataAddBool(arguments, PS_LIST_TAIL, "-temp-delete", 0, "Delete temporary files on completion?", false);

    if (argc == 1 || !psArgumentParse(arguments, &argc, argv) || argc != 3) {
        usage(argv[0], arguments, config);
    }

    const char *stampsName = psMetadataLookupStr(NULL, arguments, "-stamps"); // Name of stamps file
    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "STAMPS", 0, "Stamps file", stampsName);
    // if (!stampsName && !psMetadataLookupPtr(&mdok, config->arguments, "PPSTACK.SOURCES")) {
    // psError(PS_ERR_BAD_PARAMETER_VALUE, true, "One of -sources or -stamps must be specified.");
    //goto ERROR;
    // }

    unsigned int numBad = 0;                     // Number of bad lines
    psMetadata *inputs = psMetadataConfigRead(NULL, &numBad, argv[1], false); // Information about inputs
    if (!inputs || numBad > 0) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Unable to cleanly read MDC file with inputs.");
        goto ERROR;
    }
    psMetadataAddMetadata(config->arguments, PS_LIST_TAIL, "INPUTS", 0,
                          "Metadata with input details", inputs);
    psFree(inputs);
    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0,
                     "Root name of the output image list", argv[2]);

    valueArgStr(arguments, "-stats", "STATS", config->arguments);

    psMetadata *recipe = psMetadataLookupMetadata(NULL, config->recipes, PPSTACK_RECIPE); // Recipe for ppSim
    if (!recipe) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find recipe %s", PPSTACK_RECIPE);
        goto ERROR;
    }

    VALUE_ARG_RECIPE_INT("-iter",             "ITER",           S32, 0);
    VALUE_ARG_RECIPE_FLOAT("-combine-rej",    "COMBINE.REJ",    F32);
    VALUE_ARG_RECIPE_MASK("-mask-bad",        "MASK.BAD");
    VALUE_ARG_RECIPE_MASK("-mask-blank",      "MASK.BLANK");
    VALUE_ARG_RECIPE_FLOAT("-threshold-mask", "THRESHOLD.MASK", F32);
    VALUE_ARG_RECIPE_FLOAT("-image-rej",      "IMAGE.REJ",      F32);
    VALUE_ARG_RECIPE_INT("-rows",             "ROWS",           S32, 0);

    VALUE_ARG_RECIPE_INT("-psf-instances", "PSF.INSTANCES", S32, 0);
    VALUE_ARG_RECIPE_FLOAT("-psf-radius",  "PSF.RADIUS",    F32);
    VALUE_ARG_RECIPE_INT("-psf-order",     "PSF.ORDER",     S32, 0);
    valueArgStr(arguments, "-psf-model", "PSF.MODEL", config->arguments);

    if (psMetadataLookupBool(NULL, arguments, "-photometry") ||
        psMetadataLookupBool(NULL, recipe, "PHOTOMETRY")) {
        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "PHOTOMETRY", 0,
                          "Do photometry on stacked image?", true);
    }

    if (psMetadataLookupBool(NULL, arguments, "-variance") ||
        psMetadataLookupBool(NULL, recipe, "VARIANCE")) {
        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "VARIANCE", 0,
                          "Use variance for rejection?", true);
    }

    if (psMetadataLookupBool(NULL, arguments, "-safe") ||
        psMetadataLookupBool(NULL, recipe, "SAFE")) {
        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "SAFE", 0,
                          "Play safe with small number of pixels to combine?", true);
    }

    if (psMetadataLookupBool(NULL, arguments, "-renorm") ||
        psMetadataLookupBool(NULL, recipe, "RENORM")) {
        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "RENORM", 0, "Renormalise variance maps?", true);
    }
    VALUE_ARG_RECIPE_INT("-renorm-width", "RENORM.WIDTH", S32, 0);
    VALUE_ARG_RECIPE_STAT("-renorm-mean", "RENORM.MEAN");
    VALUE_ARG_RECIPE_STAT("-renorm-stdev", "RENORM.STDEV");

    valueArgRecipeStr(arguments, recipe, "-temp-image",  "TEMP.IMAGE",  config->arguments);
    valueArgRecipeStr(arguments, recipe, "-temp-mask",   "TEMP.MASK",   config->arguments);
    valueArgRecipeStr(arguments, recipe, "-temp-weight", "TEMP.WEIGHT", config->arguments);

    if (psMetadataLookupBool(NULL, arguments, "-temp-delete") ||
        psMetadataLookupBool(NULL, recipe, "TEMP.DELETE")) {
        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "TEMP.DELETE", 0,
                          "Delete temporary files on completion?", true);
    }

    psTrace("ppStack", 1, "Done reading command-line arguments\n");
    psFree(arguments);
    return true;

ERROR:
    psFree(arguments);
    return false;
}
