/** @file  psFitsHeader.c
 *
 *  @brief Contains Fits header I/O routines
 *
 *  @ingroup FileIO
 *
 *  @author Robert DeSonia, MHPCC
 *
 *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2006-04-18 19:01:21 $
 *
 *  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 'B': // byte
                success = psMetadataAdd(out,
                                        PS_LIST_TAIL,
                                        keyName,
                                        PS_DATA_S8 | PS_META_DUPLICATE_OK,
                                        keyComment,
                                        atoi(keyValue));
                break;
            case 'I': // short int.
                success = psMetadataAdd(out,
                                        PS_LIST_TAIL,
                                        keyName,
                                        PS_DATA_S16 | PS_META_DUPLICATE_OK,
                                        keyComment,
                                        (psS16)atoi(keyValue));
                break;
            case 'J': // int.
                success = psMetadataAdd(out,
                                        PS_LIST_TAIL,
                                        keyName,
                                        PS_DATA_S32 | PS_META_DUPLICATE_OK,
                                        keyComment,
                                        atoi(keyValue));
                break;
            case 'U': // unsigned int.
                success = psMetadataAdd(out,
                                        PS_LIST_TAIL,
                                        keyName,
                                        PS_DATA_U32 | PS_META_DUPLICATE_OK,
                                        keyComment,
                                        atol(keyValue));
                break;

            case 'K': // long int. can't all fit in a psS32, put in psF64
            case 'F':
                success = psMetadataAdd(out,
                                        PS_LIST_TAIL,
                                        keyName,
                                        PS_DATA_F64 | PS_META_DUPLICATE_OK,
                                        keyComment,
                                        atof(keyValue));
                break;
            case 'C': {
                    char *keyValueFixed = keyValue; // Fixed version of the string
                    // remove the single-quotes at front/end
                    if (keyValueFixed[0] == '\'' && keyValueFixed[strlen(keyValue)-1] == '\'') {
                        keyValue[strlen(keyValue)-1] = '\0'; // Remove the trailing quote
                        keyValueFixed += 1; // Advance past the leading quote
                    }
                    // Remove trailing spaces, which are not significant, according to the FITS standard
                    // http://archive.stsci.edu/fits/fits_standard/node31.html
                    char *lastSpace = NULL; // The last space in the string
                    while (strlen(keyValueFixed) > 1 && (lastSpace = strrchr(keyValueFixed, ' ')) &&
                            lastSpace[1] == '\0') {
                        // This is a trailing space, not a leading space.
                        lastSpace[0] = '\0'; // Truncate the string here
                    }

                    success = psMetadataAdd(out,
                                            PS_LIST_TAIL,
                                            keyName,
                                            PS_DATA_STRING | PS_META_DUPLICATE_OK,
                                            keyComment,
                                            keyValueFixed);
                    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();
    }

    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_S8:
            fits_update_key(fits->fd,
                            TBYTE,
                            item->name,
                            &item->data.S8,
                            item->comment,
                            &status);
            break;
        case PS_DATA_S16:
            fits_update_key(fits->fd,
                            TSHORT,
                            item->name,
                            &item->data.S16,
                            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_U8: {
                unsigned short int temp = item->data.U8;
                fits_update_key(fits->fd,
                                TUSHORT,
                                item->name,
                                &temp,
                                item->comment,
                                &status);
            }
            break;
        case PS_DATA_U16:
            fits_update_key(fits->fd,
                            TUSHORT,
                            item->name,
                            &item->data.U16,
                            item->comment,
                            &status);
            break;
        case PS_DATA_U32:
            fits_update_key(fits->fd,
                            TUINT,
                            item->name,
                            &item->data.U32,
                            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;
        }
    }

    psFree(iter);

    return true;
}

bool psFitsHeaderValidate(psMetadata *header)
{
    bool valid = true;
    int status;

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

    //transverse the metadata list and inspect at each key

    psListIterator* iter = psListIteratorAlloc(header->list,PS_LIST_HEAD,true);
    psMetadataItem* item;
    while ( (item=psListGetAndIncrement(iter)) != NULL ) {
        psDataType type = item->type;

        if (type > PS_DATA_STRING) { // i.e., a non-primitive type
            valid = false;
        }

        if (strlen(item->name) > 8) {
            item->name[8] = '\0'; // truncate to 8 characters
        }

        fits_uppercase(item->name); // make uppercase

        // now, let's see if CFITSIO thinks this is a good keyword...
        status = 0;
        if (fits_test_keyword(item->name,&status) != 0) {
            valid = false;
        }
    }

    psFree(iter);

    return valid;
}
