#include "StackBatch.h"
#include "StackBatchEnums.h"

/**
  Destructor
  */
static void destroy(StackBatch* this) {

    // call superclass destructor
    this->base.destroy(&this->base);
    free (this);
}

/**
  Does the work.
  */
static int run(StackBatch* this) {

    if (this->base.exitCode != PS_EXIT_SUCCESS) return this->base.exitCode; 

    int status = 0;
    fitsfile *fitsIn;

    if (fits_open_file(&fitsIn, this->base.inputFiles[0], READONLY, &status)) {

        fits_report_error(stderr, status);
        return PS_EXIT_SYS_ERROR;
    }


    // get primary header and pull stuff out for later
    int nKeys = 0;
    fits_get_hdrspace(fitsIn, &nKeys, NULL, &status);
    float exposureTime; status=0; fits_read_key(fitsIn, TFLOAT, "EXPTIME", &exposureTime, NULL, &status);
    char filterType[20]; status=0; fits_read_key(fitsIn, TSTRING, "FILTER", filterType, NULL, &status);


    char** assocDate = (char**)calloc(this->MAXDETECT, sizeof(char**));
    for (uint32_t i=0; i<this->MAXDETECT;i++) assocDate[i] = (char*)calloc(20,sizeof(char));



    // write StackMeta
    ippToPspsConfig_writeTable(this->base.config, fitsIn, this->base.fitsOut, 1, "StackMeta", true);
    fits_write_col(this->base.fitsOut, TLONG, STACKMETA_SKYCELLID, 1, 1, 1, &this->skycellId, &status);

    int8_t filterID = -1;
    if (!ippToPspsConfig_getFilterId(this->base.config, filterType, &filterID)) {

//        this->base.exitCode = PS_EXIT_DATA_ERROR;
    //    return this->base.exitCode; TODO
    }

    long* removeList = (long*)calloc(this->MAXDETECT, sizeof(long));
    float* instMag = (float*)calloc(this->MAXDETECT, sizeof(float));
    int8_t* filterIDs = (int8_t*)calloc(this->MAXDETECT, sizeof(int8_t));
    int8_t* surveyIDs = (int8_t*)calloc(this->MAXDETECT, sizeof(int8_t));
    float floatnull = -999.0;
    int anynull = 0;

    fits_write_col(this->base.fitsOut, TBYTE, STACKMETA_FILTERID, 1, 1, 1, &filterID, &status);

    fits_write_col(this->base.fitsOut, TBYTE, STACKMETA_SURVEYID, 1, 1, 1, &this->base.surveyID, &status);


    // psf detections
    char extensionName[15];
    sprintf(extensionName, "Chip.psf");
    if (fits_movnam_hdu(fitsIn, BINARY_TBL, extensionName, 0, &status)) {
        psError(PS_ERR_IO, false, "Can't move to extension: %s\n", extensionName);

    }
    else {

        // some stuff is the same for all detections so we can populate here
        for (long s = 0; s<this->MAXDETECT; s++) {

            filterIDs[s] = filterID;
            surveyIDs[s] = this->base.surveyID;
            strcpy(assocDate[s], this->base.todaysDate);
        }


        long nDet = 0;
        if (fits_get_num_rows(fitsIn, &nDet, &status)) {
            fits_report_error(stderr, status);
        }

        int instMagNum;
        status=0;fits_get_colnum(fitsIn, CASESEN, "PSF_INST_MAG", &instMagNum, &status);
        if (status) psError(PS_ERR_IO, false, "Unable to read col num for PSF_INST_MAG");
        fits_read_col(fitsIn, TFLOAT, instMagNum, 1, 1, nDet, &floatnull, instMag, &anynull, &status);


        printf("Looping through %ld psf detections\n", nDet);
        float mag;
        long unmatched = 0, totalDetections = 0, numOfDuplicates = 0, numInvalidFlux = 0, numDetectionsOut = 0;

        for (long s = 0; s<nDet; s++) {

            // TODO implement this match in DVO
            if (1) {

                mag = instMag[s];
                if (!isfinite(mag) || mag < -998.0) {

                    removeList[numOfDuplicates+numInvalidFlux] = s+1;
                    numInvalidFlux++;
                }

                totalDetections++;
            }
            else {

                unmatched++;
                continue;
            }
        }

        numDetectionsOut = totalDetections - numInvalidFlux;

        if (numDetectionsOut > 0) {

            ippToPspsConfig_writeTable(this->base.config, fitsIn, this->base.fitsOut, nDet, "StackDetection", false);
            fits_write_col(this->base.fitsOut, TLONG, STACKDETECTION_SKYCELLID, 1, 1, 1, &this->skycellId, &status);
            fits_write_col(this->base.fitsOut, TBYTE, STACKDETECTION_FILTERID, 1, 1, nDet, filterIDs, &status);
            fits_write_col(this->base.fitsOut, TBYTE, STACKDETECTION_SURVEYID, 1, 1, nDet, surveyIDs, &status);
            fits_write_col(this->base.fitsOut, TSTRING, STACKDETECTION_ASSOCDATE, 1, 1, nDet, assocDate, &status);

            if (numInvalidFlux) fits_delete_rowlist(this->base.fitsOut, removeList, numInvalidFlux, &status);

        }
        psLogMsg("ippToPsps", PS_LOG_INFO,
                "+---------------+---------+----------+------------------+---------------+--------------+\n"
                "|   Extension   | Rows in | Rows out | Missing from DVO | Duplicate IDs | Invalid Flux |\n"
                "|  %12s |  %5ld  |   %5ld  |      %5ld       |    %5ld      |    %5ld     |\n",
                extensionName, nDet, numDetectionsOut, unmatched, numOfDuplicates, numInvalidFlux);

    }



    // extended source
    sprintf(extensionName, "Chip.xsrc");
    if (fits_movnam_hdu(fitsIn, BINARY_TBL, extensionName, 0, &status)) {
        psError(PS_ERR_IO, false, "Can't move to extension: %s\n", extensionName);

    }
    else {

        int colNum;
        int type;
        long repeat;

        ippToPspsConfig_getFitsColumnMeta(
                "PROF_FLUX",
                &colNum,
                &type,
                &repeat,
                fitsIn);
        printf("PROF_FILL = %d %d %ld\n", colNum, type, repeat);
        float* vector = calloc(repeat, sizeof(float));

        ippToPspsConfig_getColumnVector(
                colNum,
                1,
                type,
                repeat,
                vector,
                fitsIn);



        free(vector);

        long nDet = 0;
        if (fits_get_num_rows(fitsIn, &nDet, &status)) {
            fits_report_error(stderr, status);
        }

        printf("Looping through %ld extended source detections\n", nDet);
        for (long s = 0; s<nDet; s++) {



        }

        ippToPspsConfig_writeTable(this->base.config, fitsIn, this->base.fitsOut, nDet, "StackApFlx", false);
        fits_write_col(this->base.fitsOut, TBYTE, STACKAPFLX_FILTERID, 1, 1, nDet, filterIDs, &status);
        fits_write_col(this->base.fitsOut, TBYTE, STACKAPFLX_SURVEYID, 1, 1, nDet, surveyIDs, &status);
    }


    // free stuff up
    free(removeList);
    free(instMag);
    free(filterIDs);
    free(surveyIDs);

    status=0;
    if (fits_close_file(fitsIn, &status)) fits_report_error(stderr, status);


    return this->base.exitCode;
}


/**
  Print-out this class. Calls base-calls print method first.
  */
static void print(StackBatch* this) {

    this->base.print(&this->base);

    printf("* skycell ID      : %d\n", this->skycellId);
    printf("\n");
}

/**
  Reads command-line arguments.  Calls base-calls print method first.
  */
static bool parseArguments(StackBatch* this, int argc, char **argv) {

    bool haveSkycellId = false;

    int32_t optind = 1;
    while( ( optind < argc ) ) {

        char * sw = argv[optind];

        if( argv[optind][0] == '-' ) { 

            if(strcmp(sw, "-skycellid") == 0 ) {
                optind++; if( optind > ( argc-1 ) ) break;
                this->skycellId = atoi(argv[optind]);
                haveSkycellId = true;
            }
        }
        optind++;
    }

    this->base.parseArguments(&this->base, argc, argv);

    if (
            !this->base.gotResultsPath() ||
            !this->base.gotFitsInPath() ||
            !this->base.gotFitsOutPath() ||
            !this->base.gotConfig() ||
            !this->base.gotSurveyType() ||
            !haveSkycellId) {

        printf("\n* ERROR with supplied arguments:");
        this->print(this);
        this->base.exitCode = PS_EXIT_CONFIG_ERROR;
        return false;
    }

    return true;
}

/**
  Constructor
  */
StackBatch* new_StackBatch(int *argc, char **argv) {

    StackBatch *this = (StackBatch*)calloc(1, sizeof(StackBatch));

    // call base-class constructor
    if (!new_Batch(&this->base)) {

        psError(PS_ERR_IO, false, "Unable to create Batch base-class object");
        return this;
    }

    this->MAXDETECT = 150000;

    this->print = print;
    this->destroy = destroy;
    this->base.run = run;

    if (!parseArguments(this, *argc, argv)) { return this; }

    strcat(this->base.configsDir, "/stack");
    sprintf(this->base.fitsOutFile, "%08d.FITS", this->skycellId);

    this->base.init(&this->base);

    return this;
}

/**
  Main
  */
int main(int argc, char **argv) {

    psTimerStart("stackbatch");

    ippToPsps_VersionPrint();

    int exitCode;

    StackBatch* stackBatch = new_StackBatch(&argc, argv);
    stackBatch->base.run(stackBatch);
    exitCode = stackBatch->base.exitCode;

    stackBatch->destroy(stackBatch);

    double secs = psTimerMark("stackbatch");

    psLogMsg("stackbatch", 3, "stackbatch completed %ssuccessfully, with exit code %d, in %.1f %s\n",
            (exitCode == PS_EXIT_SUCCESS) ? "" : "un",
            exitCode,
            (secs<60.0) ? secs : (secs/60.0),
            (secs<60.0) ? "sec(s)" : "min(s)");

    // tidy up
    psTimerStop();
    psLibFinalize();

    return exitCode;
}



