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

#include "pmFPA.h"
#include "pmFPARead.h"
#include "pmConcepts.h"

static bool writeHDU(psFits *fits,      // FITS file to which to write
                     p_pmHDU *hdu       // Pixel data to write
                    )
{
    bool status = true;                 // Status of write, to return
    for (int i = 0; i < hdu->images->n; i++) {
        status &= psFitsWriteImage(fits, hdu->header, hdu->images->data[i], i);
        // XXX: Insert here the writing on mask and weight images
    }

    return status;
}


bool pmFPAWrite(psFits *fits,           // FITS file to which to write
                pmFPA *fpa,             // FPA to write
                psDB *db                // Database to update
               )
{
    bool status = true;                 // Status of writing, to return

    psTrace(__func__, 1, "Writing FPA...\n");

    psTrace(__func__, 7, "Outgesting FPA concepts...\n");
    pmConceptsWriteFPA(fpa, db);

    // Write the primary header
    if (fpa->phu) {
        status &= psFitsMoveExtNum(fits, 0, false);
        status &= psFitsWriteHeader(fpa->phu, fits);
    }

    psArray *chips = fpa->chips;        // Array of component chips
    for (int i = 0; i < chips->n; i++) {
        pmChip *chip = chips->data[i];  // The component chip
        if (chip && chip->exists && chip->process) {
            psTrace(__func__, 1, "Writing out chip %d...\n", i);

            pmConceptsWriteChip(chip, db);

            psArray *cells = chip->cells;       // Array of component cells
            for (int j = 0; j < cells->n; j++) {
                pmCell *cell = cells->data[j]; // The component cell
                if (cell && cell->exists && cell->process) {
                    psTrace(__func__, 2, "Writing out cell, %d...\n", j);

                    pmConceptsWriteCell(cell, db);

                    if (cell->hdu && strlen(cell->hdu->extname) > 0) {
                        status &= writeHDU(fits, cell->hdu);
                    }
                }
            }

            if (chip->hdu && strlen(chip->hdu->extname) > 0) {
                status &= writeHDU(fits, chip->hdu);
            }
        }

    }

    if (fpa->hdu && strlen(fpa->hdu->extname) > 0) {
        status &= writeHDU(fits, fpa->hdu);
    }

    psTrace(__func__, 1, "Done writing FPA...\n");

    return status;
}


bool pmFPAWriteMask(pmFPA *fpa,         // FPA containing mask to write
                    psFits *fits        // FITS file for image
                   )
{
    const psMetadata *camera = fpa->camera; // Camera configuration for FPA
    bool mdok = false;                  // Status of MD lookup

    // Get the required information from the camera configuration
    psMetadata *supps = psMetadataLookupMD(&mdok, camera, "SUPPLEMENTARY"); // Rules for supplementary data
    if (! mdok || ! supps) {
        psError(PS_ERR_IO, false, "Unable to find SUPPLEMENTARY in camera configuration!\n");
        return false;
    }
    psString sourceType = psMetadataLookupStr(&mdok, supps, "MASK.SOURCE"); // Type of source: EXT | FILE
    if (! mdok || strlen(sourceType) <= 0) {
        psError(PS_ERR_IO, false, "Unable to find MASK.SOURCE in SUPPLEMENTARY section of camera "
                "configuration!\n");
        return false;
    }
    psString name = psMetadataLookupStr(&mdok, supps, "MASK.NAME"); // Name of mask
    if (! mdok || strlen(sourceType) <= 0) {
        psError(PS_ERR_IO, false, "Unable to find MASK.NAME in SUPPLEMENTARY section of camera "
                "configuration!\n");
        return false;
    }

    // Go through the FPA to each cell/readout to get the mask
    p_pmHDU *hdu = fpa->hdu;            // The HDU into which we will read the mask
    psArray *chips = fpa->chips;        // Array of chips
    for (int chipNum = 0; chipNum < chips->n; chipNum++) {
        pmChip *chip = chips->data[chipNum]; // The current chip of interest
        if (chip->exists && chip->process) {
            if (chip->hdu) {
                hdu = chip->hdu;
            }
            psArray *cells = chip->cells;       // Array of cells
            for (int cellNum = 0; cellNum < cells->n; cellNum++) {
                pmCell *cell = cells->data[cellNum]; // The current cell of interest
                if (cell->exists && cell->process) {
                    if (cell->hdu) {
                        hdu = cell->hdu;
                    }

                    // Now, need to find out where to write the pixels
                    psFits *maskDest = psMemIncrRefCounter(fits); // Destination of mask image
                    psMetadata *header = psMetadataAlloc(); // A dummy header, containing the extension name
                    if (strcasecmp(sourceType, "FILE") == 0) {
                        // Source is a file (with optional extension, e.g., "myMaskFile.fits:thisExt"
                        psString extname = NULL; // Extension name
                        psString filename = p_pmFPATranslateFileExt(&extname, name, cell); // The filename
                        psFree(maskDest);
                        maskDest = psFitsOpen(filename, "rw");
                        if (extname) {
                            psMetadataAddStr(header, PS_LIST_TAIL, "EXTNAME", 0, "Extension name", extname);
                        }
                        psFree(filename);
                        psFree(extname);
                    } else if (strncasecmp(sourceType, "EXT", 3) == 0) {
                        // Source is an extension in the original file
                        psString extname = p_pmFPATranslateName(name, cell);
                        psMetadataAddStr(header, PS_LIST_TAIL, "EXTNAME", 0, "Extension name", extname);
                        psFree(extname);
                    }

                    // We've arrived where the pixels are.  Now we need to write them out.
                    psArray *readouts = cell->readouts; // The array of readouts
                    for (int readNum = 0; readNum < readouts->n; readNum++) {
                        pmReadout *readout = readouts->data[readNum]; // The readout of interest
                        if (! readout->mask) {
                            psLogMsg(__func__, PS_LOG_WARN, "No mask to write out in %d,%d,%d\n",
                                     chipNum, cellNum, readNum);
                        } else {
                            // XXX: Need to add the extname to the existing header
                            if (! psFitsWriteImage(maskDest, header, readout->mask, readNum)) {
                                psError(PS_ERR_IO, false, "Unable to write mask plane %d in extension %s\n",
                                        readNum, hdu->extname);
                                return false;
                            }
                        }
                    } // Iterating over readouts
                    psFree(header);
                    psFree(maskDest);
                } // Valid cells
            } // Iterating over cells
        } // Valid chips
    } // Iterating over chips

    return true;
}


bool pmFPAWriteWeight(pmFPA *fpa,       // FPA containing mask to write
                      psFits *fits      // FITS file for image
                     )
{
    const psMetadata *camera = fpa->camera; // Camera configuration for FPA
    bool mdok = false;                  // Status of MD lookup

    // Get the required information from the camera configuration
    psMetadata *supps = psMetadataLookupMD(&mdok, camera, "SUPPLEMENTARY"); // Rules for supplementary data
    if (! mdok || ! supps) {
        psError(PS_ERR_IO, false, "Unable to find SUPPLEMENTARY in camera configuration!\n");
        return false;
    }
    psString sourceType = psMetadataLookupStr(&mdok, supps, "WEIGHT.SOURCE"); // Type of source: EXT | FILE
    if (! mdok || strlen(sourceType) <= 0) {
        psError(PS_ERR_IO, false, "Unable to find WEIGHT.SOURCE in SUPPLEMENTARY section of camera "
                "configuration!\n");
        return false;
    }
    psString name = psMetadataLookupStr(&mdok, supps, "WEIGHT.NAME"); // Name of weight
    if (! mdok || strlen(sourceType) <= 0) {
        psError(PS_ERR_IO, false, "Unable to find WEIGHT.NAME in SUPPLEMENTARY section of camera "
                "configuration!\n");
        return false;
    }

    // Go through the FPA to each cell/readout to get the weight
    p_pmHDU *hdu = fpa->hdu;            // The HDU into which we will read the weight
    psArray *chips = fpa->chips;        // Array of chips
    for (int chipNum = 0; chipNum < chips->n; chipNum++) {
        pmChip *chip = chips->data[chipNum]; // The current chip of interest
        if (chip->exists && chip->process) {
            if (chip->hdu) {
                hdu = chip->hdu;
            }
            psArray *cells = chip->cells;       // Array of cells
            for (int cellNum = 0; cellNum < cells->n; cellNum++) {
                pmCell *cell = cells->data[cellNum]; // The current cell of interest
                if (cell->exists && cell->process) {
                    if (cell->hdu) {
                        hdu = cell->hdu;
                    }

                    // Now, need to find out where to write the pixels
                    psFits *weightDest = psMemIncrRefCounter(fits); // Destination of weight image
                    psMetadata *header = psMetadataAlloc(); // A dummy header, containing the extension name
                    if (strcasecmp(sourceType, "FILE") == 0) {
                        // Source is a file (with optional extension, e.g., "myWeightFile.fits:thisExt"
                        psString extname = NULL; // Extension name
                        psString filename = p_pmFPATranslateFileExt(&extname, name, cell); // The filename

                        psFree(weightDest);
                        weightDest = psFitsOpen(filename, "rw");
                        if (extname) {
                            psMetadataAddStr(header, PS_LIST_TAIL, "EXTNAME", 0, "Extension name", extname);
                        }
                        psFree(filename);
                        psFree(extname);
                    } else if (strncasecmp(sourceType, "EXT", 3) == 0) {
                        // Source is an extension in the original file
                        psString extname = p_pmFPATranslateName(name, cell);
                        psMetadataAddStr(header, PS_LIST_TAIL, "EXTNAME", 0, "Extension name", extname);
                        psFree(extname);
                    }

                    // We've arrived where the pixels are.  Now we need to write them out.
                    psArray *readouts = cell->readouts; // The array of readouts
                    for (int readNum = 0; readNum < readouts->n; readNum++) {
                        pmReadout *readout = readouts->data[readNum]; // The readout of interest
                        if (! readout->weight) {
                            psLogMsg(__func__, PS_LOG_WARN, "No weight image to write out in %d,%d,%d\n",
                                     chipNum, cellNum, readNum);
                        } else {
                            if (! psFitsWriteImage(weightDest, header, readout->weight, readNum)) {
                                psError(PS_ERR_IO, false, "Unable to write weight plane %d in extension %s\n",
                                        readNum, hdu->extname);
                                return false;
                            }
                        }
                    } // Iterating over readouts
                    psFree(header);
                    psFree(weightDest);
                } // Valid cells
            } // Iterating over cells
        } // Valid chips
    } // Iterating over chips

    return true;
}
