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

#include "Fits.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);
}

/**
  Writes results to XML file
  */
static bool writeResults(DetectionBatch* this, long minObjID, long maxObjID, long totalDetectionsOut) {

    if (!this->base.resultsXmlDoc) return false;

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

    return true;
}

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

    // open input FITS file
    Fits* fitsIn = existing_Fits(this->base.inputFiles[0], this->base.logger);
    if (fitsIn->getFilePtr(fitsIn) == NULL)  return PS_EXIT_SYS_ERROR;

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


    this->base.fitsGenerator->createAndPopulateTable(this->base.fitsGenerator, fitsIn, this->base.fitsOut, 1, "FrameMeta", true);

    // FrameMeta values
    int8_t filterID = -1;
    if (!this->base.initData->getFilterId(this->base.initData, filterType, &filterID)) {
    
        this->base.exitCode = PS_EXIT_DATA_ERROR;
        return this->base.exitCode;
    }
    this->base.fitsOut->writeColumn(this->base.fitsOut, TBYTE, FRAMEMETA_FILTERID, 1, 1, 1, &filterID);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TLONG, FRAMEMETA_FRAMEID, 1, 1, 1, &this->expId);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TSTRING, FRAMEMETA_FRAMENAME, 1, 1, 1, &(this->expName));
    this->base.fitsOut->writeColumn(this->base.fitsOut, TBYTE, FRAMEMETA_SURVEYID, 1, 1, 1, &this->base.surveyID);


    int16_t cameraID = 1; // TODO
    int16_t cameraConfigID = 1; // TODO
    int16_t telescopeID = 1; // TODO
    this->base.fitsOut->writeColumn(this->base.fitsOut, TSHORT, FRAMEMETA_CAMERAID, 1, 1, 1, &cameraID);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TSHORT, FRAMEMETA_CAMERACONFIGID, 1, 1, 1, &cameraConfigID);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TSHORT, FRAMEMETA_TELESCOPEID, 1, 1, 1, &telescopeID);

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

    char ccdNumber[3], extensionName[15];
    // for storing FITS column data - do this once, to avoid expension free/calloc for each chip
    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;

        // if running in test mode, don't use today's date
        if (this->base.testMode) strcpy(assocDate[s], "2010-01-01");
        else 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;
    int startX, stopX, startY, stopY;

    // in test mode, only run for chip 33
    if (this->base.testMode) {

        startX = 3;
        stopX = 4;
        startY = 3;
        stopY = 4;
    }
    // in 'normal' mode, run for all chips
    else {
    
        startX = 0;
        stopX = 8;
        startY = 0;
        stopY = 8;
    }

    this->base.logger->print(this->base.logger, MSG_INFO, "DetectionBatch",  
            "+-----------+---------+----------+----------------+--------------+--------------+\n");
    this->base.logger->print(this->base.logger, MSG_INFO, "DetectionBatch",  
            "| Extension | Rows in | Rows out |  Duplicate IDs | Invalid Flux | Bogus det ID |\n");

    // loop round chips
    for (int x=startX; x<stopX; x++) {
        for (int y=startY; y<stopY; 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);
            if (!fitsIn->moveToBinaryTable(fitsIn, extensionName)) continue;

            // move to header extension
            sprintf(extensionName, "XY%s.hdr", ccdNumber);
            if (!fitsIn->moveToHeader(fitsIn, extensionName)) continue;

            // stuff to save from psf.hdr
            fitsIn->getHeaderKeyValue(fitsIn, TFLOAT, "FWHM_MAJ", &fwhmMaj);
            fitsIn->getHeaderKeyValue(fitsIn, TFLOAT, "FWHM_MIN", &fwhmMin);
            fitsIn->getHeaderKeyValue(fitsIn, TFLOAT, "IQ_FW1", &momentMaj);
            fitsIn->getHeaderKeyValue(fitsIn, TFLOAT, "IQ_FW2", &momentMin);
            fitsIn->getHeaderKeyValue(fitsIn, TLONG, "IMAGEID", &imageId);
            fitsIn->getHeaderKeyValue(fitsIn, TLONG, "SOURCEID", &sourceId);
            fitsIn->getHeaderKeyValue(fitsIn, TLONG, "NASTRO", &numPhotoRef);
            totalNumPhotoRef += numPhotoRef; // total up for storing in FrameMeta

            // access DVO database
            skylist = dvoSkyListByExternID(this->base.dvoConfig, sourceId, imageId, &image);
            if (skylist == NULL) {
                this->base.logger->print(this->base.logger, MSG_ERROR, "DetectionBatch",  
                        "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 ) {

                this->base.logger->print(this->base.logger, MSG_ERROR, "DetectionBatch", "Number of detections (%d) exceeds max limit (%ld)\n",
                        numDvoDetections, this->MAXDETECT);
                error = true;
                break;
            }

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

            // now move BACK to detections table in smf
            sprintf(extensionName, "XY%s.psf", ccdNumber);
            long nChipDetectionsIn = 0;
            if (!fitsIn->moveToBinaryTableAndCountRows(fitsIn, extensionName, &nChipDetectionsIn)) continue;

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

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

            // determine column numbers for certain IPP detection columns - do this only once to save time
            if (firstTimeIn) {

                fitsIn->getColumnNumber(fitsIn, "IPP_IDET", &ippIDetNum);
                fitsIn->getColumnNumber(fitsIn, "PSF_INST_MAG", &instMagNum);
                fitsIn->getColumnNumber(fitsIn, "PSF_INST_MAG_SIG", &instMagErrNum);
                fitsIn->getColumnNumber(fitsIn, "PEAK_FLUX_AS_MAG", &peakMagNum);

                firstTimeIn=false;
            }

            //anynull = 0;
            fitsIn->readColumn(fitsIn, TLONG, ippIDetNum, 1, 1, nChipDetectionsIn, ippIDet);
            fitsIn->readColumn(fitsIn, TFLOAT, instMagNum, 1, 1, nChipDetectionsIn, instMag);
            fitsIn->readColumn(fitsIn, TFLOAT, instMagErrNum, 1, 1, nChipDetectionsIn, instMagErr);
            fitsIn->readColumn(fitsIn, TFLOAT, peakMagNum, 1, 1, nChipDetectionsIn, peakMag);

            // 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
            this->base.fitsOut->writeColumn(this->base.fitsOut, TLONG, IMAGEMETA_NDETECT, 1, 1, 1, &nChipDetectionsOut);

            int totalRemove = numOfDuplicates+numInvalidFlux+numOfInvalidIppIDet;

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

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

                // skinny object
                this->base.fitsGenerator->createAndPopulateTable(this->base.fitsGenerator, fitsIn, this->base.fitsOut, nChipDetectionsIn, "SkinnyObject", false);
                this->base.fitsOut->writeColumn(this->base.fitsOut, TLONGLONG, SKINNYOBJECT_OBJID, 1, 1, nChipDetectionsIn, objID);
                this->base.fitsOut->writeColumn(this->base.fitsOut, TLONGLONG, SKINNYOBJECT_IPPOBJID, 1, 1, nChipDetectionsIn, ippObjID);
                this->base.fitsOut->writeColumn(this->base.fitsOut, TBYTE, SKINNYOBJECT_SURVEYID, 1, 1, nChipDetectionsIn, surveyIDs);
                if (numOfDuplicates||numInvalidFlux||numOfInvalidIppIDet) this->base.fitsOut->deleteRows(this->base.fitsOut, removeList, totalRemove);

                // object calibration color
                this->base.fitsGenerator->createAndPopulateTable(this->base.fitsGenerator, fitsIn, this->base.fitsOut, nChipDetectionsIn, "ObjectCalColor", false);
                this->base.fitsOut->writeColumn(this->base.fitsOut, TLONGLONG, OBJECTCALCOLOR_OBJID, 1, 1, nChipDetectionsIn, objID);
                this->base.fitsOut->writeColumn(this->base.fitsOut, TLONGLONG, OBJECTCALCOLOR_IPPOBJID, 1, 1, nChipDetectionsIn, ippObjID);
                this->base.fitsOut->writeColumn(this->base.fitsOut, TBYTE, OBJECTCALCOLOR_FILTERID, 1, 1, nChipDetectionsIn, filterIDs);
                if (numOfDuplicates||numInvalidFlux||numOfInvalidIppIDet) this->base.fitsOut->deleteRows(this->base.fitsOut, removeList, totalRemove);
            }

            this->base.logger->print(this->base.logger, MSG_INFO, "DetectionBatch",
                    "|  %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;
    }

    this->base.logger->print(this->base.logger, MSG_INFO, "DetectionBatch",  
            "+-----------+---------+----------+----------------+--------------+--------------+\n");

    this->base.logger->print(this->base.logger, MSG_INFO, "DetectionBatch", "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
    if (this->base.fitsOut->moveToBinaryTable(this->base.fitsOut, "FrameMeta")) {

        this->base.fitsOut->writeColumn(this->base.fitsOut, TSHORT, FRAMEMETA_NOTA, 1, 1, 1, &nOta);
        this->base.fitsOut->writeColumn(this->base.fitsOut, TSHORT, FRAMEMETA_NUMPHOTOREF, 1, 1, 1, &totalNumPhotoRef);
    }

    fitsIn->destroy(fitsIn);

    writeResults(this, minObjID, maxObjID, totalDetectionsOut);

    this->base.logger->print(this->base.logger, MSG_INFO, "DetectionBatch", "Data written for a total of %d chips/OTAs\n", 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);

    this->base.logger->print(this->base.logger, MSG_INFO, "DetectionBatch", "exp ID          : %d\n", this->expId);
    this->base.logger->print(this->base.logger, MSG_INFO, "DetectionBatch", "exp name        : %s\n",  this->expName ? this->expName : "undef");
    this->base.logger->print(this->base.logger, MSG_INFO, "DetectionBatch", "\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++;
    }

    char fitsOutFile[40];
    sprintf(fitsOutFile, "%08d.FITS", this->expId);
    this->base.parseArguments(&this->base, argc, argv, "/detection", fitsOutFile);

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

        this->base.logger->print(this->base.logger, MSG_ERROR, "DetectionBatch", "Problem with supplied arguments\n");
        this->print(this);
        this->base.exitCode = PS_EXIT_CONFIG_ERROR;
        return false;
    }

    return true;
}

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

    logger->print(logger, MSG_DEBUG, "DetectionBatch", "Constructor\n");
    DetectionBatch *this = (DetectionBatch*)calloc(1, sizeof(DetectionBatch));

    // call base-class constructor
    new_Batch(logger, &this->base);

    this->MAXDETECT = 200000;

    // method pointers
    this->print = print;
    this->destroy = destroy;
    this->base.run = run;

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

    parseArguments(this, *argc, argv);

    this->print(this);

    return this;
}

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

//    ippToPsps_VersionPrint();

    Logger* logger = new_Logger(NULL, false);
//    Logger* logger = new_Logger("./detBatchLog.txt", false);
    logger->print(logger, MSG_INFO, "main", "Creating new detection batch\n");

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

    detectionBatch->destroy(detectionBatch);

    // tidy up
    psLibFinalize();
    logger->destroy(logger);

    return exitCode;
}


