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

#include "ippToPspsConfig.h"
#include <ctype.h>
#include <libxml/parser.h>
#include <libxml/tree.h>

// Gets PS type from string
static int ippToPsps_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;

    psError(PS_ERR_IO, false, "Don't understand type '%s'", typename);

    return 0;
}

// finds a column within this table
static IppToPspsConfig_Column* ippToPspsConfig_findColumn(IppToPspsConfig_Table* table, const char* name) {

    IppToPspsConfig_Column* column = NULL;

    for (uint32_t i=0; i<table->numOfColumns; i++) {

        if (strcmp(table->columns[i].pspsName, name) == 0) {
            column = table->columns+i;
            break;
        }
    }

    if (!column) psError(PS_ERR_IO, false, "Could not find column '%s' in table '%s'", name, table->name);

    return column;
}

// finds a table from the array of tables
static IppToPspsConfig_Table* ippToPspsConfig_findTable(IppToPspsConfig* this, const char* name) {

    IppToPspsConfig_Table* table = NULL;

    for (uint32_t i=0; i<this->numOfTables; i++) {

        if (strcmp(this->tables[i].name, name) == 0) {
            table = this->tables+i;
            break;
        }
    }

    if (!table) psError(PS_ERR_IO, false, "Could not find table '%s'", name);

    return table;
}

// gets an attribute value from XML node
static bool ippToPspsConfig_getAttribute(xmlNode* node, const char* attName, char* _value) {

    xmlChar* value = xmlGetProp(node, (const xmlChar*)attName);
    if (!value) return false;
    sprintf(_value, "%s", value);
    xmlFree(value);

    return true;
}

// creates a FITS binary table and populates it with defaults based on definition in schema XML
static bool ippToPspsConfig_createTable(IppToPspsConfig_Table* table, fitsfile* fitsOut, const long nRows) {

    if (!table) return false;
    if (nRows < 1) return false;

    long nCols = table->numOfColumns;

    char** colNames = (char**)calloc(nCols, sizeof(char*));
    char** colTypes = (char**)calloc(nCols, sizeof(char*));

    uint32_t i = 0;
    for (i=0;i<nCols;i++) colTypes[i] = (char*)calloc(5,sizeof(char));

    i = 0;
    for (uint32_t m=0; m<table->numOfColumns; m++) {

        colNames[i] = table->columns[i].pspsName;

        //        sprintf(colNames[i], table->columns[i].pspsName);

        switch (table->columns[i].pspsType) {

            case TBYTE:
                sprintf(colTypes[i],"1B");
                break;
            case TSHORT:
                sprintf(colTypes[i],"1I");
                break;
            case TLONG:
                sprintf(colTypes[i],"1J"); // TODO
                break;
            case TLONGLONG:
                sprintf(colTypes[i],"1K");
                break;
            case TFLOAT:
                sprintf(colTypes[i],"1E");
                break;
            case TDOUBLE:
                sprintf(colTypes[i],"1D");
                break;
            case TSTRING:
                sprintf(colTypes[i],"32A"); // TODO width?
                break;

            default:
                break;
        }

        i++;
    }

    int status = 0;
    if (fits_create_tbl(fitsOut, BINARY_TBL, nRows, nCols, colNames, colTypes, NULL, table->name, &status)) {

        fits_report_error(stderr, status);
        psError(PS_ERR_IO,"Unable to create table: %s", table->name);
    }

    for (i=0;i<nCols;i++) free(colTypes[i]);
    free(colTypes);
    free(colNames);

    // now insert defaults
    int8_t* int8col = (int8_t*)calloc(nRows, sizeof(int8_t));
    int16_t* int16col = (int16_t*)calloc(nRows, sizeof(int16_t));
    long* longcol = (long*)calloc(nRows, sizeof(long));
    float* floatcol = (float*)calloc(nRows, sizeof(float));
    double* doublecol = (double*)calloc(nRows, sizeof(double));
    char** strcol = (char**)calloc(nRows, sizeof(char**));
    for (uint32_t i=0; i<nRows;i++)
        strcol[i] = (char*)calloc(20,sizeof(char)); // TODO 20? size issue

    uint32_t j;
    int64_t tempLong;
    double tempDouble;
    char tempStr[20];

    int col;
    for (uint32_t i=0; i<table->numOfColumns; i++) {

        col = i+1;

        strcpy(tempStr,table->columns[i].defaultValue);
        tempLong = atol(tempStr);
        tempDouble = atof(tempStr);

        switch (table->columns[i].pspsType) {

            case TBYTE:
                for(j=0;j<nRows;j++) int8col[j] = (int8_t)tempLong;
                fits_write_col(fitsOut, TBYTE, col, 1, 1, nRows, int8col, &status);
                break;
            case TSHORT:
                for(j=0;j<nRows;j++) int16col[j] = (int16_t)tempLong;
                fits_write_col(fitsOut, TSHORT, col, 1, 1, nRows, int16col, &status);
                break;
            case TLONG:
                for(j=0;j<nRows;j++) longcol[j] = (long)tempLong;
                fits_write_col(fitsOut, TLONG, col, 1, 1, nRows, longcol, &status);
                break;

            case TLONGLONG:
                for(j=0;j<nRows;j++) longcol[j] = tempLong;
                fits_write_col(fitsOut, TLONGLONG, col, 1, 1, nRows, longcol, &status);
                break;

            case TFLOAT:
                for(j=0;j<nRows;j++) floatcol[j] = (float)tempDouble;
                fits_write_col(fitsOut, TFLOAT, col, 1, 1, nRows, floatcol, &status);
                break;

            case TDOUBLE:
                for(j=0;j<nRows;j++) doublecol[j] = tempDouble;
                fits_write_col(fitsOut, TDOUBLE, col, 1, 1, nRows, doublecol, &status);
                break;

            case TSTRING:
                for(j=0;j<nRows;j++) strcpy(strcol[j], tempStr);
                fits_write_col(fitsOut, TSTRING, col, 1, 1, nRows, strcol, &status);
                break;

            default:
                break;

        }
    }

    free(int8col);
    free(int16col);
    free(longcol);
    free(floatcol);
    free(doublecol);
    for (uint32_t i=0; i<nRows;i++) free(strcol[i]);
    free(strcol);

    return true;
}

// count number of children with the provided name
static int ippToPspsConfig_countChildren(xmlNode* rootNode, const char* name) {

    int count = 0;
    xmlNode* node = NULL;

    for (node = rootNode->children; node; node = node->next) {

        if (strcmp((const char*)node->name, name)==0) count++;
    } 

    return count;
}

// searches through this table and finds attribute value for a provided key
static bool ippToPspsConfig_getRowAttribute(IppToPspsConfig* this, xmlNode* tableNode, 
        const char* keyName, const char* keyValue, 
        const char* attName, char* attValue) {

    xmlNode* node = NULL;

    char buffer[100];

    // loop round all available rows for this table
    for (node = tableNode->children; node; node = node->next) {
        if (node->type == XML_ELEMENT_NODE) {
            //psLogMsg("ippToPsps", PS_LOG_INFO, "Node name '%s'",  (const char*)node->name );

            if (strcmp((const char*)node->name, "row")==0) {
            //psLogMsg("ippToPsps", PS_LOG_INFO, " Looking for key '%s'",  keyName );

                if (!ippToPspsConfig_getAttribute(node, keyName, buffer)) continue;
            //psLogMsg("ippToPsps", PS_LOG_INFO, "Found key '%s' value '%s'",  keyName, buffer );

                if (strcmp(buffer, keyValue) != 0) continue;
                ippToPspsConfig_getAttribute(node, attName, attValue);
                //printf("FOUND %s %s %s %s \n", keyName, buffer, attName, attValue );
                return true;

            }
        }
    }

    psLogMsg("ippToPsps", PS_LOG_INFO, "Could not find value for '%s' for '%s' with value '%s'", attName, keyName, keyValue );
    return false;
}

// opens an XML file and returns the document
static xmlDoc* ippToPspsConfig_openXmlFile(const char* path) {

    xmlDoc* doc = xmlReadFile(path, NULL, 0);
    if (doc == NULL) psError(PS_ERR_IO, false, "Unable to open XML file at %s", path);
    return doc;
}

// closes an XML file 
static bool ippToPspsConfig_closeXmlFile(xmlDoc* doc) {

    xmlFreeDoc(doc);
    xmlCleanupParser();

    return true;
}

// gets filter ID for this filter name from 'init' data 
static bool ippToPspsConfig_getInitValue(
        IppToPspsConfig* this, 
        const char* tableName,
        const char* keyName, const char* keyValue,
        const char* attName, char* attValue) {

    bool ret = true;

    psString path = NULL;
    psStringAppend(&path, "%s/../init/data.xml", this->configsPath); // TODO nasty

    xmlDoc* doc = ippToPspsConfig_openXmlFile(path);

    if (doc == NULL) {
        psFree(path);
        return false;
    }
    psFree(path);

    xmlNode* rootElement = xmlDocGetRootElement(doc);

    if (strcmp((const char*)rootElement->name, "tabledata")!=0) {
        psError(PS_ERR_IO, false, "Root node of XML is not 'tabledata', as it should be");
        return false;
    }

    xmlNode* node = NULL;
    char tempStr[100];

    // loop round all available tables
    for (node = rootElement->children; node; node = node->next) {
        if (node->type == XML_ELEMENT_NODE) {

            if (strcmp((const char*)node->name, "table")!=0) continue;
            if (!ippToPspsConfig_getAttribute(node, "name", tempStr)) continue;
            if (strcmp(tempStr, tableName)!=0) continue;

            ret = ippToPspsConfig_getRowAttribute(this, node, keyName, keyValue, attName, attValue);
            break;
        }

    }

    ippToPspsConfig_closeXmlFile(doc);

    return ret;
}

// gets filter ID from filter type
bool ippToPspsConfig_getFilterId(IppToPspsConfig* this, const char* filterType, int8_t* filterId) {

    char tmp[2];
    strncpy(tmp,filterType,1);
    tmp[1] = '\0';
    char buffer[10];
    if (!ippToPspsConfig_getInitValue(this, "Filter", "filterType", tmp, "filterID", buffer)) {

        *filterId = -1;
        return false;
    }

    *filterId = atoi(buffer);
    return true;
}

// populate the provided table with data from XML
static bool ippToPspsConfig_populateTable(IppToPspsConfig_Table* table, xmlNode* rootElement, fitsfile *fitsOut) {

    char tempStr[100];
    xmlNode* node = NULL;
    xmlNode* tableNode = NULL;

    // first check we have data for this table in XML file
    for (node = rootElement->children; node; node = node->next) {
        if (node->type == XML_ELEMENT_NODE) {

            if (strcmp((const char*)node->name, "table")!=0) continue;
            if (!ippToPspsConfig_getAttribute(node, "name", tempStr)) continue;
            if (strcmp(tempStr, table->name)==0) {tableNode = node; break;}
        }
    }

    if(!tableNode) {

        psError(PS_ERR_IO,"Could not find table: %s", table->name);
        return false;
    }

    // now count the number of rows
    int nRows = ippToPspsConfig_countChildren(tableNode, "row");
    if (nRows < 1) return false;
    if (!ippToPspsConfig_createTable(table, fitsOut, nRows)) return false;

    int rows=0;
    int status;
    char** strcol = (char**)calloc(1, sizeof(char**));
    strcol[0] = (char*)calloc(32,sizeof(char)); // TODO 20? size issue

    int8_t* bytecol = calloc(1, sizeof(uint8_t));
    int16_t* shortcol = calloc(1, sizeof(uint16_t));
    long* longcol = calloc(1, sizeof(long));
    double* doublecol = calloc(1, sizeof(double));
    float* floatcol = calloc(1, sizeof(float));

    int col;

    // now get rows
    for (node = tableNode->children; node; node = node->next) {

        if (strcmp((const char*)node->name, "row")!=0) continue;

        rows++;

        // get column data for this row
        for (int i=0;i<table->numOfColumns;i++) {

            col = i+1;

            if (!ippToPspsConfig_getAttribute(node, table->columns[i].pspsName, tempStr)) continue;

            strncpy(strcol[0], tempStr, 32);
            status=0;
            //psLogMsg("ippToPsps", PS_LOG_INFO, "Values %s - %ld - %f - %d", strcol[0], longcol[0], doublecol[0], bytecol[0]);
            switch (table->columns[i].pspsType) {

                case TBYTE:
                    bytecol[0] = atoi(tempStr);
                    fits_write_col(fitsOut, table->columns[i].pspsType, col, rows, 1, 1, bytecol, &status); 
                    break;
                case TSHORT:
                    shortcol[0] = atoi(tempStr);
                    fits_write_col(fitsOut, table->columns[i].pspsType, col, rows, 1, 1, shortcol, &status); 
                    break;
                case TLONG:
                case TLONGLONG:
                    longcol[0] = atol(tempStr);
                    fits_write_col(fitsOut, table->columns[i].pspsType, col, rows, 1, 1, longcol, &status); 
                    break;
                case TFLOAT:
                    floatcol[0] = atof(tempStr);
                    fits_write_col(fitsOut, table->columns[i].pspsType, col, rows, 1, 1, floatcol, &status);
                    break;
                case TDOUBLE:
                    doublecol[0] = atof(tempStr);
                    fits_write_col(fitsOut, table->columns[i].pspsType, col, rows, 1, 1, doublecol, &status);
                    break;
                case TSTRING:
                    fits_write_col(fitsOut, table->columns[i].pspsType, col, rows, 1, 1, strcol, &status); 
                    break;

            }
            if (status) psError(PS_ERR_IO, false, "Unable to write value of %s to column %s in table %s", 
                    tempStr, table->columns[i].pspsName, table->name);
        }
    }

    free(bytecol);
    free(shortcol);
    free(longcol);
    free(floatcol);
    free(doublecol);
    free(strcol[0]);
    free(strcol);

    return true;
}

// loads a table description from XML
static bool ippToPspsConfig_loadTableDescription(IppToPspsConfig_Table* table, xmlNode* tableNode) {

    bool ret = true;
    char tableName[100];
    ippToPspsConfig_getAttribute(tableNode, "name", tableName);
    strcpy(table->name, tableName);

    // count how many columns we have coming in and allocate memory for them
    table->numOfColumns = ippToPspsConfig_countChildren(tableNode, "column");
    table->columns = calloc(table->numOfColumns, sizeof(IppToPspsConfig_Column));
    //    psLogMsg("ippToPsps", PS_LOG_INFO, " Found '%d' columns", table->numOfColumns);

    char buffer[200];
    xmlNode* node;
    int columnNum = 0;

    for (node = tableNode->children; node; node = node->next) {

        if (strcmp((const char*)node->name, "column") != 0) continue;

        table->columns[columnNum].usingDefault = true;

        // column name
        ippToPspsConfig_getAttribute(node, "name", buffer);
        sprintf(table->columns[columnNum].pspsName, buffer);

        // column type
        ippToPspsConfig_getAttribute(node, "type", buffer);
        table->columns[columnNum].pspsType = ippToPsps_GetDataType(buffer);

        // default value
        ippToPspsConfig_getAttribute(node, "default", buffer);
        sprintf(table->columns[columnNum].defaultValue, buffer);

        // comment
        ippToPspsConfig_getAttribute(node, "comment", buffer);
        sprintf(table->columns[columnNum].comment, buffer);

        # if 0
        psLogMsg("ippToPsps", PS_LOG_INFO, "'%s'  name='%s' type='%d'  default='%s' comment='%s'", 
                table->name,
                table->columns[columnNum].pspsName, 
                table->columns[columnNum].pspsType, 
                table->columns[columnNum].defaultValue, 
                table->columns[columnNum].comment);
# endif
        columnNum++;
    }   


    if (columnNum < 1) {

        psError(PS_ERR_IO, false, "Found no columns for table '%s'", tableName);
        ret = false;
    }

    if (columnNum != table->numOfColumns)
        psError(PS_ERR_IO, false, "Mismatch between number of columns expected (%d) and those found (%d)", table->numOfColumns, columnNum);

    return ret;
}

// read table shapes from XML file
bool ippToPspsConfig_loadTableDescriptions(IppToPspsConfig* this) {

    bool ret = true;

    psString path = NULL;
    psStringAppend(&path, "%s/tables.xml", this->configsPath);

    xmlDoc* doc = ippToPspsConfig_openXmlFile(path);

    if (doc == NULL) {

        psFree(path);
        return false;
    }
    psFree(path);

    xmlNode* rootElement = xmlDocGetRootElement(doc);

    if (strcmp((const char*)rootElement->name, "tableDescriptions")!=0) {

        psError(PS_ERR_IO, false, "Root node of XML is not 'tableDescriptions', as it should be");
        return false;
    }

    // count how many tables we have coming in and allocate memory for them
    this->numOfTables = ippToPspsConfig_countChildren(rootElement, "table");
    this->tables = calloc(this->numOfTables, sizeof(IppToPspsConfig_Table));
    //    psLogMsg("ippToPsps", PS_LOG_INFO, " Found '%d' tables", this->numOfTables);

    xmlNode* node = NULL;
    IppToPspsConfig_Table* table = NULL;

    int tableNum = 0;
    // loop round all available table descriptions
    for (node = rootElement->children; node; node = node->next) {
        if (node->type == XML_ELEMENT_NODE) {

            if (strcmp((const char*)node->name, "table")==0) {

                table = this->tables+tableNum;

                if (!ippToPspsConfig_loadTableDescription(table, node)) ret = false;
                else tableNum++;
            }
        }
    }

    if (tableNum != this->numOfTables)
        psError(PS_ERR_IO, false, "Mismatch between number of tables expected (%d) and those found (%d)", this->numOfTables, tableNum);

    ippToPspsConfig_closeXmlFile(doc);

    return ret;
}

// loads mappings from XML for a particular table
static bool ippToPspsConfig_loadTableMappings(IppToPspsConfig* this, xmlNode* tableNode) {

    bool ret = false;

    xmlNode* node = NULL;

    char tableName[100];
    ippToPspsConfig_getAttribute(tableNode, "name", tableName);

    IppToPspsConfig_Table* table = ippToPspsConfig_findTable(this, tableName);
    IppToPspsConfig_Column* column = NULL;
    if (!table) return false;

    char buffer[100];
    //psLogMsg("ippToPsps", PS_LOG_INFO, "Table '%s'", tableName);

    // loop round all available mappings for this table
    for (node = tableNode->children; node; node = node->next) {
        if (node->type == XML_ELEMENT_NODE) {

            if (strcmp((const char*)node->name, "map")==0) {

                // column type
                ippToPspsConfig_getAttribute(node, "pspsName", buffer);
                column = ippToPspsConfig_findColumn(table, buffer);

                if (!column) continue;

                // IPP name
                ippToPspsConfig_getAttribute(node, "ippName", buffer);
                strcpy(column->ippName, buffer);

                // IPP type
                ippToPspsConfig_getAttribute(node, "ippType", buffer);
                column->ippType = ippToPsps_GetDataType(buffer); 

                // IPP column
                ippToPspsConfig_getAttribute(node, "ippColumn", buffer);
                column->ippColNum = atoi(buffer);

                column->usingDefault = false;

                //psLogMsg("ippToPsps", PS_LOG_INFO, "...mapping PSPS:'%s' to '%s'", column->pspsName, column->ippName );
            }
        }
    }

    return ret;
}

// reads table mappings from XML file
static bool ippToPspsConfig_loadMappings(IppToPspsConfig* this) {

    bool ret = true;

    psString path = NULL;
    psStringAppend(&path, "%s/map.xml", this->configsPath);

    xmlDoc* doc = ippToPspsConfig_openXmlFile(path);

    if (doc == NULL) {
        psFree(path);
        return false;
    }
    psFree(path);

    xmlNode* rootElement = xmlDocGetRootElement(doc);

    if (strcmp((const char*)rootElement->name, "tabledata")!=0) {
        psError(PS_ERR_IO, false, "Root node of XML is not 'tabledata', as it should be");
        return false;
    }

    xmlNode* node = NULL;

    // loop round all available tables
    for (node = rootElement->children; node; node = node->next) {
        if (node->type == XML_ELEMENT_NODE) {

            if (strcmp((const char*)node->name, "table")==0) {

                if (!ippToPspsConfig_loadTableMappings(this, node)) ret = false;
            }
        }
    }

    ippToPspsConfig_closeXmlFile(doc);

    return ret;
}

// populate the provided table with data from XML
bool ippToPspsConfig_populateFromFile(IppToPspsConfig* this, fitsfile *fitsOut) {

    bool ret = true;

    psString path = NULL;
    psStringAppend(&path, "%s/data.xml", this->configsPath);

    xmlDoc* doc = ippToPspsConfig_openXmlFile(path);

    if (doc == NULL) {
        psFree(path);
        return false;
    }
    psFree(path);

    xmlNode* rootElement = xmlDocGetRootElement(doc);

    if (strcmp((const char*)rootElement->name, "tabledata")!=0) {
        psError(PS_ERR_IO, false, "Root node of XML is not 'tabledata', as it should be");
        return false;
    }

    for (uint32_t i=0; i<this->numOfTables; i++) {
        if (!ippToPspsConfig_populateTable(this->tables+i, rootElement, fitsOut)) {
            ret = false;
        }
    }

    ippToPspsConfig_closeXmlFile(doc);

    return ret;
}

// populate with data from another FITS table into this one
static bool ippToPspsConfig_populateTableFromFits(
        IppToPspsConfig_Table* table,
        fitsfile *fitsIn, 
        fitsfile *fitsOut,
        const long nRows, 
        const bool fromHeader) {

    if (!table) return false;
    if (!fitsIn) return false;

    int8_t* int8col = (int8_t*)calloc(nRows, sizeof(int8_t));
    int16_t* int16col = (int16_t*)calloc(nRows, sizeof(int16_t));
    long* longcol = (long*)calloc(nRows, sizeof(long));
    float* floatcol = (float*)calloc(nRows, sizeof(float));
    double* doublecol = (double*)calloc(nRows, sizeof(double));
    char** strcol = (char**)calloc(nRows, sizeof(char**));
    for (uint32_t i=0; i<nRows;i++)
        strcol[i] = (char*)calloc(50,sizeof(char)); // TODO 20? size issue

    int8_t int8null = -99;
    int16_t int16null = -999;
    long longnull = -999;
    float floatnull = -999.0;
    double doublenull = -999.0;

    int anynull = 0;
    int readStatus = 0;
    int writeStatus = 0;


    int col;
    for (uint32_t i=0; i<table->numOfColumns; i++) {

        readStatus = 0;
        writeStatus = 0;

        if (!table->columns[i].usingDefault) {

            col = i+1;

            switch (table->columns[i].ippType) {

                case TBYTE:
                    if (fromHeader) {
                        fits_read_key(fitsIn, TBYTE, table->columns[i].ippName, &int8col[0], NULL, &readStatus);
                        if (!isfinite(int8col[0])) int8col[0] = int8null;
                    }
                    else fits_read_col(fitsIn, TBYTE, table->columns[i].ippColNum, 1, 1, nRows, &int8null, int8col, &anynull, &readStatus);
                    fits_write_col(fitsOut, TBYTE, col, 1, 1, nRows, int8col, &writeStatus);
                    break;
                case TSHORT:
                    if (fromHeader) {
                        fits_read_key(fitsIn, TSHORT, table->columns[i].ippName, &int16col[0], NULL, &readStatus);
                        if (!isfinite(int16col[0])) int16col[0] = int16null;
                    }
                    else fits_read_col(fitsIn, TSHORT, table->columns[i].ippColNum, 1, 1, nRows, &int16null, int16col, &anynull, &readStatus);
                    fits_write_col(fitsOut, TSHORT, col, 1, 1, nRows, int16col, &writeStatus);
                    break;
                case TLONG:
                    if (fromHeader) {
                        fits_read_key(fitsIn, TLONG, table->columns[i].ippName, &longcol[0], NULL, &readStatus);
                        if (!isfinite(longcol[0])) longcol[0] = longnull;
                    }
                    else fits_read_col(fitsIn, TLONG, table->columns[i].ippColNum, 1, 1, nRows, &longnull, longcol, &anynull, &readStatus);
                    fits_write_col(fitsOut, TLONG, col, 1, 1, nRows, longcol, &writeStatus);
                    break;
                case TLONGLONG:
                    if (fromHeader) {
                        fits_read_key(fitsIn, TLONGLONG, table->columns[i].ippName, &longcol[0], NULL, &readStatus);
                        if (!isfinite(longcol[0])) longcol[0] = longnull;
                    }
                    else fits_read_col(fitsIn, TLONGLONG, table->columns[i].ippColNum, 1, 1, nRows, &longnull, longcol, &anynull, &readStatus);
                    fits_write_col(fitsOut, TLONGLONG, col, 1, 1, nRows, longcol, &writeStatus);
                    break;
                case TFLOAT:
                    if (fromHeader) {
                        fits_read_key(fitsIn, TFLOAT, table->columns[i].ippName,&floatcol[0], NULL, &readStatus); 
                        if (!isfinite(floatcol[0])) floatcol[0] = floatnull;
                    }
                    else fits_read_col(fitsIn, TFLOAT, table->columns[i].ippColNum, 1, 1, nRows, &floatnull, floatcol, &anynull, &readStatus);
                    fits_write_col(fitsOut, TFLOAT, col, 1, 1, nRows, floatcol, &writeStatus);
                    break;
                case TDOUBLE:
                    if (fromHeader) {
                        fits_read_key(fitsIn, TDOUBLE, table->columns[i].ippName, &doublecol[0], NULL, &readStatus);
                        if (!isfinite(doublecol[0])) doublecol[0] = doublenull;
                    }
                    else fits_read_col(fitsIn, TDOUBLE, table->columns[i].ippColNum, 1, 1, nRows, &doublenull, doublecol, &anynull, &readStatus);
                    fits_write_col(fitsOut, TDOUBLE, col, 1, 1, nRows, doublecol, &writeStatus);
                    break;
                case TSTRING:
                    if (fromHeader) fits_read_key(fitsIn, TSTRING, table->columns[i].ippName, strcol[0], NULL, &readStatus);
                    else fits_read_col(fitsIn, TSTRING, table->columns[i].ippColNum, 1, 1, nRows, " ", strcol, &anynull, &readStatus);
                    fits_write_col(fitsOut, TSTRING, col, 1, 1, nRows, strcol, &writeStatus);
                    break;

                default:
                    psError(PS_ERR_IO, false, "Don't know IPP type (%d) for mapping from  '%d'  '%s' to '%s'", table->columns[i].ippType,
                            table->columns[i].ippColNum, table->columns[i].ippName, table->columns[i].pspsName);
                    break;
            }

            // TODO need these errors, but strange error handling runs out of memory
            //if (readStatus) {
            //    psError(PS_ERR_IO, false, "Unable to read col num '%d' col name '%s', type %d", 
            //        table->columns[i].ippColNum, table->columns[i].ippName, table->columns[i].ippType);
            //    fits_report_error(stderr, readStatus);
            //}
            //if (writeStatus) {
            //    psError(PS_ERR_IO, false, "Unable to write col '%s'", table->columns[i].pspsName );
            //    fits_report_error(stderr, writeStatus);
            //}
        }
    }

    free(int8col);
    free(int16col);
    free(longcol);
    free(floatcol);
    free(doublecol);
    for (uint32_t i=0; i<nRows;i++) free(strcol[i]);
    free(strcol);

    return true;
}

// creates and populates a table
bool ippToPspsConfig_writeTable(
        IppToPspsConfig* this, 
        fitsfile* fitsIn,
        fitsfile* fitsOut,
        const long nRows,
        const char* tableName,
        const bool fromHeader) {


    IppToPspsConfig_Table* table = ippToPspsConfig_findTable(this, tableName);
    if (!table) return false;

    if(!ippToPspsConfig_createTable(table, fitsOut, nRows)) return false;
    return ippToPspsConfig_populateTableFromFits(table, fitsIn, fitsOut, nRows, fromHeader);
}

// Destructor.
void ippToPspsConfig_Destructor(IppToPspsConfig* this) {

    if (this != NULL ) {

        // TODO check logic here
        for (int i=0;i<this->numOfTables;i++)
            free(this->tables[i].columns);

        free(this->tables);
        psFree(this->configsPath);
        free(this);
    }
}

// Constructor. Loads IPP -> PSPS mappings from a csv
IppToPspsConfig* ippToPspsConfig_Constructor(const char* path) {

    IppToPspsConfig* this = NULL;

    this = malloc(sizeof(IppToPspsConfig));

    this->configsPath = NULL;
    psStringAppend(&this->configsPath, path);

    // load tables descriptions from XML
    if (ippToPspsConfig_loadTableDescriptions(this)) {

        // load any mappings we may have from XML        
        ippToPspsConfig_loadMappings(this);
    }

    return this;
}

