/** @file  psFits.c
 *
 *  @brief Contains Fits I/O routines
 *
 *  @ingroup FileIO
 *
 *  @author Robert DeSonia, MHPCC
 *
 *  @version $Revision: 1.65 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2007-04-11 17:01:50 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

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

#include <unistd.h>

#include "psFits.h"
#include "string.h"
#include "psError.h"
#include "psAssert.h"
#include "psImageStructManip.h"
#include "psMemory.h"
#include "psString.h"
#include "psLogMsg.h"
#include "psTrace.h"
#include "psVector.h"
#include "psAbort.h"

#define MAX_STRING_LENGTH 256  // maximum length string for FITS routines
static char *defaultExtword = "EXTNAME";

static bool isHDUEmpty(const psFits* fits)
{
    /* check for keys - no keys means this is really an empty HDU */
    int keysexist = -1;
    int morekeys;
    int status = 0;

    fits_get_hdrspace(fits->fd, &keysexist, &morekeys, &status);

    // if no keys exist and not primary HDU, this really is an empty HDU
    if (keysexist == 0) {
        return true;
    }

    return false;

}

static bool fitsClose(psFits* fits)
{
    int status = 0;

    if (fits != NULL) {
        if (fits_close_file(fits->fd, &status)) {
            char fitsErr[MAX_STRING_LENGTH];
            fits_get_errstatus(status, fitsErr);
            psError(PS_ERR_BAD_FITS, true,
                    "Error while closing psFits object. CFITSIO error: %s",
                    fitsErr);
            return false;
        }
	fits->fd = NULL;
    }
    return true;
}

static void fitsFree(psFits* fits)
{
    if (!fits) return;
    if (fits->fd) {
	fitsClose(fits);
    }
    psFree (fits->extword);
}

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

    bool status = fitsClose(fits);
    psFree(fits);

    return status;
}

psFits* psFitsOpen(const char* name, const char* mode)
{
    int status = 0;
    fitsfile *fptr = NULL;      /* Pointer to the FITS file */

    if (name == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Specified filename can not be NULL."));
        return NULL;
    }

    /* check the mode to determine how to open/create file */
    int iomode;
    bool newFile;
    if (strcmp(mode,"r") == 0) {
        iomode = READONLY;
        newFile = false;
    } else if (strcmp(mode,"rw") == 0 || strcmp(mode,"r+") == 0) {
        iomode = READWRITE;
        newFile = false;
    } else if (strcmp(mode,"w") == 0 || strcmp(mode,"w+") == 0) {
        iomode = READWRITE;
        newFile = true;
    } else if (strcmp(mode,"a") == 0|| strcmp(mode,"a+") == 0) {
        iomode = READWRITE;
        newFile = (access(name, F_OK) != 0);
    } else {
        // mode is not valid
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                "Specified mode, '%s', is invalid.  Supported modes are r, r+, rw, w, w+, a, or a+.",
                mode);
        return NULL;
    }

    if (newFile) {
        /* Check if an existing file is in the way before creating file*/
        if (access(name, F_OK) == 0) {
            // file exists, delete old one first
            remove
                (name);
        }

        #if ( CFITSIO_DISKFILE == 1 )
        (void)fits_create_diskfile
        #else
        (void)fits_create_file
        #endif
        (&fptr,
         name,
         &status);
        if (fptr == NULL || status != 0) {
            char fitsErr[MAX_STRING_LENGTH]
            ;
            fits_get_errstatus(status, fitsErr);
            psError(PS_ERR_IO, true,
                    _("Could not create file,'%s'. CFITSIO Error: %s"),
                    name, fitsErr);
            return NULL;
        }
    } else {
        #if ( CFITSIO_DISKFILE == 1 )
        (void)fits_open_diskfile
        #else
        (void)fits_open_file
        #endif
        (&fptr,
         name,
         iomode,
         &status);
        if (fptr == NULL || status != 0) {
            char fitsErr[MAX_STRING_LENGTH]
            ;
            fits_get_errstatus(status, fitsErr);
            psError(PS_ERR_IO, true,
                    _("Could not open file,'%s'. CFITSIO Error: %s"),
                    name, fitsErr);
            return NULL;
        }
    }

    psFits* fits = psAlloc(sizeof(psFits));
    fits->fd = fptr;
    fits->writable = (iomode == READWRITE);
    fits->extword = NULL;
    psMemSetDeallocator(fits,(psFreeFunc)fitsFree);

    return fits;
}

bool psMemCheckFits(psPtr ptr)
{
    PS_ASSERT_PTR(ptr, false);
    return ( psMemGetDeallocator(ptr) == (psFreeFunc)fitsFree );
}

bool psFitsSetExtnameWord (psFits *fits, const char *extword)
{
    PS_ASSERT_PTR_NON_NULL(fits,    false);
    PS_ASSERT_PTR_NON_NULL(extword, false);

    psFree (fits->extword);
    fits->extword = psStringCopy (extword);
    return true;
}

// move to the first HDU where extword == extname.  this is equivalent to fits_movnam_hdu() for
// a user-defined word in place of EXTNAME
bool p_psFitsMoveExtName_UserKey(const psFits *fits,
				 const char *extname,
				 const char *extword)
{
    PS_ASSERT_PTR_NON_NULL(fits,    false);
    PS_ASSERT_PTR_NON_NULL(extname, false);
    PS_ASSERT_PTR_NON_NULL(extword, false);

    int hdutype = 0;
    char name[MAX_STRING_LENGTH];
    char extstring[MAX_STRING_LENGTH];

    sprintf (extstring, "'%s'", extname);

    // NOTE: fits_* return 0 for success
    for (int i = 1; true; i++) {
	// are we able to read the next HDU?

	int status = 0;
	if (fits_movabs_hdu(fits->fd, i, &hdutype, &status)) {
	    char fitsErr[MAX_STRING_LENGTH];
	    fits_get_errstatus(status, fitsErr);
	    psError(PS_ERR_LOCATION_INVALID, true,
		    _("Could not find HDU with %s = '%s'. CFITSIO Error: %s"),
		    extword, extname, fitsErr);
	    return false;
	}
	// is there a keyword called 'extword'? (read as string regardless of type)
	status = 0;
	if (fits_read_keyword(fits->fd, (char *)extword, name, NULL, &status)) {
	    continue;
	}
	// if this was read as a string, we will have leading and trailing single-quotes
	// try both for comparison
	
	// do we have the right hdu (names match)?
	if (!strcmp (name, extname) || !strcmp (name, extstring)) {
	    return true;
	}
    }
    psAbort("we should not reach here");
}

// XXX I will need to define a low-level function p_psFitsMoveExtName_UserKey () which
// uses fits_movabs_hdu() to replicate the functionality of fits_movnam_hdu using an
// alternate name for EXTNAME
bool psFitsMoveExtName(const psFits* fits,
                       const char* extname)
{
    int status = 0;

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

    if (extname == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Specified extension name can not be NULL."));
        return false;
    }

    if (fits->extword != NULL) {
	bool result = p_psFitsMoveExtName_UserKey(fits, extname, fits->extword);
	return (result);
    }

    if (fits_movnam_hdu(fits->fd, ANY_HDU, (char*)extname, 0, &status) != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_LOCATION_INVALID, true,
                _("Could not find HDU '%s'. CFITSIO Error: %s"),
                extname, fitsErr);
        return false;
    }

    return true;
}

bool psFitsMoveExtNum(const psFits* fits,
                      int extnum,
                      bool relative)
{
    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("The input psFits object can not NULL."));
        return false;
    }

    int status = 0;
    int hdutype = 0;

    if (relative) {
        fits_movrel_hdu(fits->fd, extnum, &hdutype, &status);
        if (status != 0) {
            char fitsErr[MAX_STRING_LENGTH];
            fits_get_errstatus(status, fitsErr);
            psError(PS_ERR_LOCATION_INVALID, true,
                    _("Could not move %d HDUs from current position. CFITSIO Error: %s"),
                    extnum, fitsErr);
            return false;
        }
    } else {
        fits_movabs_hdu(fits->fd, extnum+1, &hdutype, &status);
        if (status != 0) {
            char fitsErr[MAX_STRING_LENGTH];
            fits_get_errstatus(status, fitsErr);
            psError(PS_ERR_LOCATION_INVALID, true,
                    _("Could not move to specified HDU #%d. CFITSIO Error: %s"),
                    extnum, fitsErr);
            return false;
        }
    }

    return true;
}

bool psFitsMoveLast(psFits* fits)
{
    if (fits == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("The input psFits object can not NULL."));
        return false;
    }
    int size = psFitsGetSize(fits);
    if (size == 0) { // empty file -- no action needed
        return true;
    } else {
        return psFitsMoveExtNum(fits,size-1,false);
    }
}

int psFitsGetExtNum(const psFits* fits)
{
    int hdunum;

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


    return fits_get_hdu_num(fits->fd,&hdunum) - 1;
}

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

    int status = 0;
    char name[MAX_STRING_LENGTH];

    char *extword = (fits->extword == NULL) ? defaultExtword : fits->extword;

    if (fits_read_key_str(fits->fd, extword, name, NULL, &status) != 0) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Header keyword %s is not found"), extword);
	return NULL;
    }
    return psStringCopy(name);
}

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

    if (name == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Specified extension name can not be NULL."));
        return false;
    }

    int status = 0;

    char *extword = (fits->extword == NULL) ? defaultExtword : fits->extword;

    if (fits_update_key_str(fits->fd, extword, (char*)name, NULL, &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);
        return false;
    }

    return true;
}

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

    if (! fits->writable) {
        psError(PS_ERR_IO, true,
                _("The psFits object is not writable."));
        return false;
    }

    // move to the specified HDU
    if (! psFitsMoveExtNum(fits,extnum,relative) ) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                "Failed to delete HDU #%d",
                extnum);
        return false;
    }

    int status = 0;

    // OK, now let's delete the HDU
    if (fits_delete_hdu(fits->fd, NULL, &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);
        return false;
    }

    return true;
}

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

    if (! fits->writable) {
        psError(PS_ERR_IO, true,
                _("The psFits object is not writable."));
        return false;
    }

    // move to the specified HDU
    if (! psFitsMoveExtName(fits,extname) ) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                "Failed to delete HDU with the name '%s'",
                extname);
        return false;
    }


    int status = 0;

    // OK, now let's delete the HDU
    if (fits_delete_hdu(fits->fd, NULL, &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);
        return false;
    }

    return true;
}

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

    int num = 0;
    int status = 0;

    if (fits_get_num_hdus(fits->fd, &num, &status) != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_LOCATION_INVALID, true,
                _("Failed to determine the number of HDUs. CFITSIO Error: %s"),
                fitsErr);
        return 0;
    }

    return num;
}

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

    int status = 0;
    int hdutype = PS_FITS_TYPE_NONE;

    if (fits_get_hdu_type(fits->fd, &hdutype, &status) != 0) {
        char fitsErr[MAX_STRING_LENGTH];
        fits_get_errstatus(status, fitsErr);
        psError(PS_ERR_LOCATION_INVALID, true,
                _("Failed to determine an HDU type. CFITSIO Error: %s"),
                fitsErr);
        return PS_FITS_TYPE_NONE;
    }

    if (hdutype == PS_FITS_TYPE_IMAGE &&
            psFitsGetExtNum(fits) > 0 &&
            isHDUEmpty(fits)) {
        return PS_FITS_TYPE_ANY;
    }

    return hdutype;
}

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

    if (! fits->writable) {
        psError(PS_ERR_IO, true,
                _("The psFits object is not writable."));
        return PS_FITS_TYPE_NONE;
    }

    int newEnd = psFitsGetExtNum(fits);

    psFitsMoveLast(fits);
    int end = psFitsGetExtNum(fits);

    // delete HDUs from end to beginning position + 1;
    for (int lcv=end;lcv > newEnd; lcv--) {
        if (! psFitsDeleteExtNum(fits,lcv,false)) {
            // failed to delete an HDU!?
            psError(PS_ERR_UNKNOWN, false,
                    "Failed to truncate file.  HDU #%d out of %d could not be deleted.",
                    lcv,end);
            return false;
        }
    }

    return true;
}


psDataType p_psFitsTypeFromCfitsio(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 TLOGICAL:
        return PS_TYPE_BOOL;
    default:
        psError(PS_ERR_IO, true,
                "Unknown FITS datatype, %d.",
                datatype);
        return 0;
    }
}

bool p_psFitsTypeToCfitsio(psDataType 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,
                    _("Specified type, %s, is not supported."),
                    typeStr);
            return false;
        }
    }

    // pass back 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;
}


