#include <stdio.h>
#include <string.h>
#include "pslib.h"
#include "papmodule.h"
#include "papStuff.h"
#include "papFocalPlane.h"
#include "pmFPAConstruct.h"

// NOTE: Need to deal with header inheritance

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

// Copy metadata elements into another metadata
static void metadataCopy(psMetadata *to, // Metadata to which to copy
			 psMetadata *from // Metdata from which to copy
    )
{
    psMetadataIterator *mdIter = psMetadataIteratorAlloc(from, PS_LIST_HEAD, NULL); // Iterator for the MD
    psMetadataItem *item = NULL;	// Item from the metadata
    while (item = psMetadataGetAndIncrement(mdIter)) {
	psTrace(__func__, 9, "Adding %s to metadata...\n", item->name);
	if (! psMetadataAddItem(to, item, PS_LIST_TAIL, PS_META_REPLACE)) {
	    psLogMsg(__func__, PS_LOG_WARN, "Unable to add item (%s: %s) to metadata: ignored\n", item->name,
		     item->comment);
	}
    }
    psFree(mdIter);
}

// 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);
	return NULL;
    } else {
	return cellData;
    }
}


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

papFPA *pmFPAConstruct(const psMetadata *camera, // The camera configuration
		       psDB *db		// Database handle
    )
{
    papFPA *fpa = papFPAAlloc(camera, db); // The FPA to fill out
    bool mdStatus = true;		// Status from metadata lookups
    const char *phuType = psMetadataLookupString(&mdStatus, camera, "PHU"); // What is the PHU?
    const char *extType = psMetadataLookupString(&mdStatus, camera, "EXTENSIONS"); // What's in the extensions?
    if (strncmp(phuType, "FPA", 3) == 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 (strncmp(extType, "CHIP", 4) == 0) {
	    // Extensions are chips; Content contains a list of cells
	    while (contentItem = psMetadataGetAndIncrement(contentsIter)) {
		psString extName = contentItem->name; // The name of the extension
		papChip *chip = papChipAlloc(fpa, extName); // The chip
		chip->extname = extName;// Mark chip to receive FITS data
		if (contentItem->type != PS_META_STR) {
		    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 = papSplit(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
		    papCell *cell = papCellAlloc(chip, cellName); // The cell
		    psMetadata *cellData = getCellData(camera, cellName);
		    metadataCopy(cell->values, cellData);
		}
		psFree(cellNamesIter);
		psFree(cellNames);
	    }

	} else if (strncmp(extType, "CELL", 4) == 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_META_STR) {
		    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 = papSplit(content, ": "); // Split the name from the type
		if (contents->size != 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);
		    
		    papChip *chip = psMetadataLookupChip(&mdStatus, chips, chipName); // The chip
		    if (! mdStatus && ! chip) {
			chip = papChipAlloc(fpa, chipName);
			psMetadataAdd(chips, PS_LIST_TAIL, chipName, PS_META_CHIP, "", chip);
		    }
		    // The cell
		    psArray *images = NULL;
		    psMetadata *header = NULL;
		    papCell *cell = papCellAlloc(chip, extName); // The cell
		    cell->extname = extName; // Mark cell to receive FITS data
		    psMetadata *cellData = getCellData(camera, cellType);
		    metadataCopy(cell->values, cellData);
		}
	    }
	    psFree(chips);

	} else if (strncmp(extType, "NONE", 4) == 0) {
	    // No extensions; Content contains metadata, each entry is a chip with its component cells
	    fpa->extname = "PHU";
	    while (contentItem = psMetadataGetAndIncrement(contentsIter)) {
		psString chipName = contentItem->name; // The name of the chip

		if (contentItem->type != PS_META_STR) {
		    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);
		papChip *chip = papChipAlloc(fpa, chipName); // The chip
		psList *cellNames = papSplit(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);
		    papCell *cell = papCellAlloc(chip, cellName); // The cell
		    psMetadata *cellData = getCellData(camera, cellName);
		    metadataCopy(cell->values, cellData);
		}
		psFree(cellNamesIter);
	    }

	} 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 (strncmp(phuType, "CHIP", 4) == 0) {
	// The FITS file contains a single chip only
	psString chipName = psStringCopy("CHIP"); // Name for chip
	papChip *chip = papChipAlloc(fpa, chipName); // The chip

	if (strncmp(extType, "NONE", 4) == 0) {
	    // There are no extensions --- only the PHU
	    chip->extname = "PHU";

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

	} else if (strncmp(extType, "CELL", 4) == 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 (strncmp(extType, "CELL", 4) != 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_META_STR) {
		    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);
		psArray *images = NULL;
		psMetadata *header = NULL;
		papCell *cell = papCellAlloc(chip, extName); // The cell
		cell->extname = extName; // Mark cell to receive FITS data
		psMetadata *cellData = getCellData(camera, cellType);
		metadataCopy(cell->values, cellData);

	    } // 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;
	}

    } 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(papFPA *fpa		// FPA to print
    )
{
    psTrace(__func__, 0, "FPA:\n");
    if (fpa->extname) {
	psTrace(__func__, 1, "---> FPA is extension %s.\n", fpa->extname);
	if (! fpa->pixels) {
	    psTrace(__func__, 1, "---> NO PIXELS for extension %s\n", fpa->extname);
	}
    }

    psArray *chips = fpa->chips;	// Array of chips
    // Iterate over the FPA
    for (int i = 0; i < chips->n; i++) {
	psTrace(__func__, 1, "Chip: %d\n", i);
	papChip *chip = chips->data[i]; // The chip
	if (chip->extname) {
	    psTrace(__func__, 2, "---> Chip is extension %s.\n", chip->extname);
	    if (! chip->pixels) {
		psTrace(__func__, 2, "---> NO PIXELS for extension %s\n", chip->extname);
	    }
	}
	// Iterate over the chip
	psArray *cells = chip->cells;	// Array of cells
	for (int j = 0; j < cells->n; j++) {
	    psTrace(__func__, 2, "Cell: %d\n", j);
	    papCell *cell = cells->data[j]; // The cell
	    if (cell->extname) {
		psTrace(__func__, 3, "---> Cell is extension %s.\n", cell->extname);
		if (! cell->pixels) {
		    psTrace(__func__, 3, "---> NO PIXELS for extension %s\n", cell->extname);
		}
	    }
	    psMetadataPrint(cell->values, 3);
	    psTrace(__func__, 3, "Readouts:\n");
	    psArray *readouts = cell->readouts;	// Array of readouts
	    for (int k = 0; k < readouts->n; k++) {
		papReadout *readout = readouts->data[k]; // The readout
		psImage *image = readout->image; // The image
		psTrace(__func__, 4, "Image: [%d:%d,%d:%d] (%dx%d)\n", image->col0, image->col0 + 
			image->numCols, image->row0, image->row0 + image->numRows, image->numCols,
			image->numRows);
		psList *overscans = readout->overscans;	// The list of overscans
		psListIterator *overscansIter = psListIteratorAlloc(overscans, PS_LIST_HEAD, false);
		while (image = psListGetAndIncrement(overscansIter)) {
		    psTrace(__func__, 4, "Overscan: [%d:%d,%d:%d] (%dx%d)\n", image->col0, image->col0 + 
			    image->numCols, image->row0, image->row0 + image->numRows, image->numCols,
			    image->numRows);
		}
		psFree(overscansIter);

	    } // Iterating over cell
	} // Iterating over chip
    } // Iterating over FPA

}
