#include <stdio.h>
#include <string.h>
#include "pslib.h"
#include "psmodule.h"

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

// Copy a metadata
static psMetadata *metadataCopy(psMetadata *md)
{
    psMetadata *new = psMetadataAlloc();// The metadata to return
    psMetadataIterator *mdIter = psMetadataIteratorAlloc(md, PS_LIST_HEAD, NULL); // Iterator for the MD
    psMetadataItem *item = NULL;	// Item from the metadata
    while (item = psMetadataGetAndIncrement(mdIter)) {
	if (! psMetadataAddItem(new, 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);

    return new;
}

// 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 {
	psMetadata *newCell = metadataCopy(cellData); // A copy of the cell, to be returned
	return newCell;
    }
}
	
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Public functions
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

psMetadata *pmFPAfromHeader(const psMetadata *camera // The camera configuration
    )
{
 
#if 0
    psMetadata *headers = psFitsReadHeaderSet(fitsFile); // The headers of every extension
    if (! headers) {
	psError(PS_ERR_BAD_DATA, false, "Unable to read FITS headers.\n");
	return NULL;
    }
#endif

    // Generate a build tree
    psMetadata *fpa = psMetadataAlloc(); // 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 (strcmp(phuType, "FPA") == 0) {
	// The FITS file contains an entire FPA

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

	if (strcmp(extType, "CHIP") == 0) {
	    // Extensions are chips; Content contains a list of cells
	    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
	    psMetadataItem *contentItem = NULL; // Item from the metadata
	    while (contentItem = psMetadataGetAndIncrement(contentsIter)) {
		const char *extName = contentItem->name; // The name of the extension

		if (contentItem->type != PS_META_STR) {
		    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
			     contentItem->type);
		} else {
		    const char *content = contentItem->data.V; // The content of the extension
		    psMetadata *chip = psMetadataAlloc(); // The chip
		    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
			psMetadata *cell = getCellData(camera, cellName);
			psMetadataAdd(cell, PS_LIST_HEAD, "EXTENSION", PS_META_STR, "", extName);
			// Add the cell to the chip
			psMetadataAdd(chip, PS_LIST_TAIL, cellName, PS_META_META, "", cell);
		    }
		    psFree(cellNamesIter);
		    psFree(cellNames);
		    // Add the chip onto the FPA
		    psMetadataAdd(fpa, PS_LIST_TAIL, extName, PS_META_META, "", chip);
		}
	    }
	    psFree(contentsIter);

	} else if (strcmp(extType, "CELL") == 0) {
	    // Extensions are cells; Content contains a chip name and cell type
	    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
	    psMetadataItem *contentItem = NULL; // Item from the metadata
	    while (contentItem = psMetadataGetAndIncrement(contentsIter)) {
		const char *extName = contentItem->name; // The name of the extension

		if (contentItem->type != PS_META_STR) {
		    psLogMsg(__func__, PS_LOG_WARN, "Type of content item (%x) is not string: ignored\n",
			     contentItem->type);
		} else {
		    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 {
			// The name of the chip
			const char *chipName = psListGet(contents, 0);
			// The type of cell
			const char *cellType = psListGet(contents, 1);

			psMetadata *chip = psMetadataLookupMD(&mdStatus, fpa, chipName); // The chip
			if (! mdStatus && ! chip) {
			    chip = psMetadataAlloc();
			    psMetadataAdd(fpa, PS_LIST_TAIL, chipName, PS_META_META, "", chip);
			}
			// The cell
			psMetadata *cell = getCellData(camera, cellType);
			psMetadataAdd(cell, PS_LIST_HEAD, "EXTENSION", PS_META_STR, "", extName);
			// Add the cell to the chip
			psMetadataAdd(chip, PS_LIST_TAIL, extName, PS_META_META, "", cell);
		    }
		}
	    }
	    psFree(contentsIter);
	} else if (strcmp(extType, "NONE") == 0) {
	    // No extensions; Content contains metadata, each entry is a chip with its component cells
	    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
	    psMetadataItem *contentItem = NULL; // Item from the metadata
	    while (contentItem = psMetadataGetAndIncrement(contentsIter)) {
		const char *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);
		} else {
		    const char *content = contentItem->data.V; // The content of the extension
		    psMetadata *chip = psMetadataAlloc(); // 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)) {
			psMetadata *cell = getCellData(camera, cellName);
			psMetadataAdd(cell, PS_LIST_HEAD, "EXTENSION", PS_META_STR, "", "PHU");
			psMetadataAdd(chip, PS_LIST_TAIL, cellName, PS_META_META, "", cell);
		    }
		    psFree(cellNamesIter);
		    // Add the chip onto the FPA
		    psMetadataAdd(fpa, PS_LIST_TAIL, chipName, PS_META_META, "", chip);
		}
	    }
	    psFree(contentsIter);
	} else {
	    psError(PS_ERR_IO, false, "EXTENSIONS in camera definition is not CHIP, CELL or NONE.\n");
	    psFree(fpa);
	    return NULL;
	} // Type of extension

    } else if (strcmp(phuType, "CHIP") == 0) {
	// The FITS file contains a single chip only

	psMetadata *chip = psMetadataAlloc(); // The chip
	psMetadataAdd(fpa, PS_LIST_TAIL, "CHIP", PS_META_META, "", chip);
	
	if (strcmp(extType, "NONE") == 0) {
	    // There are no extensions --- only the 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
	    const char *cellName = NULL;
	    while (cellName = psListGetAndIncrement(cellIter)) {
		psMetadata *cell = getCellData(camera, cellName);
		psMetadataAdd(cell, PS_LIST_HEAD, "EXTENSION", PS_META_STR, "", "PHU");
		// Add the cell to the chip
		psMetadataAdd(chip, PS_LIST_TAIL, cellName, PS_META_META, "", cell);
	    }
	    psFree(cellIter);
	} else if (strcmp(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 (strcmp(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)) {
		const char *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);
		} else {
		    const char *cellType = contentItem->data.V; // The type of cell
		    
		    // The cell
		    psMetadata *cell = getCellData(camera, cellType);
		    psMetadataAdd(cell, PS_LIST_HEAD, "EXTENSION", PS_META_STR, "", extName);
		    // Add the cell to the chip
		    psMetadataAdd(chip, PS_LIST_TAIL, extName, PS_META_META, "", 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;
	}

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

    
    // Print out the build tree
    psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iterator for FPA
    psMetadataItem *fpaItem = NULL;	// Item from metadata
    psTrace("pmFPAfromHeader", 0, "FPA:\n");
    while (fpaItem = psMetadataGetAndIncrement(fpaIter)) {
	if (fpaItem->type != PS_META_META) {
	    psError(PS_ERR_IO, false, "FPA metadata does not contain chip metadata.\n");
	} else {
	    psTrace("pmFPAfromHeader", 1, "Chip: %s\n", fpaItem->name);
	    psMetadata *chip = fpaItem->data.V;	// The chip
	    psMetadataIterator *chipIter = psMetadataIteratorAlloc(chip, PS_LIST_HEAD, NULL); // Iterator for
											      // chip
	    psMetadataItem *chipItem = NULL; // Item from metadata
	    while (chipItem = psMetadataGetAndIncrement(chipIter)) {
		if (chipItem->type != PS_META_META) {
		    psError(PS_ERR_IO, false, "Chip metadata does not contain cell metadata.\n");
		} else {
		    psTrace("pmFPAfromHeader", 2, "Cell: %s\n", chipItem->name);
		    psMetadata *cell = chipItem->data.V; // The cell
		    psMetadataIterator *cellIter = psMetadataIteratorAlloc(cell, PS_LIST_HEAD, NULL); // Iterator
												  // for cell
		    psMetadataItem *cellItem = NULL; // Item from metadata
		    while (cellItem = psMetadataGetAndIncrement(cellIter)) {
			switch(cellItem->type) {
			  case PS_META_STR:
			    psTrace("pmFPAfromHeader", 3, "%s: %s\n", cellItem->name, cellItem->data.V);
			    break;
			  case PS_META_F32:
			    psTrace("pmFPAfromHeader", 3, "%s: %f\n", cellItem->name, cellItem->data.F32);
			    break;
			  case PS_META_META:
			    psTrace("pmFPAfromHeader", 3, "%s:\n", cellItem->name);
			    psMetadataPrint(cellItem->data.V, 4);
			    break;
			  default:
			    psError(PS_ERR_IO, false, "Unknown type for cell (%x).\n", cellItem->type);
			}
		    } // Iterating through cell
		    psFree(cellIter);
		}
	    } // Iterating through chip
	    psFree(chipIter);
	}
    } // Iterating through FPA
    psFree(fpaIter);

    return fpa;

}
