#include "DetectionBatch.h"
#include "DetectionBatchEnums.h"

/**
  Source for all methods in the DetectionBatch class
  */

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

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

/**
  Does the work. Loops through the provided smf files, tallies the contents with the DVO database already connected to and generates a new FITS file of the fomat required by PSPS (i.e. a binary table per PSPS database table)
  */
static int run(DetectionBatch* 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;
    fits_get_hdrspace(fitsIn, &nKeys, NULL, &status);

    float zptObs, exposureTime;
    char filterType[20];
    double obsTime;
    double expStart;
    double expTime;
    status=0; fits_read_key(fitsIn, TFLOAT, "ZPT_OBS", &zptObs, NULL, &status);
    status=0; fits_read_key(fitsIn, TFLOAT, "EXPREQ", &exposureTime, NULL, &status);
    status=0; fits_read_key(fitsIn, TSTRING, "FILTERID", filterType, NULL, &status);
    status=0; fits_read_key(fitsIn, TDOUBLE, "MJD-OBS", &expStart, NULL, &status);
    status=0; fits_read_key(fitsIn, TDOUBLE, "EXPTIME", &expTime, NULL, &status);
    obsTime = expStart + (expTime/172800.0); // exp start plus half exp time (converted from secs to days)

    ippToPspsConfig_writeTable(this->base.config, fitsIn, this->base.fitsOut, 1, "FrameMeta", true);

    // FrameMeta values
    fits_write_col(this->base.fitsOut, TLONG, FRAMEMETA_FRAMEID, 1, 1, 1, &this->expId, &status);
    fits_write_col(this->base.fitsOut, TSTRING, FRAMEMETA_FRAMENAME, 1, 1, 1, &(this->expName), &status);
    fits_write_col(this->base.fitsOut, TBYTE, FRAMEMETA_SURVEYID, 1, 1, 1, &this->base.surveyID, &status);

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

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

    int16_t cameraID = 1; // TODO
    fits_write_col(this->base.fitsOut, TSHORT, FRAMEMETA_CAMERAID, 1, 1, 1, &cameraID, &status);

    int16_t cameraConfigID = 1; // TODO
    fits_write_col(this->base.fitsOut, TSHORT, FRAMEMETA_CAMERACONFIGID, 1, 1, 1, &cameraConfigID, &status);

    int16_t telescopeID = 1; // TODO
    fits_write_col(this->base.fitsOut, TSHORT, FRAMEMETA_TELESCOPEID, 1, 1, 1, &telescopeID, &status);

    // stuff to keep from psf.hdr header
    int sourceId = -1, imageId = -1;
    float fwhmMaj, fwhmMin, momentMaj, momentMin, psfFwhm, momentFwhm;
    long pspsImageId = -1;

    // DVO variables
    SkyList *skylist = NULL;
    dvoDetection *dvoDetections = NULL;
    int maxDvoDetId = -1, numDvoDetections = -1;
    Image *image = NULL;

    // stuff for detections table
    uint32_t s,d, invalidDvoRows, nChipDetectionsOut = 0;

    long longnull = -999;
    float floatnull = -999.0;
    int anynull = 0;

    char ccdNumber[3], extensionName[15];
    // for storing FITS column data
    long* ippIDet = (long*)calloc(this->MAXDETECT, sizeof(long));
    float* instMag = (float*)calloc(this->MAXDETECT, sizeof(float));
    float* instMagErr = (float*)calloc(this->MAXDETECT, sizeof(float));
    float* peakMag = (float*)calloc(this->MAXDETECT, sizeof(float));
    uint64_t* flags = (uint64_t*)calloc(this->MAXDETECT, sizeof(uint64_t));
    long* objID = (long*)calloc(this->MAXDETECT, sizeof(long));
    long* detectID = (long*)calloc(this->MAXDETECT, sizeof(long));
    long* ippObjID = (long*)calloc(this->MAXDETECT, sizeof(long));
    long* ippDetectID = (long*)calloc(this->MAXDETECT, sizeof(long));
    long* imageID = (long*)calloc(this->MAXDETECT, sizeof(long));
    double* obsTimes = (double*)calloc(this->MAXDETECT, sizeof(double));
    float* instFlux = (float*)calloc(this->MAXDETECT, sizeof(float));
    float* instFluxErr = (float*)calloc(this->MAXDETECT, sizeof(float));
    float* peakFlux = (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));

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

    // 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 maxObjID = LONG_MIN, minObjID = LONG_MAX;
    uint64_t imageFlags;
    short nOta = 0;
    long i;
    uint32_t numOfDuplicates, numInvalidFlux, numOfInvalidIppIDet;
    long totalDetectionsOut = 0, numPhotoRef, totalNumPhotoRef = 0;
    long* removeList = (long*)calloc(this->MAXDETECT, sizeof(long));
    long thisObjId;
    bool firstTimeIn = true, peakFluxOk, instFluxOk;
    int ippIDetNum, instMagNum, instMagErrNum, peakMagNum;
    bool error = false;

    // loop round all 60 chips
    for (int x=0; x<8; x++) {
        for (int y=0; y<8; y++) {

            // dodge the corners
            if (x==0 && y==0) continue;
            if (x==0 && y==7) continue;
            if (x==7 && y==0) continue;
            if (x==7 && y==7) continue;

            sprintf(ccdNumber, "%d%d", x, y);

            // check we can move to detections table in smf
            sprintf(extensionName, "XY%s.psf", ccdNumber);
            status=0;
            if (fits_movnam_hdu(fitsIn, BINARY_TBL, extensionName, 0, &status)) {
                psError(PS_ERR_IO, false, "Can't move to extension: %s skipping this chip\n", extensionName);
                continue;
            }

            // move to header extension
            sprintf(extensionName, "XY%s.hdr", ccdNumber);
            status=0;
            if (fits_movnam_hdu(fitsIn, IMAGE_HDU, extensionName, 0, &status)) {
                psError(PS_ERR_IO, false, "Can't move to extension: %s skipping this chip\n", extensionName);
                continue;
            }

            // stuff to save from psf.hdr
            status=0; fits_read_key(fitsIn, TFLOAT, "FWHM_MAJ", &fwhmMaj, NULL, &status);
            status=0; fits_read_key(fitsIn, TFLOAT, "FWHM_MIN", &fwhmMin, NULL, &status);
            status=0; fits_read_key(fitsIn, TFLOAT, "IQ_FW1", &momentMaj, NULL, &status);
            status=0; fits_read_key(fitsIn, TFLOAT, "IQ_FW2", &momentMin, NULL, &status);
            status=0; fits_read_key(fitsIn, TLONG, "IMAGEID", &imageId, NULL, &status);
            status=0; fits_read_key(fitsIn, TLONG, "SOURCEID", &sourceId, NULL, &status);
            status=0; fits_read_key(fitsIn, TLONG, "NASTRO", &numPhotoRef, NULL, &status);
            totalNumPhotoRef += numPhotoRef; // total up for storing in FrameMeta
            // access DVO database
            skylist = dvoSkyListByExternID(this->base.dvoConfig, sourceId, imageId, &image);
            if (skylist == NULL) {
                psError(PS_ERR_IO, false,
                        "DVO: can't find SkyList for sourceId='%d' imageId='%d' (CCD = XY%s): skipping this chip\n",
                        sourceId, imageId, ccdNumber);
                continue;
            }

            // create unique int from 'frameID' (aka exposure ID) and ccd number
            pspsImageId = (this->expId*100) + image->ccdnum;

            // now get DVO detections
            dvoDetections = NULL;
            numDvoDetections = dvoGetDetections(skylist, image->imageID, &dvoDetections, &maxDvoDetId);

            if (numDvoDetections > this->MAXDETECT ) {

                psError(PS_ERR_IO, false,
                        " Number of detections (%d) exceeds max limit (%ld)\n",
                        numDvoDetections, this->MAXDETECT);
                error = true;
                break;
            }

            // create ImageMeta
            ippToPspsConfig_writeTable(this->base.config, fitsIn, this->base.fitsOut, 1, "ImageMeta", true);
            psfFwhm = (fwhmMaj+fwhmMin)/2;
            momentFwhm = (momentMaj+momentMin)/2;
            imageFlags = (uint64_t)image->flags;
            fits_write_col(this->base.fitsOut, TLONGLONG, IMAGEMETA_IMAGEID, 1, 1, 1, &pspsImageId, &status);
            fits_write_col(this->base.fitsOut, TLONG, IMAGEMETA_FRAMEID, 1, 1, 1, &this->expId, &status);
            fits_write_col(this->base.fitsOut, TSHORT, IMAGEMETA_CCDID, 1, 1, 1, &image->ccdnum, &status);
            fits_write_col(this->base.fitsOut, TLONG, IMAGEMETA_PHOTOCALID, 1, 1, 1, &image->photcode, &status);
            fits_write_col(this->base.fitsOut, TBYTE, IMAGEMETA_FILTERID, 1, 1, 1, &filterID, &status);
            fits_write_col(this->base.fitsOut, TFLOAT, IMAGEMETA_PHOTOSCAT, 1, 1, 1, &zptObs, &status);
            fits_write_col(this->base.fitsOut, TFLOAT, IMAGEMETA_PSFFWHM, 1, 1, 1, &psfFwhm, &status);
            fits_write_col(this->base.fitsOut, TFLOAT, IMAGEMETA_MOMENTFWHM, 1, 1, 1, &momentFwhm, &status);
            fits_write_col(this->base.fitsOut, TFLOAT, IMAGEMETA_PHOTOZERO, 1, 1, 1, &zptObs, &status);
            fits_write_col(this->base.fitsOut, TLONGLONG, IMAGEMETA_QAFLAGS, 1, 1, 1, &imageFlags, &status);

            // now move BACK to detections table in smf
            sprintf(extensionName, "XY%s.psf", ccdNumber);
            status=0;
            if (fits_movnam_hdu(fitsIn, BINARY_TBL, extensionName, 0, &status)) {
                psError(PS_ERR_IO, false, "Can't move to extension: %s skipping\n", extensionName);
                continue;
            }

            // keep a running count of 'images' we find in order to write total to FrameMeta at the end
            nOta++;

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

            // loop round detections to populate some extra stuff
            s = d = nChipDetectionsOut = invalidDvoRows = 0;

            // determine column numbers for certain IPP detection columns
            if (firstTimeIn) {

                status=0;fits_get_colnum(fitsIn, CASESEN, "IPP_IDET", &ippIDetNum, &status);
                if (status) psError(PS_ERR_IO, false, "Unable to read col num for IPP_IDET");
                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");
                status=0;fits_get_colnum(fitsIn, CASESEN, "PSF_INST_MAG_SIG", &instMagErrNum, &status);
                if (status) psError(PS_ERR_IO, false, "Unable to read col num for PSF_INST_MAG_SIG");
                status=0;fits_get_colnum(fitsIn, CASESEN, "PEAK_FLUX_AS_MAG", &peakMagNum, &status);
                if (status) psError(PS_ERR_IO, false, "Unable to read col num for PEAK_FLUX_AS_MAG");

                firstTimeIn=false;
            }

            anynull = 0;
            fits_read_col(fitsIn, TLONG, ippIDetNum, 1, 1, nChipDetectionsIn, &longnull, ippIDet, &anynull, &status);
            fits_read_col(fitsIn, TFLOAT, instMagNum, 1, 1, nChipDetectionsIn, &floatnull, instMag, &anynull, &status);
            fits_read_col(fitsIn, TFLOAT, instMagErrNum, 1, 1, nChipDetectionsIn, &floatnull, instMagErr, &anynull, &status);
            fits_read_col(fitsIn, TFLOAT, peakMagNum, 1, 1, nChipDetectionsIn, &floatnull, peakMag, &anynull, &status);

            // DVO detections are ordered by IPP_IDET, which increments from 0 in SMF table
            // both DVO detections and smf detections should increment. however, there may be gaps in smf and 'invalid' rows in dvo
            numOfDuplicates = 0;
            numInvalidFlux = 0;
            numOfInvalidIppIDet = 0;
            
            // loop through all detections in smf file extension for this chip.
            // there are three ways for a detection to be ommitted from the final FITS output:
            // 1. it has a duplicate obj ID to a previous detection
            // 2. it has an invalid detection ID (< 0 or > max DVO det ID)
            // 3. it has an invalid flux value
            for (long s = 0; s<nChipDetectionsIn; s++) {

                // catch crazy large or negative IPP_IDET, and skip them
                if (ippIDet[s] < 0 || ippIDet[s] > maxDvoDetId) {

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

                // loop forward through DVO detections until we find corresponding detection ID
                while (ippIDet[s] > dvoDetections[d].meas.detID && dvoDetections[d].meas.detID <= maxDvoDetId) {

                    d++;
                    if (!dvoDetections[d].valid) invalidDvoRows++;
                }

                // if we have found matching detection IDs then continue to populate other columns
                if (ippIDet[s] == dvoDetections[d].meas.detID ) {

                    // check if we have already seen this obj ID, and if we have, skip this detection
                    thisObjId = dvoDetections[d].ave.extID;
                    bool isDuplicate = false;
                    for (i=0; i<s; i++) {
                        if (thisObjId == objID[i]) {
                            removeList[numOfDuplicates+numInvalidFlux+numOfInvalidIppIDet] = s+1;
                            numOfDuplicates++;
                            isDuplicate=true;
                            break;
                        }
                    }
                    if (isDuplicate) continue; 

                    // populate some arrays to be shoved in table in bulk later
                    objID[s] = thisObjId;
                    detectID[s] = dvoDetections[d].meas.extID;
                    ippObjID[s] = (uint64_t)dvoDetections[d].ave.catID*1000000000 + (uint64_t)dvoDetections[d].ave.objID;
                    ippDetectID[s] = dvoDetections[d].meas.detID;

                    // the 'photFlags' are the same as those in the 'FLAGS' column of the smf
                    flags[s] = ((uint64_t)dvoDetections[d].meas.dbFlags << 32) | (uint64_t)dvoDetections[d].meas.photFlags;
                    imageID[s] = pspsImageId;
                    obsTimes[s] = obsTime;

                    // calculate flux and flux errors from magnitudes
                    peakFluxOk = getFlux(exposureTime, peakMag[s], &peakFlux[s], 0.0, NULL);
                    instFluxOk = getFlux(exposureTime, instMag[s], &instFlux[s], instMagErr[s], &instFluxErr[s]);

                    // check for invalid flux values, and skip them
                    if ( !peakFluxOk || !instFluxOk) {
                        removeList[numOfDuplicates+numInvalidFlux+numOfInvalidIppIDet] = s+1;
                        numInvalidFlux++;
                        continue;
                    }

                    // store max/min objID
                    if (objID[s] > maxObjID) maxObjID = objID[s];
                    if (objID[s] < minObjID) minObjID = objID[s];

                    nChipDetectionsOut++;
                }
            } // end of loop round detections

            // write number of rows (detections) to ImageMeta
            fits_write_col(this->base.fitsOut, TLONG, IMAGEMETA_NDETECT, 1, 1, 1, &nChipDetectionsOut, &status);

            int totalRemove = numOfDuplicates+numInvalidFlux+numOfInvalidIppIDet;

            // avoid writing out an empty table of detections
            if (nChipDetectionsOut > 0) {

                // detections
                ippToPspsConfig_writeTable(this->base.config, fitsIn, this->base.fitsOut, nChipDetectionsIn, "Detection", false);
                fits_write_col(this->base.fitsOut, TLONGLONG, DETECTION_OBJID, 1, 1, nChipDetectionsIn, objID, &status);
                fits_write_col(this->base.fitsOut, TLONGLONG, DETECTION_DETECTID, 1, 1, nChipDetectionsIn, detectID, &status);
                fits_write_col(this->base.fitsOut, TLONGLONG, DETECTION_IPPOBJID, 1, 1, nChipDetectionsIn, ippObjID, &status);
                fits_write_col(this->base.fitsOut, TLONGLONG, DETECTION_IPPDETECTID, 1, 1, nChipDetectionsIn, ippDetectID, &status);
                fits_write_col(this->base.fitsOut, TBYTE, DETECTION_FILTERID, 1, 1, nChipDetectionsIn, filterIDs, &status);
                fits_write_col(this->base.fitsOut, TBYTE, DETECTION_SURVEYID, 1, 1, nChipDetectionsIn, surveyIDs, &status);
                fits_write_col(this->base.fitsOut, TLONGLONG, DETECTION_IMAGEID, 1, 1, nChipDetectionsIn, imageID, &status);
                fits_write_col(this->base.fitsOut, TDOUBLE, DETECTION_OBSTIME, 1, 1, nChipDetectionsIn, obsTimes, &status);
                fits_write_col(this->base.fitsOut, TFLOAT, DETECTION_INSTFLUX, 1, 1, nChipDetectionsIn, instFlux, &status);
                fits_write_col(this->base.fitsOut, TFLOAT, DETECTION_INSTFLUXERR, 1, 1, nChipDetectionsIn, instFluxErr, &status);
                fits_write_col(this->base.fitsOut, TFLOAT, DETECTION_PEAKADU, 1, 1, nChipDetectionsIn, peakFlux, &status);
                fits_write_col(this->base.fitsOut, TSTRING, DETECTION_ASSOCDATE, 1, 1, nChipDetectionsIn, assocDate, &status);
                fits_write_col(this->base.fitsOut, TLONGLONG, DETECTION_INFOFLAG, 1, 1, nChipDetectionsIn, flags, &status);
                if (numOfDuplicates||numInvalidFlux||numOfInvalidIppIDet) 
                    fits_delete_rowlist(this->base.fitsOut, removeList, totalRemove, &status);

                // skinny object
                ippToPspsConfig_writeTable(this->base.config, fitsIn, this->base.fitsOut, nChipDetectionsIn, "SkinnyObject", false);
                fits_write_col(this->base.fitsOut, TLONGLONG, SKINNYOBJECT_OBJID, 1, 1, nChipDetectionsIn, objID, &status);
                fits_write_col(this->base.fitsOut, TLONGLONG, SKINNYOBJECT_IPPOBJID, 1, 1, nChipDetectionsIn, ippObjID, &status);
                fits_write_col(this->base.fitsOut, TBYTE, SKINNYOBJECT_SURVEYID, 1, 1, nChipDetectionsIn, surveyIDs, &status);
                if (numOfDuplicates||numInvalidFlux||numOfInvalidIppIDet)
                    fits_delete_rowlist(this->base.fitsOut, removeList, totalRemove, &status);

                // object calibration color
                ippToPspsConfig_writeTable(this->base.config, fitsIn, this->base.fitsOut, nChipDetectionsIn, "ObjectCalColor", false);
                fits_write_col(this->base.fitsOut, TLONGLONG, OBJECTCALCOLOR_OBJID, 1, 1, nChipDetectionsIn, objID, &status);
                fits_write_col(this->base.fitsOut, TLONGLONG, OBJECTCALCOLOR_IPPOBJID, 1, 1, nChipDetectionsIn, ippObjID, &status);
                fits_write_col(this->base.fitsOut, TBYTE, OBJECTCALCOLOR_FILTERID, 1, 1, nChipDetectionsIn, filterIDs, &status);
                if (numOfDuplicates||numInvalidFlux||numOfInvalidIppIDet)
                    fits_delete_rowlist(this->base.fitsOut, removeList, totalRemove, &status);
            }

            psLogMsg("ippToPsps", PS_LOG_INFO,
                    "+-----------+---------+----------+----------------+--------------+--------------+\n"
                    "| Extension | Rows in | Rows out |  Duplicate IDs | Invalid Flux | Bogus det ID |\n"
                    "|  %s |  %5ld  |   %5d  |     %5d      |    %5d     |    %5d     |\n",
                    extensionName, nChipDetectionsIn, nChipDetectionsOut, numOfDuplicates, numInvalidFlux, numOfInvalidIppIDet);

            totalDetectionsOut += nChipDetectionsOut;

            if (dvoDetections!= NULL) dvoFree(dvoDetections);
            SkyListFree(skylist);
        }

        if (error) break;
    }

    psLogMsg("ippToPsps", PS_LOG_INFO, "Total detections for this exposure = %ld\n", totalDetectionsOut);

    // free-up memory
    free(ippIDet);
    free(instMag);
    free(instMagErr);
    free(peakMag);
    free(flags);
    free(objID);
    free(detectID);
    free(ippObjID);
    free(ippDetectID);
    free(imageID);
    free(obsTimes);
    free(instFlux);
    free(instFluxErr);
    free(peakFlux);
    free(filterIDs);
    free(surveyIDs);
    free(removeList);
    for (uint32_t i=0; i<this->MAXDETECT;i++) free(assocDate[i]);
    free(assocDate);

    // write number of images we have found into FrameMeta table
    status=0;
    if (fits_movnam_hdu(this->base.fitsOut, BINARY_TBL, "FrameMeta", 0, &status))
        psError(PS_ERR_IO, false, "Can't move back to FrameMeta extension to write number of OTAs\n");
    else {
        fits_write_col(this->base.fitsOut, TSHORT, FRAMEMETA_NOTA, 1, 1, 1, &nOta, &status);
        fits_write_col(this->base.fitsOut, TSHORT, FRAMEMETA_NUMPHOTOREF, 1, 1, 1, &totalNumPhotoRef, &status);
    }
    status=0;
    if (fits_close_file(fitsIn, &status)) fits_report_error(stderr, status);
    // write results
    if (this->base.resultsXmlDoc) {

        xmlNodePtr rootNode = xmlDocGetRootElement(this->base.resultsXmlDoc);
        char tmp[100];
        sprintf(tmp, "%ld", minObjID);
        xmlNewChild(rootNode, NULL, BAD_CAST "minObjID", BAD_CAST tmp);
        sprintf(tmp, "%ld", maxObjID);
        xmlNewChild(rootNode, NULL, BAD_CAST "maxObjID", BAD_CAST tmp);
        sprintf(tmp, "%ld", totalDetectionsOut);
        xmlNewChild(rootNode, NULL, BAD_CAST "totalDetections", BAD_CAST tmp);
    }

    psLogMsg("ippToPsps", PS_LOG_INFO, "Data written for a total of %d chips/OTAs", nOta);

    if (error || nOta < 1) this->base.exitCode = PS_EXIT_DATA_ERROR;
    else this->base.exitCode = PS_EXIT_SUCCESS;

    return this->base.exitCode;
}


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

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

    printf("* exp ID          : %d\n", this->expId);
    printf("* exp name        : '%s'\n",  this->expName ? this->expName : "undef");
    printf("\n");
}

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

    bool haveExpId = false;
    bool haveExpName = false;

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

        char * sw = argv[optind];

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

            if(strcmp(sw, "-expid") == 0 ) {
                optind++; if( optind > ( argc-1 ) ) break;
                this->expId = atoi(argv[optind]);
                haveExpId = true;
            }
            else if(strcmp(sw, "-expname") == 0 ) {
                optind++; if( optind > ( argc-1 ) ) break;
                strcpy(this->expName, argv[optind]);
                haveExpName = 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() ||
            !haveExpId || 
            !haveExpName) {

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

    return true;
}

/**
  Constructor. Returns a new DetectionBatch object.
  */
DetectionBatch* new_DetectionBatch(int *argc, char **argv) {

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

    // 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 = 200000;

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

    this->expName = (char*)calloc(100, sizeof(char));

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

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

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

    return this;
}

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

    psTimerStart("detectionbatch");

    ippToPsps_VersionPrint();

    int exitCode;

    DetectionBatch* detectionBatch = new_DetectionBatch(&argc, argv);
    detectionBatch->base.run(detectionBatch);
    exitCode = detectionBatch->base.exitCode;

    detectionBatch->destroy(detectionBatch);

    double secs = psTimerMark("ippToPsps");

    psLogMsg("detectionbatch", 3, "detectionbatch 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;
}


