/** @file  psFitsHeader.c
 *
 *  @brief Contains Fits header I/O routines
 *
 *  @ingroup FileIO
 *
 *  @author Robert DeSonia, MHPCC
 *
 *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2005-11-14 22:18:30 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

#include <unistd.h>

#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"

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

// list of FITS header keys to ignore.
static char* standardFitsKeys[] = {
                                      NULL
                                  };

psMetadata* psFitsReadHeader(psMetadata* out,
                             const psFits* fits)
{
    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psFits_NULL);
        return NULL;
    }

    if (out == NULL) {
        out = psMetadataAlloc();
        if (out == NULL) {
            psError(PS_ERR_UNKNOWN, false,
                    "Failed to allocate a new psMetadata container.");
            return NULL;
        }
    }

    // Get number of key names
    int numKeys = 0;
    int keyNum = 0;
    int status = 0;
    fits_get_hdrpos(fits->fd, &numKeys, &keyNum, &status);

    // Get each key name. Keywords start at one.
    char keyType;
    char keyName[MAX_STRING_LENGTH];
    char keyValue[MAX_STRING_LENGTH];
    char keyComment[MAX_STRING_LENGTH];
    psBool tempBool;
    psBool success;
    psBool stdKey;
    for (int i = 1; i <= numKeys; i++) {

        fits_read_keyn(fits->fd, i, keyName, keyValue, keyComment, &status);

        stdKey = false;

        int stdKeyIdx = 0;
        while (standardFitsKeys[stdKeyIdx] != NULL && ! stdKey) {
            if (strcmp(keyName,standardFitsKeys[stdKeyIdx++]) == 0) {
                stdKey = true;
            }
        }

        if (keyValue[0] != 0) { // blank values are not handled by fits_get_keytype
            fits_get_keytype(keyValue, &keyType, &status);
        } else {
            keyType = 'C';
        }
        if (status != 0) {
            break;
        }

        if (! stdKey) {
            switch (keyType) {
            case 'X': // bit
            case 'I': // short int.
            case 'J': // int.
            case 'B': // byte
                success = psMetadataAdd(out,
                                        PS_LIST_TAIL,
                                        keyName,
                                        PS_DATA_S32 | PS_META_DUPLICATE_OK,
                                        keyComment,
                                        atoi(keyValue));
                break;
            case 'U': // unsigned int. may not fit in a psS32
            case 'K': // long int. can't all fit in a psS32
            case 'F':
                success = psMetadataAdd(out,
                                        PS_LIST_TAIL,
                                        keyName,
                                        PS_DATA_F64 | PS_META_DUPLICATE_OK,
                                        keyComment,
                                        atof(keyValue));
                break;
            case 'C':
                // remove the single-quotes at front/end
                if (keyValue[0] == '\'' && keyValue[strlen(keyValue)-1] == '\'') {
                    keyValue[strlen(keyValue)-1] = '\0';
                    success = psMetadataAdd(out,
                                            PS_LIST_TAIL,
                                            keyName,
                                            PS_DATA_STRING | PS_META_DUPLICATE_OK,
                                            keyComment,
                                            keyValue+1);
                } else {
                    success = psMetadataAdd(out,
                                            PS_LIST_TAIL,
                                            keyName,
                                            PS_DATA_STRING | PS_META_DUPLICATE_OK,
                                            keyComment,
                                            keyValue);
                }
                break;
            case 'L':
                tempBool = (keyValue[0] == 'T') ? 1 : 0;
                success = psMetadataAdd(out,
                                        PS_LIST_TAIL,
                                        keyName,
                                        PS_DATA_BOOL | PS_META_DUPLICATE_OK,
                                        keyComment,
                                        tempBool);
                break;
            default:
                psError(PS_ERR_IO, true,
                        PS_ERRORTEXT_psFits_METATYPE_INVALID,
                        keyType);
                return out;
            }

            if (!success) {
                psError(PS_ERR_UNKNOWN, false,
                        PS_ERRORTEXT_psFits_METADATA_ADD_FAILED,
                        keyName);
                return out;
            }
        }

    }

    if ( status != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        (void)fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_IO, true,
                PS_ERRORTEXT_psFits_METADATA_ADD_FAILED,
                fitsErr);
        return false;
    }

    return out;
}

psMetadata* psFitsReadHeaderSet(psMetadata* out,
                                const psFits* fits)
{
    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psFits_NULL);
        psFree(out);
        return NULL;
    }

    if (out == NULL) {
        out = psMetadataAlloc();
        if (out == NULL) {
            psError(PS_ERR_UNKNOWN, false,
                    "Failed to allocate a new psMetadata container.");
            return NULL;
        }
    }

    int size = psFitsGetSize(fits);

    int origPosition = psFitsGetExtNum(fits);

    for (int lcv=0; lcv < size; lcv++) {
        psFitsMoveExtNum(fits, lcv, false);

        char* name = NULL;
        if (lcv == 0) {
            name = psStringCopy("PHU");
        } else {
            name = psFitsGetExtName(fits);
        }

        psMetadata* header = psFitsReadHeader(NULL, fits);
        if (name != NULL && header != NULL) {
            psMetadataAddMetadata(out, PS_LIST_HEAD, name, 0, "FITS Header",
                                  header);
        } else {
            psWarning("Failed to read HDU#%d header data.",
                      lcv);
        }

        psFree(name);
        psFree(header);
    }

    // reposition to the original position
    psFitsMoveExtNum(fits, origPosition, false);

    return out;
}

bool psFitsWriteHeader(const psMetadata* output,
                       psFits* fits)
{

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

    if (output == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                PS_ERRORTEXT_psFits_METADATA_NULL);
        return false;
    }

    int status = 0;

    //transverse the metadata list and add each key.

    psListIterator* iter = psListIteratorAlloc(output->list,PS_LIST_HEAD,true);
    psMetadataItem* item;
    while ( (item=psListGetAndIncrement(iter)) != NULL ) {
        switch (item->type) {
        case PS_DATA_BOOL: {
                int value = item->data.B;
                fits_update_key(fits->fd,
                                TLOGICAL,
                                item->name,
                                &value,
                                item->comment,
                                &status);
                break;
            }
        case PS_DATA_S32:
            fits_update_key(fits->fd,
                            TINT,
                            item->name,
                            &item->data.S32,
                            item->comment,
                            &status);
            break;
        case PS_DATA_F32:
            fits_update_key(fits->fd,
                            TFLOAT,
                            item->name,
                            &item->data.F32,
                            item->comment,
                            &status);
            break;
        case PS_DATA_F64:
            fits_update_key(fits->fd,
                            TDOUBLE,
                            item->name,
                            &item->data.F64,
                            item->comment,
                            &status);
            break;
        case PS_DATA_STRING:
            fits_update_key(fits->fd,
                            TSTRING,
                            item->name,
                            item->data.V,
                            item->comment,
                            &status);
            break;
        default:  // all other META types are ignored
            break;
        }

        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);
            return false;
        }
    }

    return true;
}

