/** @file  psFits.c
 *
 *  @brief Contains Fits I/O routines
 *
 *  @ingroup FileIO
 *
 *  @author Robert DeSonia, MHPCC
 *
 *  @version $Revision: 1.53 $ $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 <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

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_IO, true,
                    "Error while closing psFits object. CFITSIO error: %s",
                    fitsErr);
            return false;
        }
    }

    return true;
}

static void fitsFree(psFits* fits)
{
    (void)fitsClose(fits);
}

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

    bool status = fitsClose(fits);
    psMemSetDeallocator(fits,NULL);
    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,
                PS_ERRORTEXT_psFits_FILENAME_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,
                        PS_ERRORTEXT_psFits_FILENAME_CREATE_FAILED,
                        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_BAD_PARAMETER_VALUE, true,
                            PS_ERRORTEXT_psFits_FILENAME_INVALID,
                            name, fitsErr);
                    return NULL;
                }
            }

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

        return fits;
    }

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


    bool psFitsMoveExtName(const psFits* fits,
                           const char* extname)
    {
        int status = 0;

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

        if (extname == NULL) {
            psError(PS_ERR_BAD_PARAMETER_NULL, true,
                    PS_ERRORTEXT_psFits_EXTNAME_NULL);
            return false;
        }


        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,
                    PS_ERRORTEXT_psFits_EXTNAME_INVALID,
                    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,
                    PS_ERRORTEXT_psFits_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,
                        PS_ERRORTEXT_psFits_EXTNUM_REL_MOVE_FAILED,
                        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,
                        PS_ERRORTEXT_psFits_EXTNUM_ABS_MOVE_FAILED,
                        extnum, fitsErr);
                return false;
            }
        }

        return true;
    }

    bool psFitsMoveLast(psFits* fits)
    {
        if (fits == NULL) {
            psError(PS_ERR_BAD_PARAMETER_NULL, true,
                    PS_ERRORTEXT_psFits_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,
                    PS_ERRORTEXT_psFits_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,
                    PS_ERRORTEXT_psFits_NULL);
            return NULL;
        }

        int status = 0;
        char name[MAX_STRING_LENGTH];

        if (fits_read_key_str(fits->fd, "EXTNAME", name, NULL, &status) != 0) {
            status = 0;
            if (fits_read_key_str(fits->fd, "HDUNAME", name, NULL, &status) != 0) {
                int num = psFitsGetExtNum(fits);
                snprintf(name, MAX_STRING_LENGTH, "EXT-%3d",num);
            }
        }
        return psStringCopy(name);
    }

    bool psFitsSetExtName(psFits* fits, const char* name)
    {
        if (fits == NULL) {
            psError(PS_ERR_BAD_PARAMETER_NULL, true,
                    PS_ERRORTEXT_psFits_NULL);
            return false;
        }

        if (name == NULL) {
            psError(PS_ERR_BAD_PARAMETER_NULL, true,
                    PS_ERRORTEXT_psFits_EXTNAME_NULL);
            return false;
        }

        int status = 0;

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

    bool psFitsDeleteExtNum(psFits* fits,
                            int extnum,
                            bool relative)
    {
        if (fits == NULL) {
            psError(PS_ERR_BAD_PARAMETER_NULL, true,
                    PS_ERRORTEXT_psFits_NULL);
            return false;
        }

        if (! fits->writable) {
            psError(PS_ERR_IO, true,
                    PS_ERRORTEXT_psFits_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,
                    PS_ERRORTEXT_psFits_WRITE_FAILED,
                    fitsErr);
            return false;
        }

        return true;
    }

    bool psFitsDeleteExtName(psFits* fits,
                             const char* extname)
    {
        if (fits == NULL) {
            psError(PS_ERR_BAD_PARAMETER_NULL, true,
                    PS_ERRORTEXT_psFits_NULL);
            return false;
        }

        if (! fits->writable) {
            psError(PS_ERR_IO, true,
                    PS_ERRORTEXT_psFits_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,
                    PS_ERRORTEXT_psFits_WRITE_FAILED,
                    fitsErr);
            return false;
        }

        return true;
    }

    int psFitsGetSize(const psFits* fits)
    {
        if (fits == NULL) {
            psError(PS_ERR_BAD_PARAMETER_NULL, true,
                    PS_ERRORTEXT_psFits_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,
                    PS_ERRORTEXT_psFits_GETNUMHDUS_FAILED,
                    fitsErr);
            return 0;
        }

        return num;
    }

    psFitsType psFitsGetExtType(const psFits* fits)
    {
        if (fits == NULL) {
            psError(PS_ERR_BAD_PARAMETER_NULL, true,
                    PS_ERRORTEXT_psFits_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,
                    PS_ERRORTEXT_psFits_GETHDUTYPE_FAILED,
                    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,
                    PS_ERRORTEXT_psFits_NULL);
            return PS_FITS_TYPE_NONE;
        }

        if (! fits->writable) {
            psError(PS_ERR_IO, true,
                    PS_ERRORTEXT_psFits_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;
    }
