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

#include <stdio.h>
#include <strings.h>
#include <pslib.h>

#include "pmFPA.h"
#include "pmHDU.h"
#include "pmHDUUtils.h"
#include "pmHDUGenerate.h"
#include "pmConcepts.h"

#include "pmFPAWrite.h"

bool pmReadoutWriteNext(pmReadout *readout, // Readout to write
                        psFits *fits,   // FITS file to which to write
                        int z           // Image plane to write
                       )
{
    PS_ASSERT_PTR_NON_NULL(readout, false);
    PS_ASSERT_PTR_NON_NULL(fits, false);

    pmHDU *hdu = pmHDUFromReadout(readout); // The HDU to which to write
    if (!hdu) {
        psError(PS_ERR_IO, false, "Unable to find HDU for readout.\n");
        return false;
    }
    psMetadata *header = hdu->header;   // The FITS header
    if (!header) {
        psError(PS_ERR_IO, true, "No FITS header available in the HDU.\n");
        return false;
    }

    // We have to rely to a great extent on the FITS header, because otherwise we simply don't know how many
    // image planes there are (NAXIS3) or the size of the original image (NAXIS1, NAXIS2).
    bool mdok = true;                   // Status of MD lookup
    int naxis1 = psMetadataLookupS32(&mdok, header, "NAXIS1"); // Number of columns
    if (!mdok || naxis1 <= 0) {
        psError(PS_ERR_IO, true, "Can't find NAXIS1 in header.\n");
        return false;
    }
    int naxis2 = psMetadataLookupS32(&mdok, header, "NAXIS2"); // Number of rows
    if (!mdok || naxis2 <= 0) {
        psError(PS_ERR_IO, true, "Can't find NAXIS2 in header.\n");
        return false;
    }
    int naxis3 = psMetadataLookupS32(&mdok, header, "NAXIS3"); // Number of image planes
    if (!mdok || naxis3 <= 0) {
        naxis3 = 1;
    }
    if (z >= naxis3) {
        psError(PS_ERR_IO, true, "Specified a plane number (%d) greater than NAXIS3 allows.\n", z);
        return false;
    }

    if (!hdu->images) {
        psError(PS_ERR_IO, true, "No images allocated in HDU.\n");
        return false;
    }
    psImage *image = readout->image; // The image from the HDU to write
    if (readout->row0 == 0 && readout->col0 == 0 && z == 0) {
        // Then we can assume that nothing has been written to the FITS file for now
        if (naxis1 == image->numCols && naxis2 == image->numRows) {
            // We can write the whole lot at once
            return psFitsWriteImage(fits, header, image, z, hdu->extname);
        }
        // Create a dummy image so we can write something larger than we actually have
        psImage *dummy = psImageAlloc(naxis1, naxis2, image->type.type); // Dummy image
        psImageInit(dummy, 0);
        psImageOverlaySection(dummy, image, 0, 0, "=");
        bool result = psFitsWriteImage(fits, header, dummy, z, hdu->extname);
        psFree(dummy);
        return result;
    }

    // We can simply update an existing HDU
    if (hdu->blankPHU && !psFitsMoveExtNum(fits, 0, false)) {
        psError(PS_ERR_IO, false, "Unable to move to PHU\n");
        return false;
    }
    return psFitsUpdateImage(fits, image, readout->col0, readout->row0, z);
}




bool pmCellWrite(pmCell *cell,          // Cell to write
                 psFits *fits,          // FITS file to which to write
                 psDB *db,              // Database handle for "concepts" update
                 bool blank             // Write a blank PHU?
                )
{
    PS_ASSERT_PTR_NON_NULL(cell, false);
    PS_ASSERT_PTR_NON_NULL(fits, false);

    psTrace ("pmFPAWrite", 5, "writing to Cell (%d)\n", blank);

    pmHDU *hdu = cell->hdu;             // The HDU
    if (!hdu) {
        return true;                    // We wrote every HDU that exists
    }

    // Generate the HDU if needed --- this is required after a pmFPACopy, or similar, which does not
    // generate the HDU, but only copies the structure.
    if (!hdu->blankPHU && !hdu->images) {
        if (!pmHDUGenerateForCell(cell) || !hdu->images) {
            psAbort(__func__, "Unable to generate HDU for cell --- likely programming error.\n");
        }
    }

    // We only write out a blank PHU if it's specifically requested.
    bool writeBlank = blank && hdu->blankPHU && !hdu->images; // Write a blank PHU?
    bool writeImage = !blank && !hdu->blankPHU && hdu->images; // Write an image?

    if (writeBlank || writeImage) {
        pmConceptSource source = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CAMERA |
                                 PM_CONCEPT_SOURCE_DEFAULTS;
        if (!pmConceptsWriteCell(cell, source, false, NULL)) {
            psError(PS_ERR_IO, false, "Unable to write concepts for cell.\n");
            return false;
        }
        if (!pmHDUWrite(hdu, fits)) {
            psError(PS_ERR_IO, false, "Unable to write HDU for cell.\n");
            return false;
        }
    }
    // No lower levels to which to recurse

    return true;
}


bool pmChipWrite(pmChip *chip,          // Chip to write
                 psFits *fits,          // FITS file to which to write
                 psDB *db,              // Database handle for "concepts" update
                 bool blank,            // Write a blank PHU?
                 bool recurse           // Recurse to lower levels?
                )
{
    PS_ASSERT_PTR_NON_NULL(chip, false);
    PS_ASSERT_PTR_NON_NULL(fits, false);

    pmHDU *hdu = chip->hdu;             // The HDU

    psTrace ("pmFPAWrite", 5, "writing to Chip (%d, %d)\n", blank, recurse);

    // If we have data at this level, try to write it out
    if (hdu) {
        // Generate the HDU if needed --- this is required after a pmFPACopy, or similar, which does not
        // generate the HDU, but only copies the structure.
        if (!blank && !hdu->blankPHU && !hdu->images) {
            if (!pmHDUGenerateForChip(chip) || !hdu->images) {
                psAbort (__func__, "Unable to generate HDU for chip --- likely programming error.\n");
            }
        }

        // We only write out a blank PHU if it's specifically requested.
        bool writeBlank = blank && hdu->blankPHU && !hdu->images; // Write a blank HDU?
        bool writeImage = !blank && !hdu->blankPHU && hdu->images; // Write an image?

        if (writeBlank || writeImage) {
            pmConceptSource source = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CAMERA |
                                     PM_CONCEPT_SOURCE_DEFAULTS;
            if (!pmConceptsWriteChip(chip, source, false, true, NULL)) {
                psError(PS_ERR_IO, false, "Unable to write concepts for chip.\n");
                return false;
            }
            if (!pmHDUWrite(hdu, fits)) {
                psError(PS_ERR_IO, false, "Unable to write HDU for chip.\n");
                return false;
            }
        }
    }

    // Recurse to lower level if specifically requested.
    // XXX recursion implies blank == false (must be called on correct level?)
    if (recurse) {
        psArray *cells = chip->cells;       // Array of cells
        for (int i = 0; i < cells->n; i++) {
            pmCell *cell = cells->data[i];  // The cell of interest
            if (!pmCellWrite(cell, fits, db, false)) {
                psError(PS_ERR_IO, false, "Unable to write Chip.\n");
                return false;
            }
        }
    }

    return true;
}



bool pmFPAWrite(pmFPA *fpa,             // FPA to write
                psFits *fits,           // FITS file to which to write
                psDB *db,               // Database handle for "concepts" update
                bool blank,             // Write a blank PHU?
                bool recurse            // Recurse to lower levels?
               )
{
    PS_ASSERT_PTR_NON_NULL(fpa, false);
    PS_ASSERT_PTR_NON_NULL(fits, false);

    pmHDU *hdu = fpa->hdu;              // The HDU

    psTrace ("pmFPAWrite", 5, "writing to FPA (%d, %d)\n", blank, recurse);

    // If we have data at this level, try to write it out
    if (hdu) {
        // Generate the HDU if needed --- this is required after a pmFPACopy, or similar, which does not
        // generate the HDU, but only copies the structure.
        if (!blank && !hdu->blankPHU && !hdu->images) {
            if (!pmHDUGenerateForFPA(fpa)) {
                psAbort("pmFPAWrite", "error generating HDU");
            }
            if (!hdu->images) {
                psAbort("pmFPAWrite", "programming error: failure generating HDU");
            }
        }

        // We only write out a blank PHU if it's specifically requested.
        bool writeBlank = blank && hdu->blankPHU && !hdu->images; // Write a blank PHU?
        bool writeImage = !blank && !hdu->blankPHU && hdu->images; // Write an image?

        if (writeBlank || writeImage) {
            pmConceptSource source = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CAMERA |
                                     PM_CONCEPT_SOURCE_DEFAULTS;
            if (!pmConceptsWriteFPA(fpa, source, true, NULL)) {
                psError(PS_ERR_IO, false, "Unable to write concepts for FPA.\n");
                return false;
            }
            if (!pmHDUWrite(hdu, fits))  {
                psError(PS_ERR_IO, false, "Unable to write HDU for FPA.\n");
                return false;
            }
        }
    }

    // Recurse to lower levels if requested
    if (recurse) {
        psArray *chips = fpa->chips;        // Array of chips
        for (int i = 0; i < chips->n; i++) {
            pmChip *chip = chips->data[i];  // The chip of interest
            if (!pmChipWrite(chip, fits, db, false, true)) {
                psError(PS_ERR_IO, false, "Unable to write FPA.\n");
                return false;
            }
        }
    }

    return true;
}
