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

#include "Fits.h"

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

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

/**
  Creates the StackDetection table
  */
static bool createStackDetectionTable(
        StackBatch* this,
        Fits* fitsIn,
        int8_t* filterIDs,
        int8_t* surveyIDs,
        long* skycellIDs,
        float exposureTime
        ) {

    char extensionName[25];
    sprintf(extensionName, "SkyChip.psf");
    long nDet = 0;
    if (!fitsIn->moveToBinaryTableAndCountRows(fitsIn, extensionName, &nDet)) return false;


    // allocate stuff
    char** assocDate = (char**)calloc(this->MAXDETECT, sizeof(char**));
    for (uint32_t i=0; i<this->MAXDETECT;i++) assocDate[i] = (char*)calloc(20,sizeof(char));
    long* removeList = (long*)calloc(this->MAXDETECT, sizeof(long));
    float* instMag = (float*)calloc(this->MAXDETECT, sizeof(float));
    float* peakMag = (float*)calloc(this->MAXDETECT, sizeof(float));
    float* peakFlux = (float*)calloc(this->MAXDETECT, sizeof(float));
    int* flags1 = (int*)calloc(this->MAXDETECT, sizeof(int));
    int* flags2 = (int*)calloc(this->MAXDETECT, sizeof(int));
    uint64_t* infoFlags = (uint64_t*)calloc(this->MAXDETECT, sizeof(uint64_t));

    bool peakFluxOk;

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

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

    fitsIn->readColumnUsingName(fitsIn, TFLOAT, "PSF_INST_MAG", 1, 1, nDet, instMag);
    fitsIn->readColumnUsingName(fitsIn, TFLOAT, "PEAK_FLUX_AS_MAG", 1, 1, nDet, peakMag);
    fitsIn->readColumnUsingName(fitsIn, TLONG, "FLAGS", 1, 1, nDet, flags1);
    fitsIn->readColumnUsingName(fitsIn, TLONG, "FLAGS2", 1, 1, nDet, flags2);

    this->base.logger->print(this->base.logger, MSG_INFO, "StackBatch", 
            "Looping through %ld psf detection rows from '%s'\n", nDet, extensionName);
    float mag;
    long unmatched = 0, totalDetections = 0, numOfDuplicates = 0, numInvalidFlux = 0, numDetectionsOut = 0;

    this->base.logger->print(this->base.logger, MSG_INFO, "StackBatch",  
            "+---------------+---------+----------+------------------+---------------+--------------+\n");
    this->base.logger->print(this->base.logger, MSG_INFO, "StackBatch",  
            "|   Extension   | Rows in | Rows out | Missing from DVO | Duplicate IDs | Invalid Flux |\n");

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

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

            //infoFlags[s] = ((uint64_t)flags1[s] << 32) | (uint64_t)flags2[s]; TODO implement after schema change to make infoFlag 64-bit
            infoFlags[s] = (uint64_t)flags1[s];

            peakFluxOk = getFlux(exposureTime, peakMag[s], &peakFlux[s], 0.0, NULL);

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

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

            totalDetections++;
        }
        else {

            unmatched++;
            continue;
        }
    }

    numDetectionsOut = totalDetections - numInvalidFlux;
    if (numDetectionsOut > 0) {

        this->base.fitsGenerator->createAndPopulateTable(this->base.fitsGenerator, fitsIn, this->base.fitsOut, nDet, "StackDetection", false);
        this->base.fitsOut->writeColumn(this->base.fitsOut, TLONG, STACKDETECTION_SKYCELLID, 1, 1, nDet, skycellIDs);
        this->base.fitsOut->writeColumn(this->base.fitsOut, TBYTE, STACKDETECTION_FILTERID, 1, 1, nDet, filterIDs);
        this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKDETECTION_PEAKFLUX, 1, 1, nDet, peakFlux);
        this->base.fitsOut->writeColumn(this->base.fitsOut, TBYTE, STACKDETECTION_SURVEYID, 1, 1, nDet, surveyIDs);
        this->base.fitsOut->writeColumn(this->base.fitsOut, TSTRING, STACKDETECTION_ASSOCDATE, 1, 1, nDet, assocDate);
        this->base.fitsOut->writeColumn(this->base.fitsOut, TLONGLONG, STACKDETECTION_INFOFLAG, 1, 1, nDet, flags1);

        if (numInvalidFlux) this->base.fitsOut->deleteRows(this->base.fitsOut, removeList, numInvalidFlux);

    }

    this->base.logger->print(this->base.logger, MSG_INFO, "StackBatch",
            "|  %12s |  %5ld  |   %5ld  |      %5ld       |    %5ld      |    %5ld     |\n",
            extensionName, nDet, numDetectionsOut, unmatched, numOfDuplicates, numInvalidFlux);
    this->base.logger->print(this->base.logger, MSG_INFO, "StackBatch",
            "+---------------+---------+----------+------------------+---------------+--------------+\n");


    // free stuff up
    free(removeList);
    free(instMag);
    free(flags1);
    free(flags2);
    free(infoFlags);
    for (uint32_t i=0; i<this->MAXDETECT;i++) free(assocDate[i]);
    free(assocDate);

    return true;
}
/**
  Structure to encapsulate block of flux values for StackApFlx table
  */
typedef struct {

    float* flx;
    float* flxErr;
    float* flxStd;
    float* flxFill;

} StackApFlxFluxes;

/**
  Creates the StackApFlx table for extended source attributes
  */
static bool createStackApFlxTable(
        StackBatch* this,
        Fits* fitsIn,
        int8_t* filterIDs,
        int8_t* surveyIDs
        ) {

    long nDet = 0;
    char extensionName[15];
    sprintf(extensionName, "SkyChip.xrad");
    if (!fitsIn->moveToBinaryTableAndCountRows(fitsIn, extensionName, &nDet)) return false;


    int aperFluxColNum, aperFluxErrColNum, aperFluxStDevColNum, aperFluxFillColNum;
    int type; // all the same type
    long repeat;

    fitsIn->getColumnMeta(fitsIn, "APER_FLUX", &aperFluxColNum, &type, &repeat);
    fitsIn->getColumnMeta(fitsIn, "APER_FLUX_ERR", &aperFluxErrColNum, &type, &repeat);
    fitsIn->getColumnMeta(fitsIn, "APER_FLUX_STDEV", &aperFluxStDevColNum, &type, &repeat);
    fitsIn->getColumnMeta(fitsIn, "APER_FILL", &aperFluxFillColNum, &type, &repeat);
    float* vector = calloc(repeat, sizeof(float));

    // we store 10 different flux values
    StackApFlxFluxes* stackApFlxFluxes = (StackApFlxFluxes*)calloc(11, sizeof(StackApFlxFluxes));
    for (long i=0; i<11; i++) {

        stackApFlxFluxes[i].flx = (float*)calloc(nDet, sizeof(float));
        stackApFlxFluxes[i].flxErr = (float*)calloc(nDet, sizeof(float));
        stackApFlxFluxes[i].flxStd = (float*)calloc(nDet, sizeof(float));
        stackApFlxFluxes[i].flxFill = (float*)calloc(nDet, sizeof(float));
    }

    this->base.logger->print(this->base.logger, MSG_INFO, "StackBatch", 
            "Looping through %ld extended source detections from extension '%s'\n", nDet, extensionName);
    for (long s=0; s<nDet; s++) {

        fitsIn->getColumnVector(fitsIn, aperFluxColNum,s, type, repeat, vector);
        for (int i=0; i<repeat; i++) stackApFlxFluxes[i].flx[s] = vector[i];

        fitsIn->getColumnVector(fitsIn, aperFluxErrColNum,s, type, repeat, vector);
        for (int i=0; i<repeat; i++) stackApFlxFluxes[i].flxErr[s] = vector[i];

        fitsIn->getColumnVector(fitsIn, aperFluxStDevColNum,s, type, repeat, vector);
        for (int i=0; i<repeat; i++) stackApFlxFluxes[i].flxStd[s] = vector[i];

        fitsIn->getColumnVector(fitsIn, aperFluxFillColNum,s, type, repeat, vector);
        for (int i=0; i<repeat; i++) stackApFlxFluxes[i].flxFill[s] = vector[i];

    }

    free(vector);

    //int status = 0;
    this->base.fitsGenerator->createAndPopulateTable(this->base.fitsGenerator, fitsIn, this->base.fitsOut, nDet, "StackApFlx", false);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TBYTE, STACKAPFLX_FILTERID, 1, 1, nDet, filterIDs);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TBYTE, STACKAPFLX_SURVEYID, 1, 1, nDet, surveyIDs);

    // R1->r10 flux values
    // 1
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR1, 1, 1, nDet, stackApFlxFluxes[0].flx);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR1ERR, 1, 1, nDet, stackApFlxFluxes[0].flxErr);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR1STD, 1, 1, nDet, stackApFlxFluxes[0].flxStd);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR1FILL, 1, 1, nDet, stackApFlxFluxes[0].flxFill);
    // 2
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR2, 1, 1, nDet, stackApFlxFluxes[1].flx);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR2ERR, 1, 1, nDet, stackApFlxFluxes[1].flxErr);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR2STD, 1, 1, nDet, stackApFlxFluxes[1].flxStd);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR2FILL, 1, 1, nDet, stackApFlxFluxes[1].flxFill);
    // 3
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR3, 1, 1, nDet, stackApFlxFluxes[2].flx);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR3ERR, 1, 1, nDet, stackApFlxFluxes[2].flxErr);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR3STD, 1, 1, nDet, stackApFlxFluxes[2].flxStd);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR3FILL, 1, 1, nDet, stackApFlxFluxes[2].flxFill);
    // 4
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR4, 1, 1, nDet, stackApFlxFluxes[3].flx);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR4ERR, 1, 1, nDet, stackApFlxFluxes[3].flxErr);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR4STD, 1, 1, nDet, stackApFlxFluxes[3].flxStd);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR4FILL, 1, 1, nDet, stackApFlxFluxes[3].flxFill);
    // 5
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR5, 1, 1, nDet, stackApFlxFluxes[4].flx);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR5ERR, 1, 1, nDet, stackApFlxFluxes[4].flxErr);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR5STD, 1, 1, nDet, stackApFlxFluxes[4].flxStd);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR5FILL, 1, 1, nDet, stackApFlxFluxes[4].flxFill);
    // 6
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR6, 1, 1, nDet, stackApFlxFluxes[5].flx);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR6ERR, 1, 1, nDet, stackApFlxFluxes[5].flxErr);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR6STD, 1, 1, nDet, stackApFlxFluxes[5].flxStd);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR6FILL, 1, 1, nDet, stackApFlxFluxes[5].flxFill);
    // 7
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR7, 1, 1, nDet, stackApFlxFluxes[6].flx);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR7ERR, 1, 1, nDet, stackApFlxFluxes[6].flxErr);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR7STD, 1, 1, nDet, stackApFlxFluxes[6].flxStd);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR7FILL, 1, 1, nDet, stackApFlxFluxes[6].flxFill);
    // 8
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR8, 1, 1, nDet, stackApFlxFluxes[7].flx);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR8ERR, 1, 1, nDet, stackApFlxFluxes[7].flxErr);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR8STD, 1, 1, nDet, stackApFlxFluxes[7].flxStd);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR8FILL, 1, 1, nDet, stackApFlxFluxes[7].flxFill);
    // 9
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR9, 1, 1, nDet, stackApFlxFluxes[8].flx);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR9ERR, 1, 1, nDet, stackApFlxFluxes[8].flxErr);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR9STD, 1, 1, nDet, stackApFlxFluxes[8].flxStd);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR9FILL, 1, 1, nDet, stackApFlxFluxes[8].flxFill);
    // 10
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR10, 1, 1, nDet, stackApFlxFluxes[9].flx);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR10ERR, 1, 1, nDet, stackApFlxFluxes[9].flxErr);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR10STD, 1, 1, nDet, stackApFlxFluxes[9].flxStd);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TFLOAT, STACKAPFLX_FLXR10FILL, 1, 1, nDet, stackApFlxFluxes[9].flxFill);

    for (long i=0; i<11; i++) {

        free(stackApFlxFluxes[i].flx);
        free(stackApFlxFluxes[i].flxErr);
        free(stackApFlxFluxes[i].flxStd);
        free(stackApFlxFluxes[i].flxFill);
    }

    free(stackApFlxFluxes);

    return true;
}

/**
  Creates the StackModelFit table
  */
static bool createStackModelFitTable(
        StackBatch* this,
        Fits *fitsIn,
        int8_t* filterIDs,
        int8_t* surveyIDs) {

    long nDet = 0;
    char extensionName[40];
    sprintf(extensionName, "SkyChip.xfit");
    if (!fitsIn->moveToBinaryTableAndCountRows(fitsIn, extensionName, &nDet)) return false;

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

    // read whole columns
    fitsIn->readColumnUsingName(fitsIn, TLONG, "IPP_IDET", 1, 1, nDet, ippIdets);
    fitsIn->readColumnUsingName(fitsIn, TSTRING, "MODEL_TYPE", 1, 1, nDet, modelTypes);

    this->base.logger->print(this->base.logger, MSG_INFO, "StackBatch", 
            "Looping through %ld model fit rows from '%s'\n", nDet, extensionName);
    for (long i = 0; i<nDet; i++) {

        printf("IPP_IDET %ld model %s\n", ippIdets[i], modelTypes[i]);

    }

    this->base.fitsGenerator->createAndPopulateTable(this->base.fitsGenerator, fitsIn, this->base.fitsOut, nDet, "StackModelFit", false);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TBYTE, STACKMODELFIT_FILTERID, 1, 1, nDet, filterIDs);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TBYTE, STACKMODELFIT_SURVEYID, 1, 1, nDet, surveyIDs);

    // free up
    free(ippIdets);
    for (uint32_t i=0; i<this->MAXDETECT;i++) free(modelTypes[i]);
    free(modelTypes);

    return true;
}

/**
  Creates the StackToImage table
  */
static bool createStackToImageTable(
        StackBatch* this,
        Fits* fitsIn
        ) {

    this->base.fitsGenerator->createAndPopulateTable(this->base.fitsGenerator, fitsIn, this->base.fitsOut, 3/*TODO*/, "StackToImage", false);

    return true;
}

/**
  Does the work. Writes all extensions to new FITS file.
  */
static int run(StackBatch* 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;

    // get primary header and pull stuff out for later
    float exposureTime; fitsIn->getHeaderKeyValue(fitsIn, TFLOAT, "EXPTIME", &exposureTime);
    char filterType[20]; fitsIn->getHeaderKeyValue(fitsIn, TSTRING, "FILTER", filterType);

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

    exposureTime = 60.0;// TODO
    filterID = 3; // TODO

    // write StackMeta
    this->base.fitsGenerator->createAndPopulateTable(this->base.fitsGenerator, fitsIn, this->base.fitsOut, 1, "StackMeta", true);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TLONG, STACKMETA_SKYCELLID, 1, 1, 1, &this->skycellId);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TBYTE, STACKMETA_FILTERID, 1, 1, 1, &filterID);
    this->base.fitsOut->writeColumn(this->base.fitsOut, TBYTE, STACKMETA_SURVEYID, 1, 1, 1, &this->base.surveyID);

    // allocate stuff for other extensions
    int8_t* filterIDs = (int8_t*)calloc(this->MAXDETECT, sizeof(int8_t));
    int8_t* surveyIDs = (int8_t*)calloc(this->MAXDETECT, sizeof(int8_t));
    long* skycellIDs = (long*)calloc(this->MAXDETECT, sizeof(long));

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

        skycellIDs[s] = this->skycellId;
        filterIDs[s] = filterID;
        surveyIDs[s] = this->base.surveyID;
    }

    // create all extensions
    createStackDetectionTable(this, fitsIn, filterIDs, surveyIDs, skycellIDs, exposureTime);
    createStackApFlxTable(this, fitsIn, filterIDs, surveyIDs);
    createStackModelFitTable(this, fitsIn, filterIDs, surveyIDs);
    createStackToImageTable(this, fitsIn);

    // free stuff up
    free(filterIDs);
    free(surveyIDs);
    free(skycellIDs);
    fitsIn->destroy(fitsIn);

    return this->base.exitCode;
}

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

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

    this->base.logger->print(this->base.logger, MSG_INFO, "StackBatch", "skycell ID      : %d\n", this->skycellId);
    this->base.logger->print(this->base.logger, MSG_INFO, "StackBatch", "\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++;
    }

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

    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(Logger* logger, int *argc, char **argv) {

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

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

    this->MAXDETECT = 150000;

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

    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->print(logger, MSG_INFO, "main", "Creating new stack batch\n");

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

    stackBatch->destroy(stackBatch);

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

    return exitCode;
}

