/** @file  psFitsTable.c
 *
 *  @brief Contains Fits I/O routines
 *
 *  @ingroup FileIO
 *
 *  @author Robert DeSonia, MHPCC
 *
 *  @version $Revision: 1.25 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2007-02-07 23:52:53 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "psFits.h"
#include "string.h"
#include "psError.h"

#include "psImageStructManip.h"
#include "psMemory.h"
#include "psString.h"
#include "psLogMsg.h"
#include "psTrace.h"
#include "psVector.h"
#include "psFitsTable.h"
#include "psFitsHeader.h"
#include "psAbort.h"
#include "psAssert.h"

#define MAX_STRING_LENGTH 256  // maximum length string for FITS routines


psMetadata* psFitsReadTableRow(const psFits* fits,
                               int row)
{
    long numRows;
    int numCols;
    int status = 0;

    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("The input psFits object can not NULL."));
        return NULL;
    }

    // check to see if we even are positioned on a table HDU
    int hdutype;
    fits_get_hdu_type(fits->fd,&hdutype, &status);
    if ( status != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Could not determine the HDU type. CFITSIO Error: %s"),
                fitsErr);
        return NULL;
    }
    if (hdutype != ASCII_TBL && hdutype != BINARY_TBL) {
        psError(PS_ERR_IO, true,
                _("Current FITS HDU type must be a table."));
        return NULL;
    }

    // get the size of the FITS table
    fits_get_num_rows(fits->fd, &numRows, &status);
    fits_get_num_cols(fits->fd, &numCols, &status);
    if ( status != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Failed to determine the size of the current HDU table. CFITSIO Error: %s"),
                fitsErr);
        return NULL;
    }

    psTrace("psLib.fits",5,"Table size is %ix%li\n",numCols, numRows);
    // the row parameter in the proper range?
    if (row < 0 || row >= numRows) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                _("Specified row, %d, is not valid for current table of %ld rows."),
                row, numRows);
        return NULL;
    }

    psMetadata* data = psMetadataAlloc();

    int typecode;
    long repeat;
    long width;
    char name[60];
    for (int col = 1; col <= numCols; col++) {
        // get the column name
        if (hdutype == BINARY_TBL) {
            fits_get_bcolparms(fits->fd, col, name,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, &status);
        } else {
            fits_get_acolparms(fits->fd, col, name,
                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, &status);
        }
        // get the column type
        fits_get_coltype(fits->fd, col, &typecode, &repeat, &width, &status);

        if (status == 0) {

            #define READ_TABLE_ROW_CASE(FITSTYPE, NATIVETYPE, TYPE, VECTYPE) \
        case FITSTYPE: { \
                if (repeat == 1) { \
                    NATIVETYPE value; \
                    int anynul = 0; \
                    fits_read_col(fits->fd, FITSTYPE, col,row+1, \
                                  1, 1, NULL, &value, &anynul, &status); \
                    psTrace("psLib.fits",5,"Column #%i, '%s', is type %i, repeat %li, Value = %g\n", \
                            col, name, typecode, repeat, (double)value); \
                    psMetadataAdd(data,PS_LIST_TAIL, name, \
                                  PS_DATA_##TYPE, \
                                  "", (ps##TYPE)value); \
                } else { \
                    NATIVETYPE* value = psAlloc(sizeof(NATIVETYPE)*repeat); \
                    psVector* vec = psVectorAlloc(repeat,PS_TYPE_##VECTYPE); \
                    int anynul = 0; \
                    fits_read_col(fits->fd, FITSTYPE, col,row+1, \
                                  1, repeat, NULL, value, &anynul, &status); \
                    for (int lcv = 0; lcv < repeat; lcv++) { \
                        vec->data.VECTYPE[lcv] = value[lcv]; \
                    } \
                    psMetadataAdd(data,PS_LIST_TAIL, name, PS_DATA_VECTOR, "", vec); \
                    psFree(value); \
                    psFree(vec); \
                } \
                break; \
            }

            switch (typecode) {
            case TBYTE:
            case TSHORT:
            case TLONGLONG:
                READ_TABLE_ROW_CASE(TLONG, long, S32,S32)
                READ_TABLE_ROW_CASE(TFLOAT, float, F32,F32)
                READ_TABLE_ROW_CASE(TDOUBLE, double, F64,F64)
                READ_TABLE_ROW_CASE(TLOGICAL, bool, BOOL,S8);
            case TSTRING: {
                    char* value = psAlloc(repeat+1);
                    int anynul = 0;
                    fits_read_col(fits->fd, TSTRING, col,row+1,
                                  1, 1, NULL, &value, &anynul, &status);
                    psTrace("psLib.fits",5,"Column #%i, '%s', is type %i, repeat %li, value = %s\n",
                            col, name, typecode, repeat, value);
                    if (anynul == 0) {
                        psMetadataAdd(data,PS_LIST_TAIL, name,
                                      PS_DATA_STRING,
                                      "", value);
                    }
                    psFree(value);
                    break;
                }
            default:
                psWarning("Data type (%d) not supportted for column %d",
                          typecode, col);

                psTrace("psLib.fits", 2,
                        "Column %d or row %d was of a non primitive type, %d",
                        col, row, typecode);
            }
        }

        if ( status != 0) {
            char fitsErr[MAX_STRING_LENGTH];
            (void)fits_get_errstatus(status, fitsErr);
            psError(PS_ERR_IO, true,
                    _("Failed to retrieve table element (%d,%d). CFITSIO Error: %s"),
                    col,row,fitsErr);
            psFree(data);
            return NULL;
        }

    }

    return data;
}

psArray* psFitsReadTableColumn(const psFits* fits,
                               const char* colname)
{
    int colnum = 0;
    int status = 0;

    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("The input psFits object can not NULL."));
        return NULL;
    }

    // check to see if we even are positioned on a table HDU
    int hdutype;
    if ( fits_get_hdu_type(fits->fd,&hdutype, &status) != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Could not determine the HDU type. CFITSIO Error: %s"),
                fitsErr);
        return NULL;
    }
    if (hdutype != ASCII_TBL && hdutype != BINARY_TBL) {
        psError(PS_ERR_IO, true,
                _("Current FITS HDU type must be a table."));
        return NULL;
    }

    // find the column by name
    if ( fits_get_colnum(fits->fd, CASESEN, (char*)colname, &colnum, &status) != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Specified column, %s, was not found. CFITSIO Error: %s"),
                colname, fitsErr);
        return NULL;
    }

    // get the number of rows
    long numRows = 0;
    fits_get_num_rows(fits->fd, &numRows, &status);

    // get the column length.
    int width;
    if ( fits_get_col_display_width(fits->fd, colnum, &width, &status) != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Could not determine the datatype of the table column. CFITSIO Error: %s"),
                fitsErr);
        return NULL;
    }

    // allocate the buffers
    psArray* result = psArrayAlloc(numRows);
    for (int row = 0; row < numRows; row++) {
        result->data[row] = psAlloc((width+1)*sizeof(char));
    }

    fits_read_col_str(fits->fd,
                      colnum,
                      1, // firstrow
                      1, // firestelem
                      numRows,
                      "", // nulstr
                      (char**)result->data,
                      NULL,
                      &status);

    if ( status != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Failed to read table column. CFITSIO Error: %s"),
                fitsErr);
        return NULL;
    }

    return result;
}

psVector* psFitsReadTableColumnNum(const psFits* fits,
                                   const char* colname)
{
    int status = 0;
    int colnum = 0;

    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("The input psFits object can not NULL."));
        return NULL;
    }

    // check to see if we even are positioned on a table HDU
    int hdutype;
    if ( fits_get_hdu_type(fits->fd,&hdutype, &status) != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Could not determine the HDU type. CFITSIO Error: %s"),
                fitsErr);
        return NULL;
    }
    if (hdutype != ASCII_TBL && hdutype != BINARY_TBL) {
        psError(PS_ERR_IO, true,
                _("Current FITS HDU type must be a table."));
        return NULL;
    }

    // find the column by name
    if ( fits_get_colnum(fits->fd, CASESEN, (char*)colname, &colnum, &status) != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Specified column, %s, was not found. CFITSIO Error: %s"),
                colname, fitsErr);
        return NULL;
    }

    // get the number of rows
    long numRows = 0;
    fits_get_num_rows(fits->fd,
                      &numRows,
                      &status);

    // get the column datatype.
    int typecode;
    long repeat;
    long width;
    if ( fits_get_eqcoltype(fits->fd, colnum, &typecode, &repeat, &width, &status) != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Could not determine the datatype of the table column. CFITSIO Error: %s"),
                fitsErr);
        return NULL;
    }

    psVector* result = psVectorAlloc(numRows, p_psFitsTypeFromCfitsio(typecode));

    fits_read_col(fits->fd,
                  typecode,
                  colnum,
                  1 /* firstrow */,
                  1 /* firstelem */,
                  numRows,
                  NULL,
                  (psPtr)(result->data.U8),
                  NULL,
                  &status);

    if ( status != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Failed to read table column. CFITSIO Error: %s"),
                fitsErr);
        return NULL;
    }

    return result;
}


psArray* psFitsReadTable(const psFits* fits)
{
    int status = 0;

    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("The input psFits object can not NULL."));
        return NULL;
    }

    // check to see if we even are positioned on a table HDU
    int hdutype;
    if ( fits_get_hdu_type(fits->fd,&hdutype, &status) != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Could not determine the HDU type. CFITSIO Error: %s"),
                fitsErr);
        return NULL;
    }
    if (hdutype != ASCII_TBL && hdutype != BINARY_TBL) {
        psError(PS_ERR_IO, true,
                _("Current FITS HDU type must be a table."));
        return NULL;
    }

    // get the size of the FITS table
    long numRows = 0;
    fits_get_num_rows(fits->fd, &numRows, &status);
    if ( status != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Failed to determine the size of the current HDU table. CFITSIO Error: %s"),
                fitsErr);
        return NULL;
    }


    psArray* table = psArrayAlloc(numRows);

    for (int row = 0; row < numRows; row++) {
        psTrace("psLib.fits",5,"Reading row %i of %li\n",row, numRows);
        table->data[row] = psFitsReadTableRow(fits,row);
    }

    return table;
}

bool psFitsWriteTable(psFits* fits,
                      const psMetadata* header,
                      const psArray* table,
                      const char *extname)
{
    psFitsMoveLast(fits);
    return psFitsInsertTable(fits, header, table, extname, true);
}


// Return the size of the column
static inline size_t columnSize(const psMetadataItem *item // Item for which to get the size
                               )
{
    if (PS_DATA_IS_PRIMITIVE(item->type)) {
        return 1;
    }
    switch (item->type) {
    case PS_DATA_STRING:
        return strlen(item->data.V);
    case PS_DATA_VECTOR: {
            psVector *vector = item->data.V;
            return vector->n;
        }
    default:
        psAbort("Shouldn't ever get here.");
    }
    return 0;
}

// Get the TFORM character, given a PS type
static inline char getTForm(psDataType type)
{
    switch (type) {
    case PS_TYPE_U8:
    case PS_TYPE_S8:
        return 'B';
    case PS_TYPE_S16:
        return 'I';
    case PS_TYPE_S32:
        return 'J';
    case PS_TYPE_U64:
    case PS_TYPE_S64:
        return 'K';
    case PS_TYPE_U16:
        return 'U';
    case PS_TYPE_U32:
        return 'V';
    case PS_TYPE_F32:
        return 'E';
    case PS_TYPE_F64:
        return 'D';
    case PS_TYPE_C32:
        return 'C';
    case PS_TYPE_C64:
        return 'M';
    case PS_TYPE_BOOL:
        return 'L';
    case PS_DATA_STRING:
        return 'A';
    default:
        psError(PS_ERR_UNKNOWN, true, "Unknown type: %x\n", type);
        return '?';
    }
}


// Column specification
// Included here, because there's no need for the user to have access to it
typedef struct
{
    psDataType type;                    // psLib type (e.g., PS_DATA_STRING or PS_TYPE_F32)
    size_t size;                        // Size (number of repeats)
    psElemType vectorType;              // psLib type of vectors
}
colSpec;


bool psFitsInsertTable(psFits* fits,
                       const psMetadata* header,
                       const psArray* table,
                       const char *extname,
                       bool after)
{
    int status = 0;

    PS_ASSERT_PTR_NON_NULL(fits, false);
    PS_ASSERT_ARRAY_NON_NULL(table, false);

    long numRows = table->n;
    if (numRows < 1) {
        // no table data, what can I do?
        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
                _("Can't create a table without any rows."));
        return false;
    }

    // Find the unique items in the array of metadata 'rows', and their sizes
    psMetadata *colSpecs = psMetadataAlloc(); // Column specifications
    size_t rowSize = 0;                 // Size (in bytes) of each row
    for (long i = 0; i < numRows; i++) {
        psMetadata* row = table->data[i];
        if (!row) {
            continue;
        }
        psMetadataIterator *rowIter = psMetadataIteratorAlloc(row, PS_LIST_HEAD, NULL); // Iterator
        psMetadataItem *colItem;        // Column item, from iteration
        while ((colItem = psMetadataGetAndIncrement(rowIter))) {
            if (!(PS_DATA_IS_PRIMITIVE(colItem->type) || colItem->type == PS_DATA_STRING ||
                    colItem->type == PS_DATA_VECTOR)) {
                // unsupported type -- treating as an error
                psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                        "Unsupported data type (%d) for Metadata Item '%s' in row %ld.",
                        colItem->type, colItem->name, i);
                psFree(rowIter);
                psFree(colSpecs);
                return false;
            }

            size_t size = columnSize(colItem); // Size for this column

            // Check to see if we know about this one; or update the size if required
            psMetadataItem *colSpecItem = psMetadataLookup(colSpecs, colItem->name);
            if (!colSpecItem) {
                // A new one!
                colSpec *spec = psAlloc(sizeof(colSpec)); // Specification for this column
                spec->type = colItem->type;
                spec->size = size;
                if (colItem->type == PS_DATA_VECTOR) {
                    psVector *vector = colItem->data.V; // The vector
                    spec->vectorType = vector->type.type;
                }
                psMetadataAddPtr(colSpecs, PS_LIST_TAIL, colItem->name, PS_DATA_UNKNOWN, "", spec);
                psFree(spec);           // Drop reference
                rowSize += PSELEMTYPE_SIZEOF(spec->type);
            } else {
                colSpec *spec = colSpecItem->data.V; // The specification
                if (size > spec->size) {
                    spec->size = size;
                }
                if (colItem->type != spec->type) {
                    psLogMsg(__func__, PS_LOG_WARN, "Differing type found for column %s: %x vs %x --- "
                             "using the first found.\n", colSpecItem->name, colItem->type, spec->type);
                }
                if (colItem->type == PS_DATA_VECTOR) {
                    psVector *vector = colItem->data.V; // The vector
                    if (vector->type.type != spec->vectorType) {
                        psLogMsg(__func__, PS_LOG_WARN, "Differing vector type found for column %s: %x vs %x"
                                 "--- using the first found.\n", colSpecItem->name, vector->type.type,
                                 spec->vectorType);
                    }
                }
            }
        }
        psFree(rowIter);
    }

    long numColumns = colSpecs->list->n;// Number of columns
    if (numColumns == 0) {
        // No table columns found
        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
                "Did not find any column data to write to a table.");
        psFree(colSpecs);
        return false;
    }

    // Create array of column names and types.
    psArray *columnNames = psArrayAlloc(numColumns); // Array of column names, for cfitsio
    psArray *columnTypes = psArrayAlloc(numColumns); // Array of column types, for cfitsio
    psMetadataIterator *colSpecsIter = psMetadataIteratorAlloc(colSpecs, PS_LIST_HEAD, NULL); // Iterator
    psMetadataItem *colSpecItem;        // Column specification item, from iteration
    for (long i = 0; (colSpecItem = psMetadataGetAndIncrement(colSpecsIter)); i++) {
        colSpec *spec = colSpecItem->data.V; // The specification
        columnNames->data[i] = psMemIncrRefCounter(colSpecItem->name);
        psString colType = NULL;        // The column type
        if (spec->type == PS_DATA_VECTOR) {
            psStringAppend(&colType, "%zd%c", spec->size, getTForm(spec->vectorType));
        } else {
            psStringAppend(&colType, "%zd%c", spec->size, getTForm(spec->type));
        }
        columnTypes->data[i] = colType;
    }

    // Create the table HDU
    int numHDUs = psFitsGetSize(fits);  // Number of HDUs in file
    if (numHDUs == 0) {
        // We're creating the first extension
        fits_create_tbl(fits->fd,
                        BINARY_TBL,
                        table->n, // number of rows in table
                        numColumns, // number of columns in table
                        (char**)columnNames->data, // names of the columns
                        (char**)columnTypes->data, // format of the columns
                        NULL, // physical unit of columns
                        (char*)extname, // extension name; casting away const because cfitsio is horrible
                        &status);
    } else {
        if (!after) {
            if (psFitsGetExtNum(fits) == 0) {
                // We're creating a replacement primary HDU.
                // Set status to signal fits_insert_img to insert a new primary HDU
                status = PREPEND_PRIMARY;
            } else {
                // Move back one to perform an insert after the previous HDU
                psFitsMoveExtNum(fits, -1, true);
            }
        }
        // Insert the table
        fits_insert_btbl(fits->fd,
                         table->n, // number of rows in table
                         numColumns, // number of columns in table
                         (char**)columnNames->data, // names of the columns
                         (char**)columnTypes->data, // format of the columns
                         NULL, // physical unit of columns
                         (char*)extname, // extension name; casting away const because cfitsio is horrible
                         0, &status);
    }
    psFree(columnNames);
    psFree(columnTypes);

    if (status != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_LOCATION_INVALID, true, "Unable to create FITS table with %ld columns and %ld rows: %s",
                numColumns, table->n, fitsErr);
        psFree(colSpecsIter);
        psFree(colSpecs);
        return false;
    }

    // Write header
    if (header && !psFitsWriteHeader(fits, header)) {
        psError(PS_ERR_IO, false, "Unable to write FITS header.\n");
        psFree(colSpecsIter);
        psFree(colSpecs);
        return false;
    }


    // cfitsio requires that we write the data by columns --- urgh!
    psMetadataIteratorSet(colSpecsIter, PS_LIST_HEAD);
    for (long colNum = 1; (colSpecItem = psMetadataGetAndIncrement(colSpecsIter)); colNum++) {
        // Note: colNum is unit-indexed, because it's for cfitsio
        colSpec *spec = colSpecItem->data.V; // The specification
        if (PS_DATA_IS_PRIMITIVE(spec->type)) {
            size_t dataSize = PSELEMTYPE_SIZEOF(spec->type); // Size (in bytes) of this type
            psVector *columnData = psVectorAlloc(table->n, spec->type); // The raw row data, to be written
            psVectorInit(columnData, 0);
            for (long i = 0; i < table->n; i++) {
                psMetadata *row = table->data[i]; // The row of interest
                psMetadataItem *dataItem = psMetadataLookup(row, colSpecItem->name); // The value of interest
                memcpy(&columnData->data.U8[i * dataSize], &dataItem->data, dataSize);
            }

            int fitsDataType;           // Data type for cfitsio
            p_psFitsTypeToCfitsio(spec->type, NULL, NULL, &fitsDataType);
            fits_write_col(fits->fd,
                           fitsDataType,
                           colNum, // column number
                           1, // first row
                           1, // first element
                           table->n, // number of rows
                           columnData->data.U8, // the data
                           &status);
            psFree(columnData);
        } else {
            switch (spec->type) {
            case PS_DATA_STRING: {
                    psArray *strings = psArrayAlloc(table->n); // Array of strings
                    for (long i = 0; i < table->n; i++) {
                        psMetadata *row = table->data[i]; // The row of interest
                        strings->data[i] = psMemIncrRefCounter(psMetadataLookupStr(NULL, row,
                                                               colSpecItem->name));
                    }
                    fits_write_col_str(fits->fd, colNum, 1, 1, table->n, (char**)strings->data, &status);
                    psFree(strings);
                    break;
                }
            case PS_DATA_VECTOR: {
                    size_t dataSize = PSELEMTYPE_SIZEOF(spec->vectorType); // Size of data, in bytes
                    psVector *columnData = psVectorAlloc(spec->size * table->n * dataSize, PS_TYPE_U8);
                    psVectorInit(columnData, 0);
                    for (long i = 0; i < table->n; i++) {
                        psMetadata *row = table->data[i]; // The row of interest
                        psMetadataItem* dataItem = psMetadataLookup(row, colSpecItem->name);
                        if (dataItem->type != PS_DATA_VECTOR) {
                            // Just in case --- get a zero instead of some weird result
                            continue;
                        }
                        psVector *vector = dataItem->data.V;
                        memcpy(&columnData->data.U8[i * dataSize * spec->size], vector->data.U8,
                               vector->n * dataSize);
                    }

                    int fitsDataType;           // Data type for cfitsio
                    p_psFitsTypeToCfitsio(spec->vectorType, NULL, NULL, &fitsDataType);
                    fits_write_col(fits->fd, fitsDataType, colNum, 1, 1, table->n * spec->size,
                                   columnData->data.U8, &status);
                    psFree(columnData);
                    break;
                }
            default:
                psAbort("Should never get here.\n");
            }
        }

        // Check error status from writing column
        if (status != 0) {
            char fitsErr[MAX_STRING_LENGTH];
            fits_get_errstatus(status, fitsErr);
            psError(PS_ERR_LOCATION_INVALID, true, "Unable to write column %ld of FITS table: %s",
                    colNum, fitsErr);
            psFree(colSpecsIter);
            psFree(colSpecs);
            return false;
        }
    }

    psFree(colSpecsIter);
    psFree(colSpecs);

    return true;
}

bool psFitsUpdateTable(psFits* fits,
                       const psMetadata* data,
                       int row)
{
    int status = 0;

    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("The input psFits object can not NULL."));
        return false;
    }

    if (data == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("The input psImage was NULL.  Need a non-NULL psImage for operation to be performed."));
        return false;
    }

    // check to see if we even are positioned on a table HDU
    int hdutype;
    if ( fits_get_hdu_type(fits->fd,&hdutype, &status) != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                _("Could not determine the HDU type. CFITSIO Error: %s"),
                fitsErr);
        return false;
    }
    if (hdutype != ASCII_TBL && hdutype != BINARY_TBL) {
        psError(PS_ERR_IO, true,
                _("Current FITS HDU type must be a table."));
        return false;
    }

    psMetadataIterator* iter = psMetadataIteratorAlloc((psPtr)data,PS_LIST_HEAD,NULL);

    psMetadataItem* item;

    while ( (item=psMetadataGetAndIncrement(iter)) != NULL) {
        if (PS_DATA_IS_PRIMITIVE(item->type) ||
                item->type == PS_DATA_BOOL ||
                item->type == PS_DATA_STRING) {
            // operating on primitive data type or string, i.e., not a complex object
            int colnum = 0;

            if ( fits_get_colnum(fits->fd, CASESEN, item->name, &colnum, &status) == 0) {
                // cooresponding column found in table
                int dataType;
                p_psFitsTypeToCfitsio(item->type, NULL, NULL, &dataType);

                if (fits_write_col(fits->fd, dataType, colnum, row+1, 1, 1, &item->data,&status) != 0) {
                    char fitsErr[MAX_STRING_LENGTH];
                    (void)fits_get_errstatus(status, fitsErr);
                    psError(PS_ERR_IO, true,
                            _("Could not write data to file. CFITSIO Error: %s"),
                            fitsErr);
                    psFree(iter);
                    return false;
                }
            } else {
                // the column was not found.
                psWarning("No column with the name '%s' exists in the table.",
                          item->name);
            }
        }
    }

    psFree(iter);

    return true;
}
