#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <pslib.h>

#include "pmConfig.h"
#include "pmHDU.h"
#include "pmFPA.h"

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File-static (private) functions
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Move to the appropriate extension in FITS file for HDU
static bool hduMove(pmHDU *hdu,         // HDU with extname
                    psFits *fits        // FITS file in which to move
                   )
{
    // Deal with the PHU case
    if (hdu->blankPHU || !hdu->extname) {
        if (!psFitsMoveExtNum(fits, 0, false)) {
            psError(PS_ERR_IO, false, "Unable to move to primary header!\n");
            return false;
        }
        return true;
    }

    if (!psFitsMoveExtName(fits, hdu->extname)) {
        psError(PS_ERR_IO, false, "Unable to move to extension %s\n", hdu->extname);
        return false;
    }

    return true;
}

static void hduFree(pmHDU *hdu)
{
    psFree(hdu->extname);
    psFree(hdu->format);
    psFree(hdu->header);
    psFree(hdu->images);
    psFree(hdu->weights);
    psFree(hdu->masks);
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Public functions
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

pmHDU *pmHDUAlloc(const char *extname)
{
    pmHDU *hdu = psAlloc(sizeof(pmHDU));
    psMemSetDeallocator(hdu, (psFreeFunc)hduFree);

    if (!extname || strlen(extname) == 0) {
        hdu->blankPHU = true;
        hdu->extname = NULL;
    } else {
        hdu->blankPHU = false;
        hdu->extname = psStringCopy(extname);
    }
    hdu->format  = NULL;
    hdu->header  = NULL;
    hdu->images  = NULL;
    hdu->weights = NULL;
    hdu->masks   = NULL;

    return hdu;
}

bool pmHDUReadHeader(pmHDU *hdu, psFits *fits)
{
    PS_ASSERT_PTR_NON_NULL(hdu, false);
    PS_ASSERT_PTR_NON_NULL(fits, false);

    // Move to the appropriate extension
    psTrace("psModules.camera", 5, "Moving to extension %s...\n", hdu->extname);
    if (!hduMove(hdu, fits)) {
        return false;
    }

    if (!hdu->header) {
        psTrace("psModules.camera", 5, "Reading the header...\n");
        hdu->header = psFitsReadHeader(hdu->header, fits);
        if (! hdu->header) {
            psError(PS_ERR_IO, false, "Unable to read header for extension %s\n", hdu->extname);
            return false;
        }
    }

    return true;
}

// Read an HDU from a FITS file
// XXX: Add a region specifier?
bool hduRead(pmHDU *hdu,                // HDU to write
             psArray **images,          // Images into which to read
             psFits *fits               // FITS file to read
            )
{
    assert(hdu);
    assert(images);
    assert(fits);

    // Read the header; includes the move
    if (!pmHDUReadHeader(hdu, fits)) {
        return false;
    }

    if (hdu->blankPHU) {
        // Done already!
        return true;
    }

    if (*images) {
        psLogMsg(__func__, PS_LOG_WARN, "HDU %s has already been read --- overwriting.\n", hdu->extname);
        psFree(*images);                // Blow away anything existing
    }
    psTrace("psModules.camera", 5, "Reading the pixels...\n");
    *images = psFitsReadImageCube(fits, psRegionSet(0,0,0,0));
    if (!*images) {
        psError(PS_ERR_IO, false, "Unable to read pixels for extension %s\n", hdu->extname);
        return false;
    }
    return true;
}

bool pmHDURead(pmHDU *hdu, psFits *fits)
{
    PS_ASSERT_PTR_NON_NULL(hdu, false);
    PS_ASSERT_PTR_NON_NULL(fits, false);

    return hduRead(hdu, &hdu->images, fits);
}

bool pmHDUReadMask(pmHDU *hdu, psFits *fits)
{
    PS_ASSERT_PTR_NON_NULL(hdu, false);
    PS_ASSERT_PTR_NON_NULL(fits, false);

    return hduRead(hdu, &hdu->masks, fits);
}

bool pmHDUReadWeight(pmHDU *hdu, psFits *fits)
{
    PS_ASSERT_PTR_NON_NULL(hdu, false);
    PS_ASSERT_PTR_NON_NULL(fits, false);

    return hduRead(hdu, &hdu->weights, fits);
}

// Write an HDU to a FITS file
static bool hduWrite(pmHDU *hdu,        // HDU to write
                     psArray *images,   // Images to write
                     psFits *fits       // FITS file to which to write
                    )
{
    assert(hdu);
    assert(fits);

    psTrace("psModules.camera", 7, "Writing HDU %s\n", hdu->extname);

    if (!images && !hdu->header) {
        psLogMsg(__func__, PS_LOG_WARN, "Nothing to write for HDU %s\n", hdu->extname);
        return false;
    }

    // Preserve the extension name, if it's the PHU
    char *extname = hdu->extname;       // The name of the extension
    if (!extname && hdu->header) {
        bool mdok = true;               // Status of MD lookup
        extname = psMetadataLookupStr(&mdok, hdu->header, "EXTNAME");
        if (!mdok || !extname || strlen(extname) == 0) {
            extname = "";
        }
    }

    // Make sure it's recognisable as what it's supposed to be
    if (!hdu->header) {
        hdu->header = psMetadataAlloc();
    }
    if (!pmConfigConformHeader(hdu->header, hdu->format)) {
        psError(PS_ERR_UNKNOWN, false, "Unable to conform header to format.\n");
        return false;
    }

    // Only a header
    if (!images && !psFitsWriteBlank(fits, hdu->header, extname)) {
        psError(PS_ERR_IO, false, "Unable to write header for extension %s\n", extname);
        return false;
    }

    if (images) {
        psTrace("psModules.camera", 9, "Writing pixels for %s\n", hdu->extname);
        if (!psFitsWriteImageCube(fits, hdu->header, images, extname)) {
            psError(PS_ERR_IO, false, "Unable to write image to extension %s\n", hdu->extname);
            return false;
        }
    }
    return true;
}

// XXX: Add a region specifier?
bool pmHDUWrite(pmHDU *hdu, psFits *fits)
{
    PS_ASSERT_PTR_NON_NULL(hdu, false);
    PS_ASSERT_PTR_NON_NULL(fits, false);

    return hduWrite(hdu, hdu->images, fits);
}

bool pmHDUWriteMask(pmHDU *hdu, psFits *fits)
{
    PS_ASSERT_PTR_NON_NULL(hdu, false);
    PS_ASSERT_PTR_NON_NULL(fits, false);

    return hduWrite(hdu, hdu->masks, fits);
}

bool pmHDUWriteWeight(pmHDU *hdu, psFits *fits)
{
    PS_ASSERT_PTR_NON_NULL(hdu, false);
    PS_ASSERT_PTR_NON_NULL(fits, false);

    return hduWrite(hdu, hdu->weights, fits);
}
