/** @file Fits.c
 *
 *  @ingroup ippToPsps
 *
 *  Source for all methods in the Fits class
 *
 *  @author IfA
 *  Copyright 2011 Institute for Astronomy, University of Hawaii
 */

#include "Fits.h"

/**
  Non-class method

  Gets cfitsio type from string
  */
int Fits_getDataType(char *typename) {

    if (!strncmp(typename, "TBYTE", 5))  return TBYTE;
    if (!strncmp(typename, "TSHORT", 6)) return TSHORT;
    if (!strncmp(typename, "TLONGLONG", 9)) return TLONGLONG;
    if (!strncmp(typename, "TLONG", 5)) return TLONG;
    if (!strncmp(typename, "TFLOAT", 6)) return TFLOAT;
    if (!strncmp(typename, "TDOUBLE", 7)) return TDOUBLE;
    if (!strncmp(typename, "TSTRING", 7)) return TSTRING;

//    this->logger->print(this->logger, MSG_ERROR, "Fits", "Don't understand data type '%s'", typename);

    return 0;
}

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

    this->logger->print(this->logger, MSG_DEBUG, "Fits", "Destructor\n");

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

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Unable to close FITS file '%s'\n", this->path);
        fits_report_error(stderr, status);
    }
    else 
        this->logger->print(this->logger, MSG_DEBUG, "Fits", "Closed FITS file '%s'\n", this->path);

    free(this);
}

/**
  Gets a header key value
  */
static bool getHeaderKeyValue(Fits* this, int type, char* name, void* value) {

    int status = 0;
    fits_read_key(this->file, type, name, value, NULL, &status);

    if (status) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Could not get value for header key '%s' from file at '%s'\n", name, this->path);
        return false;
    }

    return true;
}

/*
   Gets the contents of a column vector
   */
static bool getColumnVector(
        Fits* this,
        int col,
        long row,
        int type,
        int repeat,
        float* vector) {

    int status = 0;
    int anynull = 0;

    // remember to add 1 to row number since cfitsio is the stupidest libary ever written
    fits_read_col(this->file, type, col, row+1, 1, repeat, NULL, vector, &anynull, &status);

    if (status) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Failed to read vector column %d row %ld\n", col, row);
        return false;
    }

    return true;
}

/**
  Gets the column number for the provided column name
  */
static bool getColumnNumber(Fits* this, char* name, int* number) {

    int status = 0;
    fits_get_colnum(this->file, CASESEN, name, number, &status);
    if (status) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Could not get column number for '%s'\n", name);
        return false;
    }

    return true;
}

/*
   Gets metadata about a column 
   */
bool getColumnMeta(
        Fits* this,
        char* name,
        int* colNum,
        int* type,
        long* repeat) {

    int status = 0;
    fits_get_colnum(this->file, CASESEN, name, colNum, &status);
    if (status) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Unable to read col '%s'\n", name);
        return false;
    }

    status = 0;
    fits_get_eqcoltype(this->file, *colNum, type, repeat, NULL, &status);
    if (status) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Unable to read type info for '%s'\n", name);
        return false;
    }

    return true;
}


/**
  Moves to a certain extension of provided name and type
  */
bool moveToExtension(Fits* this, char* name, int type) {

    int status = 0;
    if (fits_movnam_hdu(this->file, type, name, 0, &status)) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Can't move to table extension named '%s'\n", name);
        return false;
    }

    return true;
}

/**
  Moves to a certain header extension
  */
static bool moveToHeader(Fits* this, char* name) {

    return moveToExtension(this, name, IMAGE_HDU);
}

/**
  Moves to a certain binary table extension
  */
static bool moveToBinaryTable(Fits* this, char* name) {

    return moveToExtension(this, name, BINARY_TBL);
}

/**
  Counts the rows in the current table extension
  */
static bool countRows(Fits* this, long* count) {

    int status = 0;
    if (fits_get_num_rows(this->file, count, &status)) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Count not count rows in this table\n");
        return false;
    }

    return true;
}

/**
  Moves to a certain binary table extension and counts rows within
  */
static bool moveToBinaryTableAndCountRows(Fits* this, char* name, long* count) {

    if (!moveToExtension(this, name, BINARY_TBL)) return false;
    return countRows(this, count);
}

/**
  Deletes this file.
  */
static bool delete(Fits* this) {

    if (remove(this->path) == -1) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Unable to delete '%s'\n", this->path);
        return false;
    }

    this->logger->print(this->logger, MSG_INFO, "Fits", "Deleted FITS file at '%s'\n", this->path);

    return true;
}

/**
  Returns the file path.
  */
static char* getPath(Fits* this) {

    return this->path;
}

/**
  Returns the file pointer.
  */
static fitsfile* getFilePtr(Fits* this) {

    return this->file;
}

/**
  Deletes a bunch of rows from the current binary table
  */
static bool deleteRows(Fits* this, long *rowlist, long nrows) {

    int status = 0;
    fits_delete_rowlist(this->file, rowlist, nrows, &status);
    if (status) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Unable to delete rows using rowlist\n");
        return false;
    }

    return true;
}

/**
  Reads a column from the current binary table
  */
static bool readColumn(
        Fits* this, 
        int datatype, 
        int colnum, 
        LONGLONG firstrow, 
        LONGLONG firstelem,
        LONGLONG nelements, 
        void* coldata)  {

    int anynull = 0;
    int status = 0;

    if (datatype == TBYTE) {

        int8_t nullval = -99;
        fits_read_col(this->file, datatype, colnum, firstrow, firstelem, nelements, &nullval, coldata, &anynull, &status);
    }
    else if (datatype == TSHORT) {

        int16_t nullval = -999;
        fits_read_col(this->file, datatype, colnum, firstrow, firstelem, nelements, &nullval, coldata, &anynull, &status);
    }
    else if (datatype == TLONG || datatype == TLONGLONG) {

        long nullval = -999;
        fits_read_col(this->file, datatype, colnum, firstrow, firstelem, nelements, &nullval, coldata, &anynull, &status);
    }
    else if (datatype == TFLOAT) {

        float nullval = -999.0;
        fits_read_col(this->file, datatype, colnum, firstrow, firstelem, nelements, &nullval, coldata, &anynull, &status);
    }
    else if (datatype == TDOUBLE) {

        double nullval = -999.0;
        fits_read_col(this->file, datatype, colnum, firstrow, firstelem, nelements, &nullval, coldata, &anynull, &status);
    }
    else if (datatype == TSTRING) {

        char nullval[20];
        strcpy(nullval, "null");
        fits_read_col(this->file, datatype, colnum, firstrow, firstelem, nelements, &nullval, coldata, &anynull, &status);
    }
    else {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Don't understand datatype '%d'\n", datatype);
        return false;
    }

    if (status) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Unable to read column data from column %d\n", colnum);
        return false;

    }

    return true;
}

/**
  Reads a column from the current binary table providing column name
  */
static bool readColumnUsingName(
        Fits* this,
        int datatype, 
        char* colname,
        LONGLONG firstrow, 
        LONGLONG firstelem,
        LONGLONG nelements, 
        void* coldata)  {

    int colnum;
    if (!getColumnNumber(this, colname, &colnum)) return false;

    return  readColumn(this, datatype, colnum, firstrow, firstelem, nelements, coldata);
}

/**
  Writes a column to the current binary table
  */
static bool writeColumn(
        Fits* this, 
        int datatype, 
        int colnum, 
        LONGLONG firstrow,
        LONGLONG firstelem, 
        LONGLONG nelements, 
        void* coldata) { 

    int status = 0;
    fits_write_col(this->file, datatype, colnum, firstrow, firstelem, nelements, coldata, &status);
    if (status) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Unable to write column data for column %d\n", colnum);
        return false;

    }

    return true;
}

/**
  Creates a new binary table
  */
static bool createBinaryTable(
        Fits* this, 
        LONGLONG nRows, 
        int nCols, 
        char *names[],
        char *types[], 
        char *name) {

    int status = 0;
    fits_create_tbl(this->file, BINARY_TBL, nRows, nCols, names, types, NULL, name, &status);

    if (status) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Unable to create table: '%s'\n", name);
        return false;
    }


    return true;
}

/**
  Initilization common to all constructors
  */
static Fits* init(const char* path, Logger* logger) {

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

    // fields
    strcpy(this->path, path);
    this->logger = logger;

    // method pointers
    this->getFilePtr = getFilePtr;
    this->getPath = getPath;
    this->countRows = countRows;
    this->getColumnMeta = getColumnMeta;
    this->getColumnNumber = getColumnNumber;
    this->getColumnVector = getColumnVector;
    this->readColumn = readColumn;
    this->readColumnUsingName = readColumnUsingName;
    this->getHeaderKeyValue = getHeaderKeyValue;
    this->moveToExtension = moveToExtension;
    this->moveToHeader = moveToHeader;
    this->moveToBinaryTable = moveToBinaryTable;
    this->moveToBinaryTableAndCountRows = moveToBinaryTableAndCountRows;
    this->deleteRows = deleteRows;
    this->createBinaryTable = createBinaryTable;
    this->writeColumn = writeColumn;
    this->delete = delete;
    this->destroy = destroy;

    assert(this);

    return this;
}

/**
  Constructor.

  Returns a new Fits object representing an existing FITS file.

*/
Fits* existing_Fits(const char* path, Logger* logger) {

    Fits* this = init(path, logger);
    this->logger->print(this->logger, MSG_DEBUG, "Fits", "Constructor for existing file at '%s'\n", path);

    int status = 0;
    if (fits_open_file(&this->file, path, READONLY, &status)) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Unable to open FITS file here %s\n", path);
        this->file = NULL;
    }


    return this;
}

/**
  Constructor. 

  Returns a new Fits object representing a new FITS file.
  */
Fits* new_Fits(const char* path, Logger* logger) {

    Fits* this = init(path, logger);
    this->logger->print(this->logger, MSG_DEBUG, "Fits", "Constructor for new file at '%s'\n", path);

    int status = 0;
    if (fits_create_file(&this->file, path, &status)) {

        this->logger->print(this->logger, MSG_ERROR, "Fits", "Unable to create FITS file here '%s'\n", path);
        this->file = NULL;
    }

    return this;
}


