#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;
} psrOptions;

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

static void getId(char *idString, int argnum, int *pArgc, char *argv[], psrOptions *options)
{
    if (*pArgc < 2) {
        fprintf(stderr, "must specify %s\n", idString);
        usage();
    }
    psMetadataAdd (options->md, PS_LIST_TAIL, idString, PS_DATA_STRING, "", 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();
    }
    psMetadataAdd (options->md, PS_LIST_TAIL, "IMG_TYPE", PS_DATA_STRING, "", 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, "EXPOSURE")) {
    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 = psMetadataAlloc();
    int         argnum;
    bool        gotStyle  = false;
    bool        needCoord = false;
    bool        gotCenter = false;
    bool        gotRange  = false;
    bool        needROI   = true;
    bool        makeStamps = true;

    options->md = md;

    psMetadataAdd (md, PS_LIST_TAIL, "VERSION", PS_DATA_STRING, "", STAMP_REQUEST_VERSION);

    // These mode arguments should be consistent with pstampparse
    if ((argnum = psArgumentGet(argc, argv, "-list"))) {
        psMetadataAdd (md, PS_LIST_TAIL, "CMD_MODE", PS_DATA_STRING, "", "list_uri");
        psArgumentRemove(argnum, &argc, argv);
        // we don't need coordinates if we're listing unless mode is -bycoord. May be set true below.
        needROI = false;    
        makeStamps = false;
    }

    if ((argnum = psArgumentGet(argc, argv, "-get_image"))) {
        psMetadataAdd (md, PS_LIST_TAIL, "JOB_TYPE", PS_DATA_STRING, "", "get_image");
        psArgumentRemove(argnum, &argc, argv);
        // we don't need coordinates if we're listing unless mode is -bycoord. May be set true below.
        needROI = false;    
        makeStamps = false;
    } else {
        psMetadataAdd (md, PS_LIST_TAIL, "JOB_TYPE", PS_DATA_STRING, "", "stamp");
    }


    // get project from command line
    if ((argnum = psArgumentGet(argc, argv, "-project"))) {
        psArgumentRemove(argnum, &argc, argv);
        if (argc < 2) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "no project name specified");
            usage();
        }
        psMetadataAdd(md, PS_LIST_TAIL, "PROJECT", PS_DATA_STRING, "", argv[argnum]);
        psArgumentRemove(argnum, &argc, argv);
    } else {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "project name is required\n");
        usage();
    }

    // get user tag (base name for the postage stamp files
    if ((argnum = psArgumentGet(argc, argv, "-user_tag"))) {
        psArgumentRemove(argnum, &argc, argv);
        if (argc < 2) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "missing value for user_tag");
            usage();
        }
        psMetadataAdd(md, PS_LIST_TAIL, "USER_TAG", PS_DATA_STRING, "", argv[argnum]);
        psArgumentRemove(argnum, &argc, argv);
    } else if (makeStamps) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true, "no user_tag specified");
        usage();
    }

    // find style & type
    if ((argnum = psArgumentGet(argc, argv, "-bycoord"))) {
        gotStyle = true;
        psMetadataAdd(md, PS_LIST_TAIL, "REQ_TYPE", PS_DATA_STRING, "", 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;
        psMetadataAdd(md, PS_LIST_TAIL, "REQ_TYPE", PS_DATA_STRING, "", 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;
        psMetadataAdd(md, PS_LIST_TAIL, "REQ_TYPE", PS_DATA_STRING, "", 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();
        }
    }

    if (needROI) {
        if (roiParam.celestialCenter) {
            psMetadataAdd (md, PS_LIST_TAIL, "RA", PS_DATA_STRING, "", roiParam.center[0]);
            psMetadataAdd (md, PS_LIST_TAIL, "DEC", PS_DATA_STRING, "", roiParam.center[1]);
        } else {
            if (needCoord) {
                fprintf(stderr, "need to specify ROI in sky coordinates\n");
                usage();
            }
            psMetadataAdd (md, PS_LIST_TAIL, "CENTER_X", PS_DATA_STRING, "", roiParam.center[0]);
            psMetadataAdd (md, PS_LIST_TAIL, "CENTER_Y", PS_DATA_STRING, "", roiParam.center[1]);
        }
        if (roiParam.celestialRange) {
            psMetadataAdd (md, PS_LIST_TAIL, "D_RA", PS_DATA_STRING, "",  roiParam.range[0]);
            psMetadataAdd (md, PS_LIST_TAIL, "D_DEC", PS_DATA_STRING, "", roiParam.range[1]);
        } else {
            psMetadataAdd (md, PS_LIST_TAIL, "WIDTH", PS_DATA_STRING, "",  roiParam.range[0]);
            psMetadataAdd (md, PS_LIST_TAIL, "HEIGHT", PS_DATA_STRING, "", roiParam.range[1]);
        }
    }

    // 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;
    }

    psArray    *table = psArrayAlloc(1);

    table->data[0] = options->md;

    if (!psFitsWriteTable(fitsFile, NULL, 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;
}

bool sampleTable(const char *fileName)
{
    psFits *fitsFile = psFitsOpen(fileName, "w");
    int     numRows = 42;

    if (fitsFile == NULL) {
        psError(PS_ERR_IO, true, "failed to open %s for output\n", fileName);
        return false;
    }

    psArray    *table = psArrayAlloc(numRows);

    for (int i=0; i< numRows; i++) {
        psMetadata *row = psMetadataAlloc();
        psMetadataAdd (row, PS_LIST_TAIL, "ROW",   PS_DATA_S32,    "", i);
        psMetadataAdd (row, PS_LIST_TAIL, "FROW",  PS_TYPE_F32,    "", 0.1*i);
        psMetadataAdd (row, PS_LIST_TAIL, "DUMMY", PS_DATA_STRING, "", "test line");

        table->data[i] = row;
         
    }

    psFitsWriteTable(fitsFile, NULL, table, "BILLSEXT");

    psFitsClose(fitsFile);

    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;
    }
}
