/** @file  psImageIO.c
 *
 *  @brief Contains image I/O routines.
 *
 *  This file defines the file input/output functions for the psImage structure.
 *
 *  @author Robert DeSonia, MHPCC
 *
 *  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2004-10-02 02:08:00 $
 *
 *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
 */

#include <fitsio.h>
#include <unistd.h>
#include <stdbool.h>

#include "psImageIO.h"
#include "psError.h"
#include "psMemory.h"

#include "psImageErrors.h"

psImage* psImageReadSection(psImage* output,
                            int col,
                            int row,
                            int numCols,
                            int numRows,
                            int z,
                            char *extname,
                            int extnum,
                            char *filename)
{
    fitsfile *fptr = NULL;      /* Pointer to the FITS file */
    int status = 0;             /* CFITSIO file vars */
    int nAxis = 0;
    int anynull = 0;
    int bitPix = 0;             /* Pixel type */
    long nAxes[3];
    long firstPixel[3];         /* lower-left corner of image subset */
    long lastPixel[3];          /* upper-right corner of image subset */
    long increment[3];          /* increment for image subset */
    char fitsErr[80] = "";      /* CFITSIO error message string */
    int hduType = IMAGE_HDU;
    int fitsDatatype = 0;
    int datatype = 0;

    if (filename == NULL) {
        psErrorMsg(PS_ERRORNAME_DOMAIN "psImageReadSection",
                   PS_ERR_BAD_PARAMETER_NULL, true,
                   PS_ERRORTEXT_psImageIO_FILENAME_NULL);
        psFree(output);
        return NULL;
    }

    /* Open the FITS file */
    (void)fits_open_file(&fptr, filename, READONLY, &status);
    if (fptr == NULL || status != 0) {
        fits_get_errstatus(status, fitsErr);
        psErrorMsg(PS_ERRORNAME_DOMAIN "psImageReadSection",
                   PS_ERR_BAD_PARAMETER_VALUE, true,
                   PS_ERRORTEXT_psImageIO_FILENAME_INVALID,
                   filename, fitsErr);
        psFree(output);
        return NULL;
    }

    /* find the specified extension */
    if (extname != NULL) {
        if (fits_movnam_hdu(fptr, hduType, extname, 0, &status) != 0) {
            fits_get_errstatus(status, fitsErr);
            status = 0;
            (void)fits_close_file(fptr, &status);
            psErrorMsg(PS_ERRORNAME_DOMAIN "psImageReadSection",
                       PS_ERR_LOCATION_INVALID, true,
                       PS_ERRORTEXT_psImageIO_EXTNAME_INVALID,
                       extname, filename, fitsErr);
            psFree(output);
            return NULL;
        }
    } else {
        if (fits_movabs_hdu(fptr, extnum + 1, &hduType, &status) != 0) {
            fits_get_errstatus(status, fitsErr);
            status = 0;
            (void)fits_close_file(fptr, &status);
            psErrorMsg(PS_ERRORNAME_DOMAIN "psImageReadSection",
                       PS_ERR_LOCATION_INVALID, true,
                       PS_ERRORTEXT_psImageIO_EXTNUM_INVALID,
                       extnum, filename, fitsErr);
            psFree(output);
            return NULL;
        }
    }

    /* Get the data type 'bitPix' from the FITS image */
    if (fits_get_img_equivtype(fptr, &bitPix, &status) != 0) {
        fits_get_errstatus(status, fitsErr);
        status = 0;
        (void)fits_close_file(fptr, &status);
        psErrorMsg(PS_ERRORNAME_DOMAIN "psImageReadSection",
                   PS_ERR_IO, true,
                   PS_ERRORTEXT_psImageIO_DATATYPE_UNKNOWN,
                   filename, fitsErr);
        psFree(output);
        return NULL;
    }

    /* Get the dimensions 'nAxis' from the FITS image */
    if (fits_get_img_dim(fptr, &nAxis, &status) != 0) {
        (void)fits_get_errstatus(status, fitsErr);
        status = 0;
        (void)fits_close_file(fptr, &status);
        psErrorMsg(PS_ERRORNAME_DOMAIN "psImageReadSection",
                   PS_ERR_IO, true,
                   PS_ERRORTEXT_psImageIO_IMAGE_DIM_UNKNOWN,
                   filename, fitsErr);
        psFree(output);
        return NULL;
    }

    /* Validate the number of axis */
    if ((nAxis < 2) || (nAxis > 3)) {
        status = 0;
        (void)fits_close_file(fptr, &status);
        psErrorMsg(PS_ERRORNAME_DOMAIN "psImageReadSection",
                   PS_ERR_IO, true,
                   PS_ERRORTEXT_psImageIO_IMAGE_DIMENSION_UNSUPPORTED,
                   nAxis);
        psFree(output);
        return NULL;
    }

    /* Get the Image size from the FITS file */
    if (fits_get_img_size(fptr, nAxis, nAxes, &status) != 0) {
        (void)fits_get_errstatus(status, fitsErr);
        status = 0;
        (void)fits_close_file(fptr, &status);
        psErrorMsg(PS_ERRORNAME_DOMAIN "psImageReadSection",
                   PS_ERR_IO, true,
                   PS_ERRORTEXT_psImageIO_IMAGE_SIZE_UNKNOWN,
                   filename, fitsErr);
        psFree(output);
        return NULL;
    }

    if (numCols < 1) {
        numCols += nAxes[0] - col;
    }
    if (numRows < 1) {
        numRows += nAxes[1] - row;
    }

    firstPixel[0] = col + 1;
    firstPixel[1] = row + 1;
    firstPixel[2] = z + 1;

    lastPixel[0] = firstPixel[0] + numCols - 1;
    lastPixel[1] = firstPixel[1] + numRows - 1;
    lastPixel[2] = z + 1;

    increment[0] = 1;
    increment[1] = 1;
    increment[2] = 1;

    switch (bitPix) {
    case BYTE_IMG:
        datatype = PS_TYPE_U8;
        fitsDatatype = TBYTE;
        break;
        /*    case SBYTE_IMG:                  Not a requirement
                datatype = PS_TYPE_S8;
                fitsDatatype = TSBYTE;
                break;     */
        /*    case USHORT_IMG:                 Not a requirement
                datatype = PS_TYPE_U16;
                fitsDatatype = TUSHORT;
                break;     */
    case SHORT_IMG:
        datatype = PS_TYPE_S16;
        fitsDatatype = TSHORT;
        break;
        /*    case ULONG_IMG:                  Not a requirement
                datatype = PS_TYPE_U32;
                fitsDatatype = TUINT;
                break;    */
    case LONG_IMG:
        datatype = PS_TYPE_S32;
        fitsDatatype = TINT;
        break;
        /*    case LONGLONG_IMG:               Not a requirement
                datatype = PS_TYPE_S64;
                fitsDatatype = TLONGLONG;
                break;    */
    case FLOAT_IMG:
        datatype = PS_TYPE_F32;
        fitsDatatype = TFLOAT;
        break;
    case DOUBLE_IMG:
        datatype = PS_TYPE_F64;
        fitsDatatype = TDOUBLE;
        break;
    default:
        psErrorMsg(PS_ERRORNAME_DOMAIN "psImageReadSection",
                   PS_ERR_IO, true,
                   PS_ERRORTEXT_psImageIO_FITS_TYPE_UNSUPPORTED,
                   bitPix, filename);
        psFree(output);
        return NULL;
    }
    output = psImageRecycle(output, numCols, numRows, datatype);
    if (fits_read_subset(fptr, fitsDatatype, firstPixel, lastPixel, increment,
                         NULL, output->data.V[0], &anynull, &status) != 0) {
        psFree(output);
        (void)fits_get_errstatus(status, fitsErr);
        status = 0;
        (void)fits_close_file(fptr, &status);
        psErrorMsg(PS_ERRORNAME_DOMAIN "psImageReadSection",
                   PS_ERR_IO, true,
                   PS_ERRORTEXT_psImageIO_READ_FAILED,
                   filename, fitsErr);
        return NULL;
    }

    (void)fits_close_file(fptr, &status);

    return output;
}

bool psImageWriteSection(psImage* input,
                         int col0,
                         int row0,
                         int z,
                         char *extname,
                         int extnum,
                         char *filename)
{
    int numCols = 0;
    int numRows = 0;

    int status = 0;             /* CFITSIO status */
    fitsfile *fptr = NULL;      /* pointer to the FITS file */
    long nAxes[3];              /* Image axis vars */
    long firstPixel[3];         /* First Pixel to read */
    long lastPixel[3];          /* Last Pixel to read */
    char fitsErr[80];           /* FITSIO message string */
    int datatype = 0;           /* the datatype of the image */
    int bitPix = 0;             /* FITS bitPix value */
    int hduType = IMAGE_HDU;    /* the HDU type (image,table, etc.) */
    double bscale = 1.0;
    double bzero = 0.0;
    bool createNewHDU = false;

    /* need a valid image to write */
    if (input == NULL) {
        psErrorMsg(PS_ERRORNAME_DOMAIN "psImageWriteSection",
                   PS_ERR_BAD_PARAMETER_NULL, true,
                   PS_ERRORTEXT_psImage_IMAGE_NULL);
        return false;
    }

    numRows = input->numRows;
    numCols = input->numCols;

    switch (input->type.type) {
    case PS_TYPE_U8:
        bitPix = BYTE_IMG;
        datatype = TBYTE;
        break;
        /*    case PS_TYPE_S8:            Not a requirement
                bitPix = BYTE_IMG;
                bzero = INT8_MIN;
                datatype = TSBYTE;
                break;      */
        /*    case PS_TYPE_U16:           Not a requirement
                bitPix = SHORT_IMG;
                bzero = -1.0f * INT16_MIN;
                datatype = TUSHORT;
                break;      */
    case PS_TYPE_S16:
        bitPix = SHORT_IMG;
        datatype = TSHORT;
        break;
        /*    case PS_TYPE_U32:           Not a requirement
                bitPix = LONG_IMG;
                bzero = -1.0f * 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;
    default: {
            char* typeStr;
            PS_TYPE_NAME(typeStr,input->type.type);
            psErrorMsg(PS_ERRORNAME_DOMAIN "psImageWriteSection",
                       PS_ERR_BAD_PARAMETER_TYPE, true,
                       PS_ERRORTEXT_psImageIO_TYPE_UNSUPPORTED,
                       typeStr);
            return false;
        }
    }

    /* Open the FITS file */
    if (access(filename, F_OK) == 0) {     // file
        // exists
        (void)fits_open_file(&fptr, filename, READWRITE, &status);
        if (fptr == NULL || status != 0) {
            fits_get_errstatus(status, fitsErr);
            psErrorMsg(PS_ERRORNAME_DOMAIN "psImageWriteSection",
                       PS_ERR_BAD_PARAMETER_VALUE, true,
                       PS_ERRORTEXT_psImageIO_FILENAME_INVALID,
                       filename, fitsErr);
            return false;
        }

        /* find the specified extension */
        if (extname != NULL) {
            if (fits_movnam_hdu(fptr, hduType, extname, 0, &status) != 0) {
                fits_get_errstatus(status, fitsErr);
                status = 0;
                (void)fits_close_file(fptr, &status);
                psErrorMsg(PS_ERRORNAME_DOMAIN "psImageWriteSection",
                           PS_ERR_LOCATION_INVALID, true,
                           PS_ERRORTEXT_psImageIO_EXTNAME_INVALID,
                           extname, filename, fitsErr);
                return false;
            }
        } else {
            int numHDUs = 0;

            fits_get_num_hdus(fptr, &numHDUs, &status);
            if (numHDUs < extnum) {
                status = 0;
                (void)fits_close_file(fptr, &status);
                psErrorMsg(PS_ERRORNAME_DOMAIN "psImageWriteSection",
                           PS_ERR_LOCATION_INVALID, true,
                           PS_ERRORTEXT_psImageIO_WRITE_EXTNUM_INVALID,
                           extnum, numHDUs);
                return false;
            } else if (numHDUs == extnum) {
                createNewHDU = true;
            } else if (fits_movabs_hdu(fptr, extnum + 1, &hduType, &status) != 0) {
                fits_get_errstatus(status, fitsErr);
                status = 0;
                (void)fits_close_file(fptr, &status);
                psErrorMsg(PS_ERRORNAME_DOMAIN "psImageWriteSection",
                           PS_ERR_LOCATION_INVALID, true,
                           PS_ERRORTEXT_psImageIO_EXTNUM_INVALID,
                           extnum, filename, fitsErr);
                return false;
            }
        }

    } else {                               // file
        // does
        // not
        // exist

        (void)fits_create_file(&fptr, filename, &status);
        if (fptr == NULL || status != 0) {
            fits_get_errstatus(status, fitsErr);
            psErrorMsg(PS_ERRORNAME_DOMAIN "psImageWriteSection",
                       PS_ERR_IO, true,
                       PS_ERRORTEXT_psImageIO_FILENAME_CREATE_FAILED,
                       filename, fitsErr);
            return false;
        }
        createNewHDU = true;
    }

    if (createNewHDU) {
        /* create the mandatory image keywords */
        nAxes[0] = col0 + numCols;
        nAxes[1] = row0 + numRows;
        nAxes[2] = z + 1;
        if (fits_create_img(fptr, bitPix, 3, nAxes, &status) != 0) {
            (void)fits_get_errstatus(status, fitsErr);
            status = 0;
            (void)fits_close_file(fptr, &status);
            psErrorMsg(PS_ERRORNAME_DOMAIN "psImageWriteSection",
                       PS_ERR_IO, true,
                       PS_ERRORTEXT_psImageIO_CREATE_HDU_FAILED,
                       filename, fitsErr);
            return false;
        }
        // set the bscale/bzero
        fits_write_key_dbl(fptr, "BZERO", bzero, 12, "Pixel Value Offset", &status);
        fits_write_key_dbl(fptr, "BSCALE", bscale, 12, "Pixel Value Scale", &status);
        fits_set_bscale(fptr, bscale, bzero, &status);

        if (extname != NULL) {
            /* create the extension for the Primary HDU */
            if (fits_write_key_str(fptr, "EXTNAME", extname, "Extension Name", &status) != 0) {
                (void)fits_get_errstatus(status, fitsErr);
                status = 0;
                (void)fits_close_file(fptr, &status);
                psErrorMsg(PS_ERRORNAME_DOMAIN "psImageWriteSection",
                           PS_ERR_IO, true,
                           PS_ERRORTEXT_psImageIO_CREATE_EXTENSION_FAILED,
                           filename, fitsErr);
                return false;
            }
        }
    }

    firstPixel[0] = col0 + 1;
    firstPixel[1] = row0 + 1;
    firstPixel[2] = z + 1;

    lastPixel[0] = firstPixel[0] + numCols - 1;
    lastPixel[1] = firstPixel[1] + numRows - 1;
    lastPixel[2] = z + 1;

    if (fits_write_subset(fptr, datatype, firstPixel, lastPixel, input->data.V[0], &status) != 0) {
        (void)fits_get_errstatus(status, fitsErr);
        status = 0;
        (void)fits_close_file(fptr, &status);
        psErrorMsg(PS_ERRORNAME_DOMAIN "psImageWriteSection",
                   PS_ERR_IO, true,
                   PS_ERRORTEXT_psImageIO_WRITE_FAILED,
                   filename, fitsErr);
        return false;
    }

    status = 0;
    (void)fits_close_file(fptr, &status);

    return true;
}
