#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 [-target FWHM] [-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"
            "\tWEIGHTING(F32): Relative weighting to be applied\n"
            "\tSCALE(F32):     Relative scaling 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 string value from the command-line and add it to the target
static bool valueArgStr(pmConfig *config,      // Configuration
                        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;
}

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);
    psMetadataAddF32(arguments, PS_LIST_TAIL, "-target", 0, "Target PSF FWHM", NAN);
    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);
    psMetadataAddF32(arguments, PS_LIST_TAIL, "-convolve-rej", 0, "Convolution rejection thresold (sigma)", NAN);
    psMetadataAddF32(arguments, PS_LIST_TAIL, "-extent", 0, "Extent of convolution (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);
    psMetadataAddBool(arguments, PS_LIST_TAIL, "-photometry", 0, "Do photometry on stacked image?", 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;
    }

    float target = psMetadataLookupF32(NULL, arguments, "-target"); // Target PSF width
    if (!isfinite(target)) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Target PSF width not specified with -target.");
        goto ERROR;
    }
    psMetadataAddF32(config->arguments, PS_LIST_TAIL, "TARGET", 0, "Target PSF width", target);


    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(config, 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_FLOAT("-convolve-rej",   "CONVOLVE.REJ",   F32);
    VALUE_ARG_RECIPE_FLOAT("-extent",         "EXTENT",         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);

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

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

ERROR:
    psFree(arguments);
    return false;
}


