/** @file ippToPsps.c
 *
 *  @ingroup ippToPsps
 *
 *  @author IfA
 *  Copyright 2009 Institute for Astronomy, University of Hawaii
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <sys/stat.h>

#include <pslib.h>
#include <psmodules.h>
#include "ippToPsps.h"

extern int ippToPsps_batchInit(IppToPsps *data);
extern int ippToPsps_batchDetection(IppToPsps *data);
extern int ippToPsps_batchStack(IppToPsps *data);
extern int ippToPsps_batchDifference(IppToPsps *data);

// Destructor
static void ippToPsps_Destructor(IppToPsps* this) {

    // if we created an output file...
    if (this->fitsOut) {

        // if unsuccessful, then delete FITS file
        if (this->exitCode != PS_EXIT_SUCCESS) {

            psError(PS_ERR_UNKNOWN, false, "Failed, so deleting %s", this->fitsOutPath);

            // if process failed, we need to delete that FITS file
            if (remove(this->fitsOutPath) == -1) {

                psError(PS_ERR_UNKNOWN, false, "Unable to delete %s", this->fitsOutPath);
            }
        }
        // ...otherwise, close file
        else {

            int status = 0;
            if (fits_close_file(this->fitsOut, &status)) {

                psError(PS_ERR_IO, false, "Unable to close FITS file %s", this->fitsOutPath);
                fits_report_error(stderr, status);
            }
        }
    }

    psFree(this->fitsInPath);
    psFree(this->resultsPath);
    psFree(this->fitsOutPath);
    psFree(this->configsDir);
    psFree(this->pmconfig);

    if (this->resultsFile) fclose(this->resultsFile);

    for(uint32_t i=0; i<this->numOfInputFiles; i++)
        free(this->inputFiles[i]);
    free(this->inputFiles);

    if (this->dvoConfig != NULL) dvoConfigFree(this->dvoConfig);

    ippToPspsConfig_Destructor(this->config);

    pmConfigDone();

    return;
}

// read file-input file and populate array of paths
static bool ippToPsps_readInputFilePaths(IppToPsps* this) {

    FILE* file = fopen (this->fitsInPath, "r");

    if (file == NULL) {

        psError(PS_ERR_UNKNOWN, false, "Unable to open file at %s", this->fitsInPath);
        return false;
    }

    this->numOfInputFiles = 0;
    char line[1000];
    while (fgets(line, 1000, file) != NULL) this->numOfInputFiles++;

    printf ("Number of input files %d\n", this->numOfInputFiles);

    if (this->numOfInputFiles < 1) return false;


    this->inputFiles = (char**)calloc(this->numOfInputFiles, sizeof(char*));
    for(uint32_t i=0; i<this->numOfInputFiles; i++)
        this->inputFiles[i] = (char*)calloc(1000, 1);

    rewind(file);
    uint16_t i = 0;
    while (fgets(line, 1000, file) != NULL) {

        sprintf(this->inputFiles[i], "%s", line);

        // remove newline
        for(uint16_t j=0;j<strlen(this->inputFiles[i]);j++)
            if (this->inputFiles[i][j] == '\n') this->inputFiles[i][j] = '\0';
        i++;
    }

    fclose(file);

    return true;
}

// Prints usage to stdout.
static void usage(IppToPsps *this, const char *program, psMetadata *arguments) {

    fprintf(stderr, "\nUsage: %s\n", program);
    fprintf(stderr, "\n");
    psArgumentHelp(arguments);
    fprintf(stderr, "\n");
}

// the available IPP data products
typedef enum {

    PRODUCT_INIT,
    PRODUCT_DETECTION,
    PRODUCT_STACK,
    PRODUCT_DIFFERENCE,
    PRODUCT_UNDEFINED

} ippProducts;

// Deals with command-line args.
static bool ippToPsps_Arguments(IppToPsps *this, int argc, char **argv) {

    assert(this);
    assert(this->pmconfig);

    psMetadata *arguments = psMetadataAlloc();
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-expid", 0, "Exposure ID", NULL);
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-input", 0, "Path to FITS inout", NULL);
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-output", 0, "Path to FITS output", NULL);
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-results", 0, "Path to results output", NULL);
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-config", 0, "Path to config dir", NULL);
    psMetadataAddStr(arguments, PS_LIST_TAIL, "-product", 0, "Product type: [det|stack|diff]", NULL);

    bool ret = true;
    if (argc < 2 || !psArgumentParse(arguments, &argc, argv)) {

        usage(this, argv[0], arguments);
        ret = false;
    }
    else {

        char *tmp = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-expid"));
        if (tmp) this->expId = atoi(tmp);
        //free(tmp); tmp = NULL;
        this->fitsInPath = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-input"));
        this->resultsPath = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-results"));
        this->fitsOutPath = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-output"));
        this->configsDir = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-config"));
        tmp = psMemIncrRefCounter(psMetadataLookupStr(NULL, arguments, "-product"));

        if (!tmp) this->product = PRODUCT_UNDEFINED;
        else if (strcmp(tmp, "init") == 0) this->product = PRODUCT_INIT;
        else if (strcmp(tmp, "det") == 0) this->product = PRODUCT_DETECTION;
        else if (strcmp(tmp, "stack") == 0) this->product = PRODUCT_STACK;
        else if (strcmp(tmp, "diff") == 0) this->product = PRODUCT_DIFFERENCE;
        else this->product = PRODUCT_UNDEFINED;

        if (
                (this->product != PRODUCT_INIT && !this->fitsInPath) || 
                (this->product != PRODUCT_INIT && !this->resultsPath) || 
                (this->product != PRODUCT_INIT && this->expId == -1) ||
                !this->fitsOutPath || 
                !this->configsDir || 
                this->product == PRODUCT_UNDEFINED ) {

            usage(this,argv[0], arguments);
            ret = false;
        }
    }

    psTrace("ippToPsps", 1, "Done reading command-line arguments\n");
    psFree(arguments);

    return ret;
}

// Runs the requested process
static int ippToPsps_run(IppToPsps *this) {

    if(this->exitCode == PS_EXIT_SUCCESS) {

        // which product?
        switch(this->product) {

            case PRODUCT_INIT:
                this->exitCode = ippToPsps_batchInit(this);
                break;
            case PRODUCT_DETECTION:
                this->exitCode = ippToPsps_batchDetection(this);
                break;
            case PRODUCT_STACK:
                this->exitCode = ippToPsps_batchStack(this);
                break;
            case PRODUCT_DIFFERENCE:
                this->exitCode = ippToPsps_batchDifference(this);
                break;
            default:
                psError(PS_ERR_UNKNOWN, false, "Unable to run for this product type (%d)", this->product);
                this->exitCode = false;
                break;
        }
    }

    return this->exitCode;
}

// Constructor
IppToPsps *ippToPsps_Constructor(int *argc, char **argv) {

    PS_ASSERT_PTR_NON_NULL(argc, NULL);
    PS_ASSERT_PTR_NON_NULL(argv, NULL);

    IppToPsps *this = psAlloc(sizeof(IppToPsps));
    psMemSetDeallocator(this, (psFreeFunc)ippToPsps_Destructor);

    this->expId = -1;
    this->fitsInPath = NULL;
    this->resultsPath = NULL;
    this->resultsFile = NULL;
    this->numOfInputFiles = 0;
    this->inputFiles = NULL;
    this->fitsOutPath = NULL;
    this->fitsOut = NULL;
    this->configsDir = NULL;
    this->dvoConfig = NULL;
    this->config = NULL;
    this->dvoConfig = dvoConfigRead(argc, argv);
    this->pmconfig = pmConfigRead(argc, argv, NULL);
    this->exitCode = PS_EXIT_SUCCESS;
    this->run = ippToPsps_run;

    if (!this->pmconfig) {

        psError(PS_ERR_UNKNOWN, false, "Unable to read configuration.");
        this->exitCode = PS_EXIT_CONFIG_ERROR;
        return this;
    }

    if (!ippToPsps_Arguments(this, *argc, argv)) {

        psFree(this);
        this->exitCode = PS_EXIT_CONFIG_ERROR;
        return this;
    }

    if (this->product != PRODUCT_INIT && !ippToPsps_readInputFilePaths(this)) {

        this->exitCode = PS_EXIT_DATA_ERROR;
        return this;
    }

    if (this->product == PRODUCT_INIT)
        psStringAppend(&this->configsDir, "/init");
    if (this->product == PRODUCT_DETECTION)
        psStringAppend(&this->configsDir, "/detection");
    if (this->product == PRODUCT_DIFFERENCE)
        psStringAppend(&this->configsDir, "/diff");
    if (this->product == PRODUCT_STACK)
        psStringAppend(&this->configsDir, "/stack");


    char outputName[100];
    sprintf(outputName, "%08d.FITS", this->expId);
    psStringAppend(&this->fitsOutPath, "/%s", outputName);

    // create an output FITS file
    int status=0;
    if (fits_create_file(&this->fitsOut, this->fitsOutPath, &status)) {
        fits_report_error(stderr, status);
        psError(PS_ERR_IO, false, "Unable to create file at: '%s'", this->fitsOutPath);
        this->exitCode = PS_EXIT_SYS_ERROR;
        return this;
    }

    this->config = ippToPspsConfig_Constructor(this->configsDir);
    if (this->config == NULL) {

        this->exitCode = PS_EXIT_CONFIG_ERROR;
        return this;
    }

    // ready results file, if necessary
    if (this->resultsPath) {

        this->resultsFile = fopen (this->resultsPath, "w+");

        if (this->resultsFile == NULL) {

            psError(PS_ERR_UNKNOWN, false, "Unable to open results file at %s", this->resultsPath);
            // not essential to write results, so don't bail out
        }
        else {
            if (fprintf(this->resultsFile, "%s\n", outputName) < 1 )     
                psError(PS_ERR_IO, false, "Unable to write FITS output filename to'%s'\n", this->resultsPath);
        }
    }

    return this;
}


