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

#include "psFits.h"
#include "string.h"
#include "psError.h"
#include "psErrorText.h"
#include "psImageStructManip.h"
#include "psMemory.h"
#include "psString.h"
#include "psLogMsg.h"
#include "psTrace.h"
#include "psVector.h"
#include "config.h"

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

static psElemType convertFitsToPsType(int datatype)
{
    switch (datatype) {
    case TBYTE:
        return PS_TYPE_U8;
    case TSBYTE:
        return PS_TYPE_S8;
    case TSHORT:
        return PS_TYPE_S16;
    case TUSHORT:
        return PS_TYPE_U16;
    case TLONG:
        if (sizeof(long) == 8) {
            return PS_TYPE_S64;
        }
        // no break
    case TINT:
        return PS_TYPE_S32;
    case TULONG:
        if (sizeof(unsigned long) == 8) {
            return PS_TYPE_U64;
        }
        // no break
    case TUINT:
        return PS_TYPE_U32;
    case TLONGLONG:
        return PS_TYPE_S64;
    case TFLOAT:
        return PS_TYPE_F32;
    case TDOUBLE:
        return PS_TYPE_F64;
    case TCOMPLEX:
        return PS_TYPE_C32;
    case TDBLCOMPLEX:
        return PS_TYPE_C64;
    case TLOGICAL:
        return PS_TYPE_BOOL;
    default:
        psError(PS_ERR_IO, true,
                "Unknown FITS datatype, %d.",
                datatype);
        return 0;
    }
}

static bool convertPsTypeToFits(int type, int* bitPix, double* bZero, int* dataType)
{

    int bitpix;
    int datatype;
    double bzero = 0.0;

    switch (type) {

    case PS_TYPE_U8:
        bitpix = BYTE_IMG;
        datatype = TBYTE;
        break;

    case PS_TYPE_S8:
        bitpix = BYTE_IMG;
        bzero = INT8_MIN;
        datatype = TSBYTE;
        break;

    case PS_TYPE_U16:
        bitpix = SHORT_IMG;
        bzero = -1.0 * INT16_MIN;
        datatype = TUSHORT;
        break;

    case PS_TYPE_S16:
        bitpix = SHORT_IMG;
        datatype = TSHORT;
        break;

    case PS_TYPE_U32:
        bitpix = LONG_IMG;
        bzero = -1.0 * INT32_MIN;
        datatype = TUINT;
        break;

    case PS_TYPE_S32:
        bitpix = LONG_IMG;
        datatype = TINT;
        break;

    case PS_TYPE_F32:
        bitpix = FLOAT_IMG;
        datatype = TFLOAT;
        break;

    case PS_TYPE_F64:
        bitpix = DOUBLE_IMG;
        datatype = TDOUBLE;
        break;

    case PS_DATA_STRING:
        bitpix = BYTE_IMG;
        datatype = TSTRING;
        break;

    case PS_DATA_BOOL:
        bitpix = BYTE_IMG;
        datatype = TLOGICAL;
        break;

    default: {
            char* typeStr;
            PS_TYPE_NAME(typeStr,type);
            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                    PS_ERRORTEXT_psFits_TYPE_UNSUPPORTED,
                    typeStr);
            return false;
        }
    }

    // pass the requested parameters  (NULL parameters are not set, of course).
    if (bitPix != NULL) {
        *bitPix = bitpix;
    }

    if (dataType != NULL) {
        *dataType = datatype;
    }

    if (bZero != NULL) {
        *bZero = bzero;
    }

    return true;
}

static char getTForm(int 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:
        return '?';
    }
}

static bool getMetadataTForm(psMetadataItem* item, char** fitsType, int repeat)
{
    if (item == NULL) {
        return false;
    }

    *fitsType = NULL;
    char tform = getTForm(item->type);

    switch (item->type) {
    case PS_TYPE_U8:
    case PS_TYPE_S8:
    case PS_TYPE_S16:
    case PS_TYPE_S32:
    case PS_TYPE_U64:
    case PS_TYPE_S64:
    case PS_TYPE_U16:
    case PS_TYPE_U32:
    case PS_TYPE_F32:
    case PS_TYPE_F64:
    case PS_TYPE_C32:
    case PS_TYPE_C64:
    case PS_TYPE_BOOL:
        psStringAppend(fitsType, "%d%c",repeat,tform);
        break;

    case PS_DATA_STRING:
        psStringAppend(fitsType,"%dA",
                       strlen(item->data.V)*repeat);
        break;

    case PS_DATA_VECTOR: {
            psVector* vec = item->data.V;
            tform = getTForm(vec->type.type);
            psStringAppend(fitsType,"%d%c",repeat*vec->n,tform);
        }
        break;

    default:
        return false;
    }

    return true;
}

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

    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psFits_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,
                PS_ERRORTEXT_psFits_GET_HDU_TYPE_FAILED,
                fitsErr);
        return NULL;
    }
    if (hdutype != ASCII_TBL && hdutype != BINARY_TBL) {
        psError(PS_ERR_IO, true,
                PS_ERRORTEXT_psFits_NOT_TABLE_TYPE);
        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,
                PS_ERRORTEXT_psFits_GET_TABLE_SIZE_FAILED,
                fitsErr);
        return NULL;
    }

    psTrace(".psFits.psFitsReadTableRow",5,"Table size is %ix%i\n",numCols, numRows);
    // the row parameter in the proper range?
    if (row < 0 || row >= numRows) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                PS_ERRORTEXT_psFits_ROW_INVALID,
                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(".psFits.psFitsReadTableRow",5,"Column #%i, '%s', is type %i, repeat %i, 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(".psFits.psFitsReadTableRow",5,"Column #%i, '%s', is type %i, repeat %i, 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("psFits.psFitsReadTableRow", 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,
                    PS_ERRORTEXT_psFits_GET_TABLE_ELEMENT,
                    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,
                PS_ERRORTEXT_psFits_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,
                PS_ERRORTEXT_psFits_GET_HDU_TYPE_FAILED,
                fitsErr);
        return NULL;
    }
    if (hdutype != ASCII_TBL && hdutype != BINARY_TBL) {
        psError(PS_ERR_IO, true,
                PS_ERRORTEXT_psFits_NOT_TABLE_TYPE);
        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,
                PS_ERRORTEXT_psFits_FIND_COLUMN,
                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,
                PS_ERRORTEXT_psFits_GET_COLTYPE,
                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));
    }
    result->n = numRows;

    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,
                PS_ERRORTEXT_psFits_TABLE_READ_COL,
                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,
                PS_ERRORTEXT_psFits_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,
                PS_ERRORTEXT_psFits_GET_HDU_TYPE_FAILED,
                fitsErr);
        return NULL;
    }
    if (hdutype != ASCII_TBL && hdutype != BINARY_TBL) {
        psError(PS_ERR_IO, true,
                PS_ERRORTEXT_psFits_NOT_TABLE_TYPE);
        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,
                PS_ERRORTEXT_psFits_FIND_COLUMN,
                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,
                PS_ERRORTEXT_psFits_GET_COLTYPE,
                fitsErr);
        return NULL;
    }

    psVector* result = psVectorAlloc(numRows, convertFitsToPsType(typecode));
    result->n = numRows;

    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,
                PS_ERRORTEXT_psFits_TABLE_READ_COL,
                fitsErr);
        return NULL;
    }

    return result;
}


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

    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psFits_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,
                PS_ERRORTEXT_psFits_GET_HDU_TYPE_FAILED,
                fitsErr);
        return NULL;
    }
    if (hdutype != ASCII_TBL && hdutype != BINARY_TBL) {
        psError(PS_ERR_IO, true,
                PS_ERRORTEXT_psFits_NOT_TABLE_TYPE);
        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,
                PS_ERRORTEXT_psFits_GET_TABLE_SIZE_FAILED,
                fitsErr);
        return NULL;
    }


    psArray* table = psArrayAlloc(numRows);

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

    return table;
}

bool psFitsWriteTable(psFits* fits,
                      const psMetadata* header,
                      const psArray* table)
{
    int status = 0;

    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psFits_NULL);
        return false;
    }

    if (table == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psFits_IMAGE_NULL);
        return false;
    }

    int rows = table->n;
    if (rows < 1) {
        // no table data, what can I do?
        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
                PS_ERRORTEXT_psFits_TABLE_EMPTY);
        return false;
    }

    // find all the columns needed
    psArray* columns = psArrayAlloc(((psMetadata*)table->data[0])->list->n);
    columns->n=0;

    // find the unique items in the array of metadata 'rows'
    psMetadataItem* item;
    for (int row=0; row < rows; row++) {
        psMetadata* rowMeta = table->data[row];
        if (rowMeta != NULL) {
            psListIterator* iter = psListIteratorAlloc(rowMeta->list,
                                   PS_LIST_HEAD,true);
            while ( (item=psListGetAndIncrement(iter)) != NULL) {
                if (PS_DATA_IS_PRIMITIVE(item->type) ||
                        item->type == PS_DATA_STRING ||
                        item->type == PS_DATA_VECTOR) {
                    bool found = false;
                    psMetadataItem* fItem = NULL;
                    int n;
                    for (n=0; n < columns->n && ! found; n++) {
                        fItem = (psMetadataItem*)(columns->data[n]);
                        if (strcmp(item->name, fItem->name) == 0) {
                            found = true;
                            break;
                        }
                    }
                    if (! found) {
                        psArrayAdd(columns, columns->nalloc, item);
                    } else if (item->type == PS_DATA_STRING &&
                               strlen(fItem->data.V) < strlen(item->data.V)) {
                        // got to keep the longest string value as to know what size to create the table column
                        psMemDecrRefCounter(fItem);
                        columns->data[n] = psMemIncrRefCounter(item);
                        columns->n++;

                    } else if (item->type == PS_DATA_VECTOR &&
                               ((psVector*)(fItem->data.V))->n < ((psVector*)(item->data.V))->n) {
                        psMemDecrRefCounter(fItem);
                        columns->data[n] = psMemIncrRefCounter(item);
                        columns->n++;
                    }
                } else {
                    // unsupported type -- treating as an error
                    psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                            "Unsupported data type (%d) for Metadata Item '%s' in row %d.",
                            item->type, item->name, row);
                    psFree(iter);
                    psFree(columns);
                    return false;
                }
            }
            psFree(iter);
        }
    }

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

    //create list of column names and types.
    columns->n--;
    psArray* columnNames = psArrayAlloc(columns->n);
    psArray* columnTypes = psArrayAlloc(columns->n);
    for (int n=0; n < columns->n; n++) {
        columnNames->data[n] = psMemIncrRefCounter(((psMetadataItem*)columns->data[n])->name);
        columnNames->n++;
        columnTypes->n++;
        if ( ! getMetadataTForm((psMetadataItem*)columns->data[n],
                                (char**)&(columnTypes->data[n]),1) ) {
            psError(PS_ERR_UNKNOWN, true,
                    "Failed to determine the FITS data type of '%s' (type=%d).",
                    columnNames->data[n],
                    ((psMetadataItem*)columns->data[n])->type);
            psFree(columns);
            psFree(columnNames);
            psFree(columnTypes);
            return false;
        }
    }

    psString extname = NULL;
    if (header != NULL) {
        extname = psMetadataLookupStr(NULL, header, "EXTNAME");
        if ( extname == NULL) {
            extname = psMetadataLookupStr(NULL, header, "HDUNAME");
        }
    }

    fits_create_tbl(fits->fd,
                    BINARY_TBL,
                    table->n, // number of rows in table
                    columns->n, // number of columns in table
                    (char**)columnNames->data, // names of the columns
                    (char**)columnTypes->data, // format of the columns
                    NULL, // physical unit of columns
                    extname, // extension name
                    &status);

    psFree(columnNames);
    psFree(columnTypes);

    // fill in the table elements with data
    for (int n = 0; n < columns->n; n++) {
        int row;
        item = columns->data[n];
        if (PS_DATA_IS_PRIMITIVE(item->type) ) {
            psVector* col = psVectorAlloc(table->n, item->type);
            int dataSize = PSELEMTYPE_SIZEOF(item->type);
            int dataType;
            convertPsTypeToFits(item->type, NULL,NULL,&dataType);
            for (row = 0; row < table->n; row++) {
                psMetadataItem* dataItem = psMetadataLookup(
                                               table->data[row],
                                               item->name);
                memcpy(&col->data.U8[row*dataSize],&dataItem->data,dataSize);
            }
            fits_write_col(fits->fd,
                           dataType,
                           n+1, // column number
                           1, // firstrow
                           1, // firstelem
                           table->n, // nelements
                           col->data.U8,
                           &status);
            psFree(col);
        } else if (item->type == PS_DATA_STRING) {
            psArray* colArray = psArrayAlloc(table->n);
            for (row = 0; row < table->n; row++) {
                colArray->data[row] = psStringCopy(psMetadataLookupStr(NULL,
                                                   table->data[row],
                                                   item->name));
                colArray->n++;
            }
            fits_write_col_str(fits->fd,
                               n+1,
                               1,
                               1,
                               table->n,
                               (char**)colArray->data,
                               &status);
            psFree(colArray);
        } else if (item->type == PS_DATA_VECTOR) {
            psVector* vec = (psVector*)(item->data.V);
            psVector* col = psVectorAlloc(table->n*vec->n, vec->type.type);
            int dataSize = PSELEMTYPE_SIZEOF(vec->type.type)*vec->n;
            int dataType;
            convertPsTypeToFits(vec->type.type, NULL,NULL,&dataType);
            for (row = 0; row < table->n; row++) {
                psMetadataItem* dataItem = psMetadataLookup(
                                               table->data[row],
                                               item->name);
                memcpy(&col->data.U8[row*dataSize],
                       ((psVector*)(dataItem->data.V))->data.U8,
                       dataSize);
            }
            fits_write_col(fits->fd,
                           dataType,
                           n+1, // column number
                           1, // firstrow
                           1, // firstelem
                           table->n*vec->n, // nelements
                           col->data.U8,
                           &status);
            psFree(col);
        }
        if (status != 0) {
            char fitsErr[MAX_STRING_LENGTH];
            (void)fits_get_errstatus(status, fitsErr);
            psError(PS_ERR_IO, true,
                    PS_ERRORTEXT_psFits_WRITE_FAILED,
                    fitsErr);
            psFree(columns);
            return false;
        }

    }

    psFree(columns);

    return true;
}

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

    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psFits_NULL);
        return false;
    }

    if (table == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psFits_IMAGE_NULL);
        return false;
    }

    int rows = table->n;
    if (rows < 1) {
        // no table data, what can I do?
        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
                PS_ERRORTEXT_psFits_TABLE_EMPTY);
        return false;
    }

    // find all the columns needed
    psArray* columns = psArrayAlloc(((psMetadata*)table->data[0])->list->n);
    columns->n=0;

    // find the unique items in the array of metadata 'rows'
    psMetadataItem* item;
    for (int row=0; row < rows; row++) {
        psMetadata* rowMeta = table->data[row];
        if (rowMeta != NULL) {
            psListIterator* iter = psListIteratorAlloc(rowMeta->list,
                                   PS_LIST_HEAD,true);
            while ( (item=psListGetAndIncrement(iter)) != NULL) {
                if (PS_DATA_IS_PRIMITIVE(item->type) ||
                        item->type == PS_DATA_STRING ||
                        item->type == PS_DATA_VECTOR) {
                    bool found = false;
                    psMetadataItem* fItem = NULL;
                    int n;
                    for (n=0; n < columns->n && ! found; n++) {
                        fItem = (psMetadataItem*)(columns->data[n]);
                        if (strcmp(item->name, fItem->name) == 0) {
                            found = true;
                            break;
                        }
                    }
                    if (! found) {
                        psArrayAdd(columns, columns->nalloc, item);
                    } else if (item->type == PS_DATA_STRING &&
                               strlen(fItem->data.V) < strlen(item->data.V)) {
                        // got to keep the longest string value as to know what size to create the table column
                        psMemDecrRefCounter(fItem);
                        columns->data[n] = psMemIncrRefCounter(item);
                        columns->n++;

                    } else if (item->type == PS_DATA_VECTOR &&
                               ((psVector*)(fItem->data.V))->n < ((psVector*)(item->data.V))->n) {
                        psMemDecrRefCounter(fItem);
                        columns->data[n] = psMemIncrRefCounter(item);
                        columns->n++;
                    }
                } else {
                    // unsupported type -- treating as an error
                    psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                            "Unsupported data type (%d) for Metadata Item '%s' in row %d.",
                            item->type, item->name, row);
                    psFree(iter);
                    psFree(columns);
                    return false;
                }
            }
            psFree(iter);
        }
    }

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

    //create list of column names and types.
    columns->n--;
    psArray* columnNames = psArrayAlloc(columns->n);
    psArray* columnTypes = psArrayAlloc(columns->n);
    for (int n=0; n < columns->n; n++) {
        columnNames->data[n] = psMemIncrRefCounter(((psMetadataItem*)columns->data[n])->name);
        columnNames->n++;
        columnTypes->n++;
        if ( ! getMetadataTForm((psMetadataItem*)columns->data[n],
                                (char**)&(columnTypes->data[n]),1) ) {
            psError(PS_ERR_UNKNOWN, true,
                    "Failed to determine the FITS data type of '%s' (type=%d).",
                    columnNames->data[n],
                    ((psMetadataItem*)columns->data[n])->type);
            psFree(columns);
            psFree(columnNames);
            psFree(columnTypes);
            return false;
        }
    }

    psString extname = NULL;
    if (header != NULL) {
        extname = psMetadataLookupStr(NULL, header, "EXTNAME");
        if ( extname == NULL) {
            extname = psMetadataLookupStr(NULL, header, "HDUNAME");
        }
    }

    if (! after) {
        if (psFitsGetExtNum(fits) == 0) {
            // 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);
        }
    }

    fits_insert_btbl(fits->fd,
                     table->n, // number of rows in table
                     columns->n, // number of columns in table
                     (char**)columnNames->data, // names of the columns
                     (char**)columnTypes->data, // format of the columns
                     NULL, // physical unit of columns
                     extname, // extension name
                     0,
                     &status);

    psFree(columnNames);
    psFree(columnTypes);


    // fill in the table elements with data
    for (int n = 0; n < columns->n; n++) {
        int row;
        item = columns->data[n];
        if (PS_DATA_IS_PRIMITIVE(item->type) ) {
            psVector* col = psVectorAlloc(table->n, item->type);
            int dataSize = PSELEMTYPE_SIZEOF(item->type);
            int dataType;
            convertPsTypeToFits(item->type, NULL,NULL,&dataType);
            for (row = 0; row < table->n; row++) {
                psMetadataItem* dataItem = psMetadataLookup(
                                               table->data[row],
                                               item->name);
                memcpy(&col->data.U8[row*dataSize],&dataItem->data,dataSize);
            }
            fits_write_col(fits->fd,
                           dataType,
                           n+1, // column number
                           1, // firstrow
                           1, // firstelem
                           table->n, // nelements
                           col->data.U8,
                           &status);
            psFree(col);
        } else if (item->type == PS_DATA_STRING) {
            psArray* colArray = psArrayAlloc(table->n);
            for (row = 0; row < table->n; row++) {
                colArray->data[row] = psStringCopy(psMetadataLookupStr(NULL,
                                                   table->data[row],
                                                   item->name));
                colArray->n++;
            }
            fits_write_col_str(fits->fd,
                               n+1,
                               1,
                               1,
                               table->n,
                               (char**)colArray->data,
                               &status);
            psFree(colArray);
        } else if (item->type == PS_DATA_VECTOR) {
            psVector* vec = (psVector*)(item->data.V);
            psVector* col = psVectorAlloc(table->n*vec->n, vec->type.type);
            int dataSize = PSELEMTYPE_SIZEOF(vec->type.type)*vec->n;
            int dataType;
            convertPsTypeToFits(vec->type.type, NULL,NULL,&dataType);
            for (row = 0; row < table->n; row++) {
                psMetadataItem* dataItem = psMetadataLookup(
                                               table->data[row],
                                               item->name);
                memcpy(&col->data.U8[row*dataSize],
                       ((psVector*)(dataItem->data.V))->data.U8,
                       dataSize);
            }
            fits_write_col(fits->fd,
                           dataType,
                           n+1, // column number
                           1, // firstrow
                           1, // firstelem
                           table->n*vec->n, // nelements
                           col->data.U8,
                           &status);
            psFree(col);
        }
        if (status != 0) {
            char fitsErr[MAX_STRING_LENGTH];
            (void)fits_get_errstatus(status, fitsErr);
            psError(PS_ERR_IO, true,
                    PS_ERRORTEXT_psFits_WRITE_FAILED,
                    fitsErr);
            psFree(columns);
            return false;
        }

    }

    psFree(columns);

    return true;
}

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

    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psFits_NULL);
        return false;
    }

    if (data == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psFits_IMAGE_NULL);
        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,
                PS_ERRORTEXT_psFits_GET_HDU_TYPE_FAILED,
                fitsErr);
        return false;
    }
    if (hdutype != ASCII_TBL && hdutype != BINARY_TBL) {
        psError(PS_ERR_IO, true,
                PS_ERRORTEXT_psFits_NOT_TABLE_TYPE);
        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;
                convertPsTypeToFits(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,
                            PS_ERRORTEXT_psFits_WRITE_FAILED,
                            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;
}
