#include <pslib.h>
#include <string.h>
#include "pstamp.h"
#include "pstampROI.h"

// pstamprequest  - create a fits table containing a postage stamp server request


typedef struct {
    psMetadata  *md;
    psString    fileName;
    psString    requestName;
} psrOptions;

char *str_columns[] = {
    "ROWNUM",
    "PROJECT",
    "JOB_TYPE",
    "REQ_TYPE", // byid,  byexp,         bycoord
    "ID",       // db id, exposure name, n/a
    "CLASS_ID",
    "CENTER_X",
    "CENTER_Y",
    "WIDTH",
    "HEIGHT",
    "FILTER",
    "DATE_MIN",
    "DATE_MAX",
    "STAMP_NAME",
    NULL
};
char *u32_columns[] = {
    "COORD_MASK",
    "OPTION_MASK", // bitmask or of PSTAMP_SELECT_IMAGE PSTAMP_SELECT_MASK PSTAMP_SELECT_WEIGHT
    NULL
};

static void usage()
{
    psErrorStackPrint(stderr, "Unable to parse command-line arguments.");
    exit(1);
}

static psMetadata *initializeTable()
{
    psMetadata *md = psMetadataAlloc();

    for (char **col_name = str_columns; *col_name != NULL; col_name++) {
        psMetadataAddStr(md, PS_LIST_TAIL, *col_name, PS_META_DEFAULT, "", "null");
    }
    for (char **col_name = u32_columns; *col_name != NULL; col_name++) {
        psMetadataAddU32(md, PS_LIST_TAIL, *col_name, PS_META_DEFAULT, "", 0);
    }
    return md;
}
static void getId(char *idString, int argnum, int *pArgc, char *argv[], psrOptions *options)
{
    if (*pArgc < 2) {
        fprintf(stderr, "must specify %s\n", idString);
        usage();
    }
    // catch common error
    if (*argv[argnum] == '-') {
        fprintf(stderr, "%s is not a valid %s\n", argv[argnum], idString);
        usage();
    }

    psMetadataAddStr (options->md, PS_LIST_TAIL, idString, PS_META_REPLACE, "", argv[argnum]);
    psArgumentRemove(argnum, pArgc, argv);
}


static pstampImageType getType(int argnum, int *pArgc, char *argv[], psrOptions *options, char *paramName)
{
    if (*pArgc < (argnum+2)) {
        fprintf(stderr, "must specify image type and %s\n", paramName);
        usage();
    }
    char *type = argv[argnum];

    psArgumentRemove(argnum, pArgc, argv);

    pstampImageType itype = PSTAMP_UNKNOWN;

    if (strcmp(type, "raw") == 0) {
        itype = PSTAMP_RAW;
    } else if (strcmp(type, "chip") == 0) {
        itype = PSTAMP_CHIP;
    } else if (strcmp(type, "warp") == 0) {
        itype = PSTAMP_WARP;
    } else if (strcmp(type, "diff") == 0) {
        itype = PSTAMP_DIFF;
    } else if (strcmp(type, "stack") == 0) {
        itype = PSTAMP_STACK;
    } else {
        fprintf(stderr, "unknown image type %s\n", type);
        usage();
    }
    psMetadataAddStr (options->md, PS_LIST_TAIL, "IMG_TYPE", PS_META_REPLACE, "", type);

    getId(paramName, argnum, pArgc, argv, options);

    return itype;
}

static void doById(int argnum, int *pArgc, char *argv[], psrOptions *options)
{
    switch (getType(argnum, pArgc, argv, options, "ID")) {
    case PSTAMP_RAW:
        getId("CLASS_ID", argnum, pArgc, argv, options);
        break;
    case PSTAMP_CHIP:
        getId("CLASS_ID", argnum, pArgc, argv, options);
        break;
    case PSTAMP_WARP:
    case PSTAMP_DIFF:
    case PSTAMP_STACK:
        break;
    default:
        fprintf(stderr, "programming error unexpected image type\n");
        exit(1);
    }
}

static void doByExp(int argnum, int *pArgc, char *argv[], psrOptions *options)
{
    switch (getType(argnum, pArgc, argv, options, "ID")) {
    case PSTAMP_RAW:
        getId("CLASS_ID", argnum, pArgc, argv, options);
        break;
    case PSTAMP_CHIP:
        getId("CLASS_ID", argnum, pArgc, argv, options);
        break;
    case PSTAMP_WARP:
    case PSTAMP_DIFF:
    case PSTAMP_STACK:
        break;
    default:
        fprintf(stderr, "programming error unexpected image type\n");
        exit(1);
    }
}

static psrOptions *parseArguments(int argc, char *argv[])
{
    psrOptions *options = psAlloc(sizeof(psrOptions));
    psMetadata *md = initializeTable();
    int         argnum;
    bool        gotStyle  = false;
    bool        needCoord = false;
    bool        gotCenter = false;
    bool        gotRange  = false;
    bool        needROI   = false;
    bool        makeStamps= false;
    unsigned    optionMask = PSTAMP_SELECT_IMAGE;

    options->md = md;

    psMetadataAddStr (md, PS_LIST_TAIL, "ROWNUM", PS_META_REPLACE, "", "1");

    // Job type. 
    // "stamp" for make postage stamps. 
    // "get_image" requests that whole images to be retrieved (magic masked for gpc1)
    // "list_uri" results in a list of matching images
    if ((argnum = psArgumentGet(argc, argv, "-get_image"))) {
        psMetadataAddStr (md, PS_LIST_TAIL, "JOB_TYPE", PS_META_REPLACE, "", "get_image");
        psArgumentRemove(argnum, &argc, argv);
    } else if ((argnum = psArgumentGet(argc, argv, "-list_uri"))) {
        psMetadataAddStr (md, PS_LIST_TAIL, "JOB_TYPE", PS_META_REPLACE, "", "list_uri");
        psArgumentRemove(argnum, &argc, argv);
    } else {
        // default JOB_TYPE is stamp
        psMetadataAddStr (md, PS_LIST_TAIL, "JOB_TYPE", PS_META_REPLACE, "", "stamp");
        needROI = true;
        makeStamps = true;
    }

    if ((argnum = psArgumentGet(argc, argv, "-req_name"))) {
        psArgumentRemove(argnum, &argc, argv);
        if (argc < 2) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "value required for request name");
            usage();
        }
        options->requestName = argv[argnum];
        psArgumentRemove(argnum, &argc, argv);
    } else {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "req_name is required\n");
        usage();
    }

    if ((argnum = psArgumentGet(argc, argv, "-project"))) {
        psArgumentRemove(argnum, &argc, argv);
        if (argc < 2) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "project is required");
            usage();
        }
        psMetadataAddStr(md, PS_LIST_TAIL, "PROJECT", PS_META_REPLACE, "", argv[argnum]);
        psArgumentRemove(argnum, &argc, argv);
    } else {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "project is required\n");
        usage();
    }

    // user tag will be used as the base name for the postage stamp images
    if ((argnum = psArgumentGet(argc, argv, "-stamp_name"))) {
        psArgumentRemove(argnum, &argc, argv);
        if (argc < 2) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "missing value for stamp_name");
            usage();
        }
        psMetadataAddStr(md, PS_LIST_TAIL, "STAMP_NAME", PS_META_REPLACE, "", argv[argnum]);
        psArgumentRemove(argnum, &argc, argv);
    } else {
        // if stamp name not specified use requestName
        psMetadataAddStr(md, PS_LIST_TAIL, "STAMP_NAME", PS_META_REPLACE, "", options->requestName);
    }

    if ((argnum = psArgumentGet(argc, argv, "-mask"))) {
        psArgumentRemove(argnum, &argc, argv);
        optionMask |= PSTAMP_SELECT_MASK;
    }
    if ((argnum = psArgumentGet(argc, argv, "-weight"))) {
        psArgumentRemove(argnum, &argc, argv);
        optionMask |= PSTAMP_SELECT_WEIGHT;
    }
    psMetadataAddU32(md, PS_LIST_TAIL, "OPTION_MASK", PS_META_REPLACE, "", optionMask);

    // find style & image type
    if ((argnum = psArgumentGet(argc, argv, "-bycoord"))) {
        fprintf(stderr, "-bycoord not implemented yet\n");
        exit(1);

        gotStyle = true;
        psMetadataAddStr(md, PS_LIST_TAIL, "REQ_TYPE", PS_META_REPLACE, "", 1+argv[argnum]);

        psArgumentRemove(argnum, &argc, argv); needCoord = true;
        needROI = true;
        // TODO: we need an IMG_TYPE too...
    }

    if ((argnum = psArgumentGet(argc, argv, "-byid"))) {
        if (gotStyle) {
            fprintf(stderr, "only one of -bycoord -byid -byexp may be specified\n");
            usage(1);
        }
        gotStyle = true;
        psMetadataAddStr(md, PS_LIST_TAIL, "REQ_TYPE", PS_META_REPLACE, "", 1+argv[argnum]);
        psArgumentRemove(argnum, &argc, argv);
        doById(argnum, &argc, argv, options);
    }

    if ((argnum = psArgumentGet(argc, argv, "-byexp"))) {
        if (gotStyle) {
            fprintf(stderr, "only one of -bycoord -byid -byexp may be specified\n");
            usage(1);
        }
        gotStyle = true;
        psMetadataAddStr(md, PS_LIST_TAIL, "REQ_TYPE", PS_META_REPLACE, "", 1+argv[argnum]);
        psArgumentRemove(argnum, &argc, argv);
        doByExp(argnum, &argc, argv, options);
    } 

    if (!gotStyle) {
        fprintf(stderr, "one of -bycoord -byid -byexp must be specified\n");
        usage();
    }

    pstampROI roiParam;

    // try and parse the ROI even if we don't "need it" to absorb arguments if they were provided
    if (!pstampGetROI(&roiParam, &argc, argv, &gotCenter, &gotRange)) {
        if (needROI) {
            usage();
        } else {
            psErrorClear();
        }
    }

    if (needROI) {
        unsigned coord_mask = 0;

        if (!roiParam.celestialCenter) {
            if (needCoord) {
                fprintf(stderr, "need to specify ROI in sky coordinates with -bycoord\n");
                usage();
            }
            coord_mask |= PSTAMP_CENTER_IN_PIXELS;
        }
        psMetadataAddStr (md, PS_LIST_TAIL, "CENTER_X", PS_META_REPLACE, "", roiParam.center[0]);
        psMetadataAddStr (md, PS_LIST_TAIL, "CENTER_Y", PS_META_REPLACE, "", roiParam.center[1]);

        if (!roiParam.celestialRange) {
            coord_mask |= PSTAMP_RANGE_IN_PIXELS;
        }
        psMetadataAddStr (md, PS_LIST_TAIL, "WIDTH", PS_META_REPLACE, "",  roiParam.range[0]);
        psMetadataAddStr (md, PS_LIST_TAIL, "HEIGHT", PS_META_REPLACE, "", roiParam.range[1]);

        psMetadataAddU32(md, PS_LIST_TAIL, "COORD_MASK", PS_META_REPLACE, "", coord_mask);
    }

    // only argument left should be the required file name for the request file

    if (argc == 2) {
        options->fileName = psStringCopy(argv[1]);
    } else if (argc == 1) {
        fprintf(stderr, "output file name is required\n");
        usage();
    } else {
        fprintf(stderr, "too many arguments supplied:");
        fprintf(stderr, " %s", argv[1]);
        for (int i=2; i<argc; i++) {
            fprintf(stderr, ", %s", argv[i]);
        }
        fprintf(stderr, "\n");
        usage();
    }

    return options;
}

static bool writeTable(psrOptions *options)
{
    psFits *fitsFile = psFitsOpen(options->fileName, "w");
    if (fitsFile == NULL) {
        psError(PS_ERR_IO, true, "failed to open %s for output\n", options->fileName);
        return false;
    }
    psMetadata *header = psMetadataAlloc();

    psMetadataAddStr(header, PS_LIST_TAIL, "EXTVER",   PS_META_REPLACE, "", STAMP_REQUEST_VERSION);
    psMetadataAddStr(header, PS_LIST_TAIL, "REQ_NAME", PS_META_REPLACE, "", options->requestName);

    psArray    *table = psArrayAlloc(1);
    table->data[0] = options->md;

    if (!psFitsWriteTable(fitsFile, header, table, STAMP_REQUEST_EXTNAME)) {
        psError(PS_ERR_IO, false, "failed to write fits table");
        return false;
    }

    if (! psFitsClose(fitsFile)) {
        psError(PS_ERR_IO, false, "failed to close fits table");
        return false;
    }

    return true;
}

int main(int argc, char *argv[])
{
    psrOptions *options = parseArguments(argc, argv);

    if (!options) {
        return 1;
    }

    // psMetadataPrint(stderr, options->md, 0);

    if (writeTable(options)) {
        return 0;
    } else {
        psErrorStackPrint(stderr, "failed to create request table");
        // XXX: we should have variable status codes that indicate what the problem was
        return 1;
    }
}
