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

#include "pmFPA.h"
#include "pmFPAConstruct.h"
#include "psAdditionals.h"

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// File-static functions
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Read data for a particular cell from the camera configuration
static psMetadata *getCellData(const psMetadata *camera, // The camera configuration
                               const char *cellName // The name of the cell
                              )
{
    bool status = true;                 // Result of MD lookup
    psMetadata *cells = psMetadataLookupMD(&status, camera, "CELLS"); // The CELLS
    if (! status) {
        psError(PS_ERR_IO, false, "Unable to determine CELLS of camera.\n");
        return NULL;
    }

    psMetadata *cellData = psMetadataLookupMD(&status, cells, cellName); // The data for the particular cell
    if (! status) {
        psError(PS_ERR_IO, false, "Unable to find specs for cell %s: ignored\n", cellName);
    }

    #if 0
    // Need to create a new instance, so that each cell can work with its own
    psMetadata *copy = psMetadataAlloc();
    psMetadataIterator *iter = psMetadataIteratorAlloc(cellData, PS_LIST_HEAD, NULL); // Iterator
    psMetadataItem *item = NULL;        // Item from iteration
    while (item = psMetadataGetAndIncrement(iter)) {
        if (item->type == PS_DATA_METADATA_MULTI || item->type == PS_DATA_METADATA) {
            psLogMsg(__func__, PS_LOG_WARN, "PS_DATA_METADATA_MULTI and PS_DATA_METADATA are not supported "
                     "in a cell definition --- %s ignored.\n", item->name);
            continue;
        }
        if (! psMetadataAdd(copy, PS_LIST_TAIL, item->name, item->type, item->comment, item->data.V)) {
            psAbort(__func__, "Should never reach here!\n");
        }
    }
    psFree(iter);

    return copy;
    #else

    return cellData;
    #endif

}


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

pmFPA *pmFPAConstruct(const psMetadata *camera // The camera configuration
                     )
{
    pmFPA *fpa = pmFPAAlloc(camera);    // The FPA to fill out

    bool mdStatus = true;               // Status from metadata lookups
    const char *phuType = psMetadataLookupStr(&mdStatus, camera, "PHU"); // What is the PHU?
    const char *extType = psMetadataLookupStr(&mdStatus, camera, "EXTENSIONS"); // What's in the extns?

    if (strcasecmp(phuType, "FPA") == 0) {
        // The FITS file contains an entire FPA

        psMetadata *contents = psMetadataLookupMD(&mdStatus, camera, "CONTENTS"); // The CONTENTS
        if (! mdStatus) {
            psError(PS_ERR_IO, false, "Unable to determine CONTENTS of camera.\n");
            psFree(fpa);
            return NULL;
        }

        // Set up iteration over the contents
        psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
        psMetadataItem *contentItem = NULL; // Item from the metadata

        if (strcasecmp(extType, "CHIP") == 0) {
            // Extensions are chips; Content contains a list of cells
            while ((contentItem = psMetadataGetAndIncrement(contentsIter))) {
                psString extName = contentItem->name; // The name of the extension
                pmChip *chip = pmChipAlloc(fpa, extName); // The chip
                chip->hdu = p_pmHDUAlloc(extName); // Prepare chip to receive FITS data
                if (contentItem->type != PS_DATA_STRING) {
                    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
                             contentItem->type);
                    continue;
                }

                const char *content = contentItem->data.V; // The content of the extension
                psTrace(__func__, 7, "Content of %s is: %s\n", extName, content);
                psList *cellNames = psStringSplit(content, " ,"); // A list of the component cells
                psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, NULL);
                char *cellName = NULL; // The name of a cell
                while ((cellName = psListGetAndIncrement(cellNamesIter))) {
                    // Get the cell data
                    psMetadata *cellData = getCellData(camera, cellName);
                    pmCell *cell = pmCellAlloc(chip, cellData, cellName); // The cell
                    psFree(cell);       // Drop reference
                    //                    psFree(cellData);   // Drop reference
                }
                psFree(cellNamesIter);
                psFree(cellNames);
                psFree(chip);           // Drop reference
            }

        } else if (strcasecmp(extType, "CELL") == 0) {
            // Extensions are cells; Content contains a chip name and cell type
            psMetadata *chips = psMetadataAlloc(); // Given a chip name, holds the chip number
            while ((contentItem = psMetadataGetAndIncrement(contentsIter))) {
                psString extName = contentItem->name; // The name of the extension
                psTrace(__func__, 1, "Getting %s....\n", extName);

                if (contentItem->type != PS_DATA_STRING) {
                    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
                             contentItem->type);
                    continue;
                }
                const char *content = contentItem->data.V; // The content of the extension
                psList *contents = psStringSplit(content, ": "); // Split the name from the type
                if (contents->n != 2) {
                    psLogMsg(__func__, PS_LOG_WARN, "Unable to read contents of %s: ignored.\n", extName);
                } else {
                    psString chipName = psListGet(contents, 0); // The name of the chip
                    psString cellType = psListGet(contents, 1); // The type of cell
                    psTrace(__func__, 7, "Extension is cell of type %s, from chip %s\n", cellType,
                            chipName);

                    psMetadataItem *chipItem = psMetadataLookup(chips, chipName); // Item containing the chip
                    pmChip *chip = NULL; // The chip
                    if (! chipItem) {
                        chip = pmChipAlloc(fpa, chipName);
                        psMetadataAdd(chips, PS_LIST_TAIL, chipName, PS_DATA_UNKNOWN, "", chip);
                    } else {
                        chip = psMemIncrRefCounter(chipItem->data.V);
                    }
                    // The cell
                    psMetadata *cellData = getCellData(camera, cellType);
                    pmCell *cell = pmCellAlloc(chip, cellData, extName); // The cell
                    //                  psFree(cellData); // Drop reference
                    cell->hdu = p_pmHDUAlloc(extName); // Prepare cell to receive FITS data

                    psFree(cell);       // Drop reference
                    psFree(chip);       // Drop reference
                }
                psFree(contents);
            }
            psFree(chips);

        } else if (strcasecmp(extType, "NONE") == 0) {
            // No extensions; Content contains metadata, each entry is a chip with its component cells
            fpa->hdu = p_pmHDUAlloc("PHU"); // Prepare FPA to receive FITS data
            while ((contentItem = psMetadataGetAndIncrement(contentsIter))) {
                psString chipName = contentItem->name; // The name of the chip

                if (contentItem->type != PS_DATA_STRING) {
                    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
                             contentItem->type);
                    continue;
                }
                psString content = contentItem->data.V; // The content of the extension
                psTrace(__func__, 5, "Component cells are: %s\n", content);
                pmChip *chip = pmChipAlloc(fpa, chipName); // The chip
                psList *cellNames = psStringSplit(content, ", "); // Split the list of cells
                psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false);
                char *cellName = NULL; // Name of the cell
                while ((cellName = psListGetAndIncrement(cellNamesIter))) {
                    psTrace(__func__, 7, "Processing cell %s....\n", cellName);
                    psMetadata *cellData = getCellData(camera, cellName);
                    pmCell *cell = pmCellAlloc(chip, cellData, cellName); // The cell
                    psFree(cell);       // Drop reference
                    //                  psFree(cellData);   // Drop reference
                }
                psFree(cellNamesIter);
                psFree(chip);           // Drop reference
            }

        } else {
            psError(PS_ERR_IO, false, "EXTENSIONS in camera definition is not CHIP, CELL or NONE.\n");
            psFree(fpa);
            return NULL;
        } // Type of extension

        psFree(contentsIter);

    } else if (strcasecmp(phuType, "CHIP") == 0) {
        // The FITS file contains a single chip only
        psString chipName = psStringCopy("CHIP"); // Name for chip
        pmChip *chip = pmChipAlloc(fpa, chipName); // The chip

        if (strcasecmp(extType, "NONE") == 0) {
            // There are no extensions --- only the PHU
            chip->hdu = p_pmHDUAlloc("PHU");

            const char *contents = psMetadataLookupStr(&mdStatus, camera, "CONTENTS");
            if (! mdStatus) {
                psError(PS_ERR_IO, false, "Unable to determine CONTENTS of camera.\n");
                psFree(fpa);
                return NULL;
            }
            psList *cellNames = psStringSplit(contents, " ,"); // Names of cells
            psListIterator *cellIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false); // Iterator
            psString cellName = NULL;
            while ((cellName = psListGetAndIncrement(cellIter))) {
                psMetadata *cellData = getCellData(camera, cellName);
                pmCell *cell = pmCellAlloc(chip, cellData, cellName); // The cell
                psFree(cell);
            }
            psFree(cellIter);
            psFree(cellNames);

        } else if (strcasecmp(extType, "CELL") == 0) {
            // Extensions are cells
            psMetadata *contents = psMetadataLookupMD(&mdStatus, camera, "CONTENTS"); // The CONTENTS
            if (! mdStatus) {
                psError(PS_ERR_IO, false, "Unable to determine CONTENTS of camera.\n");
                psFree(fpa);
                return NULL;
            }

            if (strcasecmp(extType, "CELL") != 0) {
                psLogMsg(__func__, PS_LOG_WARN, "EXTENSIONS in camera definition is %s, but PHU is CHIP.\n"
                         "EXTENSIONS assumed to be CELL.\n", extType);
            }

            // Iterate through the contents
            psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
            psMetadataItem *contentItem = NULL; // Item from metadata
            while ((contentItem = psMetadataGetAndIncrement(contentsIter))) {
                psString extName = contentItem->name; // The name of the extension
                // Content is a cell type
                if (contentItem->type != PS_DATA_STRING) {
                    psLogMsg(__func__, PS_LOG_WARN,
                             "CONTENT metadata for extension %s is not of type string, but %x --- ignored\n",
                             extName, contentItem->type);
                    continue;
                }
                const char *cellType = contentItem->data.V; // The type of cell
                psTrace(__func__, 5, "Cell type is %s\n", cellType);
                psMetadata *cellData = getCellData(camera, cellType);
                pmCell *cell = pmCellAlloc(chip, cellData, extName); // The cell
                //              psFree(cellData);
                cell->hdu = p_pmHDUAlloc(extName); // Prepare cell to receive FITS data

                psFree(cell);
            } // Iterating through contents
            psFree(contentsIter);

        } else {
            psError(PS_ERR_IO, false, "EXTENSIONS in camera definition is neither CELL or NONE.\n");
            psFree(fpa);
            return NULL;
        }
        psFree(chipName);
        psFree(chip);                   // Drop reference

    } else {
        psError(PS_ERR_IO, true,
                "The PHU type specified in the camera configuration (%s) is not FPA or CHIP.\n",
                phuType);
        psFree(fpa);
        return NULL;
    }

    return fpa;
}

// Print out the focal plane structure
void pmFPAPrint(pmFPA *fpa              // FPA to print
               )
{
    psTrace(__func__, 1, "FPA:\n");
    if (fpa->hdu) {
        psTrace(__func__, 2, "---> FPA is extension %s.\n", fpa->hdu->extname);
        if (! fpa->hdu->images) {
            psTrace(__func__, 2, "---> NO PIXELS for extension %s\n", fpa->hdu->extname);
        }
    }
    psMetadataPrint(fpa->concepts, 2);

    psArray *chips = fpa->chips;        // Array of chips
    // Iterate over the FPA
    for (int i = 0; i < chips->n; i++) {
        psTrace(__func__, 3, "Chip: %d\n", i);
        pmChip *chip = chips->data[i]; // The chip
        if (chip->hdu) {
            psTrace(__func__, 4, "---> Chip is extension %s.\n", chip->hdu->extname);
            if (! chip->hdu->images) {
                psTrace(__func__, 4, "---> NO PIXELS for extension %s\n", chip->hdu->extname);
            }
        }
        psMetadataPrint(chip->concepts, 4);

        // Iterate over the chip
        psArray *cells = chip->cells;   // Array of cells
        for (int j = 0; j < cells->n; j++) {
            psTrace(__func__, 5, "Cell: %d\n", j);
            pmCell *cell = cells->data[j]; // The cell
            if (cell->hdu) {
                psTrace(__func__, 6, "---> Cell is extension %s.\n", cell->hdu->extname);
                if (! cell->hdu->images) {
                    psTrace(__func__, 6, "---> NO PIXELS for extension %s\n", cell->hdu->extname);
                }
            }
            psMetadataPrint(cell->concepts, 6);

            psTrace(__func__, 7, "Readouts:\n");
            psArray *readouts = cell->readouts; // Array of readouts
            for (int k = 0; k < readouts->n; k++) {
                pmReadout *readout = readouts->data[k]; // The readout
                psImage *image = readout->image; // The image
                psTrace(__func__, 8, "Image: [%d:%d,%d:%d] (%dx%d)\n", image->col0, image->col0 +
                        image->numCols, image->row0, image->row0 + image->numRows, image->numCols,
                        image->numRows);
            } // Iterating over cell
        } // Iterating over chip
    } // Iterating over FPA

}
