#include <stdio.h>
#include <string.h>
#include "pslib.h"
#include "papFocalPlane.h"
#include "psAdditionals.h"
#include "pmFPARead.h"

// I'm sure this code, especially the DB lookup, leaks memory something chronic....


// Look for a value already cached
static psMetadataItem *getValueFromCache(const papFPA *fpa, // The FPA that contains the chip
					 const papChip *chip, // The chip that contains the cell
					 const papCell *cell, // The cell
					 const char *valueName // Name of value
    )
{
    // Search the cell
    psMetadataItem *item = psMetadataLookup(cell->values, valueName); // Item from the lookup
    if (item) {
	return item;
    }
    // Search the chip
    item = psMetadataLookup(chip->values, valueName);
    if (item) {
	return item;
    }
    // Search the FPA
    item = psMetadataLookup(fpa->values, valueName);
    return item;			// item is either NULL or points to what was desired
}


// Put a value that we've found into the cache
static void setValueInCache(papFPA *fpa, // The FPA that contains the chip
			    papChip *chip, // The chip that contains the cell
			    papCell *cell, // The cell
			    const char *valueName, // Name of value
			    const psMetadataItem *value	// Item containing the value
    )
{
    // We need to create a new psMetadataItem.  I don't think we can't simply hack the existing one, since
    // that could conceivably cause memory leaks
    psMetadataItem *newItem = psMetadataItemAlloc(valueName, value->type, value->comment, value->data.V);

    if (fpa && strncmp(valueName, "FPA", 3) == 0) {
	psMetadataAddItem(fpa->values, newItem, PS_LIST_TAIL, PS_META_REPLACE);
    }
    if (chip && strncmp(valueName, "CHIP", 4) == 0) {
	psMetadataAddItem(chip->values, newItem, PS_LIST_TAIL, PS_META_REPLACE);
    }
    if (cell && strncmp(valueName, "CELL", 4) == 0) {
	psMetadataAddItem(cell->values, newItem, PS_LIST_TAIL, PS_META_REPLACE);
    }

}


static psMetadataItem *getValueFromHeader(papFPA *fpa, // The FPA that contains the chip
					  papChip *chip, // The chip that contains the cell
					  papCell *cell, // The cell
					  const char *valueName // Name of value
    )
{
    bool mdStatus = true;		// Status of MD lookup
    psMetadata *translation = psMetadataLookupMD(&mdStatus, fpa->camera, "TRANSLATION"); // FITS translation
    if (! mdStatus) {
	psError(PS_ERR_IO, false, "Unable to find TRANSLATION in camera configuration.\n");
	return NULL;
    }

    // Look for how to translate the concept into a FITS header name
    const char *header = psMetadataLookupString(&mdStatus, translation, valueName);
    if (mdStatus && strlen(header) > 0) {
	// We have a FITS header to look up --- search each level
	if (cell->header) {
	    psMetadataItem *cellItem = psMetadataLookup(cell->header, header);
	    if (cellItem) {
		// XXX: Need to clean up before returning
		setValueInCache(NULL, NULL, cell, valueName, cellItem);
		return cellItem;
	    }
	}

	if (chip->header) {
	    psMetadataItem *chipItem = psMetadataLookup(chip->header, header);
	    if (chipItem) {
		// XXX: Need to clean up before returning
		setValueInCache(NULL, chip, cell, valueName, chipItem);
		return chipItem;
	    }
	}

	if (fpa->header) {
	    psMetadataItem *fpaItem = psMetadataLookup(fpa->header, header);
	    if (fpaItem) {
		// XXX: Need to clean up before returning
		setValueInCache(fpa, chip, cell, valueName, fpaItem);
		return fpaItem;
	    }
	}
    }

    // No header value
    return NULL;
}


// Look for a default
static psMetadataItem *getValueFromDefault(papFPA *fpa, // The FPA that contains the chip
					   papChip *chip, // The chip that contains the cell
					   papCell *cell, // The cell
					   const char *valueName // Name of value
    )
{
    bool mdOK = true;			// Status of MD lookup
    psMetadata *defaults = psMetadataLookupMD(&mdOK, fpa->camera, "DEFAULTS");
    if (! mdOK) {
	psError(PS_ERR_IO, false, "Unable to find DEFAULTS in camera configuration.\n");
	return NULL;
    }

    psMetadataItem *defItem = psMetadataLookup(defaults, valueName);
    if (defItem) {
	if (defItem->type == PS_META_META) {
	    // A dependent default
	    psTrace(__func__, 7, "Evaluating dependent default....\n");
	    psMetadata *dependents = defItem->data.V; // The list of dependents
	    // Find out what it depends on
	    psString dependName = psStringCopy(valueName);
	    psStringAppend(&dependName, "_DEPEND");
	    psString dependsOn = psMetadataLookupString(&mdOK, defaults, dependName);
	    if (! mdOK) {
		psError(PS_ERR_IO, false, "Unable to find %s in camera configuration for dependent default"
			" --- ignored\n", dependName);
		// XXX: Need to clean up before returning
		return NULL;
	    }
	    psFree(dependName);
	    // Find the value of the dependent concept
	    psMetadataItem *depItem = getValueFromCache(fpa, chip, cell, dependsOn);
	    if (! depItem) {
		depItem = getValueFromHeader(fpa, chip, cell, dependsOn);
	    }
	    if (! depItem) {
		psError(PS_ERR_IO, true, "Unable to find value for %s (required for %s)\n", dependsOn,
			valueName);
		return NULL;
	    }
	    if (depItem->type != PS_META_STR) {
		psError(PS_ERR_IO, true, "Value of %s is not of type string, as required for dependency"
			" --- ignored.\n", dependsOn);
	    }

	    defItem = psMetadataLookup(dependents, depItem->data.V);	// This is now what we were after
	}
	setValueInCache(fpa, chip, cell, valueName, defItem);
    }

    // XXX: Need to clean up before returning
    return defItem;			// defItem is either NULL or points to what was desired
}


// Look for a database lookup
static psMetadataItem *getValueFromDB(papFPA *fpa, // The FPA that contains the chip
				      papChip *chip, // The chip that contains the cell
				      papCell *cell, // The cell
				      const char *valueName // Name of value
    )
{
    if (! fpa->db) {
	// No database initialised
	return NULL;
    }

    bool mdStatus = true;		// Status of MD lookup
    psMetadata *database = psMetadataLookupMD(&mdStatus, fpa->camera, "DATABASE");
    if (! mdStatus) {
	// No error, because not everyone needs to use the DB
	return NULL;
    }

    psMetadata *dbLookup = psMetadataLookupMD(&mdStatus, database, valueName);
    if (dbLookup) {
	const char *tableName = psMetadataLookupString(&mdStatus, dbLookup, "TABLE"); // Name of the table
	const char *colName = psMetadataLookupString(&mdStatus, dbLookup, "COLUMN"); // Name of the column
	const char *givenCols = psMetadataLookupString(&mdStatus, dbLookup, "GIVENDBCOL");	// Name of "where" columns
	const char *givenPS = psMetadataLookupString(&mdStatus, dbLookup, "GIVENPS"); // Values for "where" columns
	
	// Now, need to get the "given"s
	if (strlen(givenCols) || strlen(givenPS)) {
	    psList *cols = papSplit(givenCols, ",;"); // List of column names
	    psList *values = papSplit(givenPS, ",;"); // List of value names for the columns
	    psMetadata *selection = psMetadataAlloc(); // The stuff to select in the DB
	    if (cols->size != values->size) {
		psLogMsg(__func__, PS_LOG_WARN, "The GIVENDBCOL and GIVENPS entries for %s do not have "
			 "the same number of entries --- ignored.\n", valueName);
	    } else {
		// Iterators for the lists
		psListIterator *colsIter = psListIteratorAlloc(cols, PS_LIST_HEAD, false);
		psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
		char *column = NULL;	// Name of the column
		while (column = psListGetAndIncrement(colsIter)) {
		    const char *name = psListGetAndIncrement(valuesIter); // Name for the value
		    if (!strlen(column) || !strlen(name)) {
			psLogMsg(__func__, PS_LOG_WARN, "One of the columns or value names for %s is "
				 " empty --- ignored.\n", valueName);
		    } else {
			// Search for the value name
			psMetadataItem *item = getValueFromCache(fpa, chip, cell, name);
			if (! item) {
			    item = getValueFromHeader(fpa, chip, cell, name);
			}
			if (! item) {
			    item = getValueFromDefault(fpa, chip, cell, name);
			}
			if (! item) {
			    psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB "
				     " lookup on %s --- ignored.\n", name, valueName);
			} else {
			    // We need to create a new psMetadataItem.  I don't think we can't simply hack
			    // the existing one, since that could conceivably cause memory leaks
			    psMetadataItem *newItem = psMetadataItemAlloc(valueName, item->type,
									  item->comment, item->data.V);
			    psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
			    psFree(newItem);
			}
		    }
		} // Iterating through the columns
		
		psArray *dbResult = psDBSelectRows(fpa->db, tableName, selection, 2); // Lookup result
		// Note that we use limit=2 in order to test if there are multiple rows returned
		
		psMetadataItem *result = NULL; // The final result of the DB lookup
		if (dbResult->n == 0) {
		    psLogMsg(__func__, PS_LOG_WARN, "Unable to find any rows in DB for %s --- ignored\n", 
			     valueName);
		} else {
		    if (dbResult-> n > 1) {
			psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
				 " using the first one only.\n", valueName);
		    }
		    result = (psMetadataItem*)dbResult->data[0];
		}
		// XXX: Need to clean up before returning
		return result;
	    }
	}
    } // Doing the "given"s.

    // Shouldn't get here
    return NULL;
}

psMetadataItem *psCellGetValue(papCell *cell, // The cell
			       const char *valueName // Name of value
			       )
{
    psTrace(__func__, 3, "Trying to retrieve %s...\n", valueName);

    papChip *chip = cell->parent;	// The chip
    papFPA *fpa = chip->parent;		// The FPA

    // Try cache, headers, database, defaults in order
    psTrace(__func__, 5, "Trying caches....\n");
    psMetadataItem *item = getValueFromCache(fpa, chip, cell, valueName);
    if (! item) {
	psTrace(__func__, 5, "Trying headers....\n");
	item = getValueFromHeader(fpa, chip, cell, valueName);
    }
    if (! item) {
	psTrace(__func__, 5, "Trying database....\n");
	item = getValueFromDB(fpa, chip, cell, valueName);
    }
    if (! item) {
	psTrace(__func__, 5, "Trying defaults....\n");
	item = getValueFromDefault(fpa, chip, cell, valueName);
    }

    return item;			// item is either NULL or points to what was desired
}

float psCellGetValueF32(papCell *cell, // The cell
			const char *valueName // Name of value
			)
{
    float value = NAN;			// Result
    psMetadataItem *item = psCellGetValue(cell, valueName); // The item
    if (! item) {
	psError(PS_ERR_IO, true, "Unable to find value %s in cell.\n", valueName);
    } else if (item->type != PS_META_F32) {
	psError(PS_ERR_IO, true, "Metadata item type for %s is not F32.\n", valueName);
    } else {
	value = item->data.F32;
    }

    return value;
}

int psCellGetValueS32(papCell *cell, // The cell
		      const char *valueName // Name of value
    )
{
    int value = 0;			// Result
    psMetadataItem *item = psCellGetValue(cell, valueName); // The item
    if (! item) {
	psError(PS_ERR_IO, true, "Unable to find value %s.\n", valueName);
    } else if (item->type != PS_META_S32) {
	psError(PS_ERR_IO, true, "Metadata item type for %s is not S32.\n", valueName);
    } else {
	value = item->data.S32;
    }

    return value;
}


double psCellGetValueF64(papCell *cell, // The cell
			 const char *valueName // Name of value
			 )
{
    double value = NAN;			// Result
    psMetadataItem *item = psCellGetValue(cell, valueName); // The item
    if (! item) {
	psError(PS_ERR_IO, true, "Unable to find value %s.\n", valueName);
    } else if (item->type != PS_META_F64) {
	psError(PS_ERR_IO, true, "Metadata item type for %s is not F64.\n", valueName);
    } else {
	value = item->data.F64;
    }

    return value;
}


psString psCellGetValueString(papCell *cell, // The cell
			      const char *valueName // Name of value
    )
{
    psString value = NULL;		// Result
    psMetadataItem *item = psCellGetValue(cell, valueName); // The item
    if (! item) {
	psError(PS_ERR_IO, true, "Unable to find value %s.\n", valueName);
    } else if (item->type != PS_META_STR) {
	psError(PS_ERR_IO, true, "Metadata item type for %s is not STR.\n", valueName);
    } else {
	value = item->data.V;
    }

    return value;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Allocators
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

papFPA *papFPAAlloc(const psMetadata *camera, // Camera configuration
		    psDB *db		// Database
    )
{
    papFPA *fpa = psAlloc(sizeof(papFPA));// The FPA
    psMemSetDeallocator(fpa, (psFreeFcn)p_papFPAFree);

    // Fill in the components
    fpa->fromTangentPlane = NULL;
    fpa->toTangentPlane = NULL;
    fpa->projection = NULL;

    fpa->values = psMetadataAlloc();
    fpa->camera = psMemIncrRefCounter((psPtr)camera);
    fpa->db = db;
    fpa->chips = psArrayAlloc(0);

    fpa->extname = NULL;
    fpa->pixels = NULL;
    fpa->header = NULL;

    return fpa;
}

void p_papFPAFree(papFPA *fpa)
{
    psFree(fpa->fromTangentPlane);
    psFree(fpa->toTangentPlane);
    psFree(fpa->projection);

    psFree(fpa->values);
    psFree((psPtr)fpa->camera);
    psFree(fpa->db);
    psFree(fpa->chips);

    psFree(fpa->pixels);
    psFree(fpa->header);
}

papChip *papChipAlloc(papFPA *fpa,	// FPA to which the chip belongs
		      psString name	// Chip name
    )
{
    papChip *chip = psAlloc(sizeof(papChip)); // The chip
    psMemSetDeallocator(chip, (psFreeFcn)p_papChipFree);

    // Push onto the array of chips
    fpa->chips = psArrayAdd(fpa->chips, 0, chip);

    // Fill in the components
    *(int*)&chip->col0 = 0;		// Good enough for now
    *(int*)&chip->row0 = 0;		// Good enough for now

    chip->toFPA = NULL;
    chip->fromFPA = NULL;

    chip->values = psMetadataAlloc();
    chip->parent = psMemIncrRefCounter(fpa);
    chip->cells = psArrayAlloc(0);

    chip->extname = NULL;
    chip->pixels = NULL;
    chip->header = NULL;

    psMetadataAddStr(chip->values, PS_LIST_HEAD, "CHIP.NAME", "Chip name added at papChipAlloc", name);

    return chip;
}

void p_papChipFree(papChip *chip)
{
    psFree(chip->toFPA);
    psFree(chip->fromFPA);

    psFree(chip->values);
    psFree(chip->cells);
    psFree(chip->parent);

    psFree(chip->pixels);
    psFree(chip->header);
}

papCell *papCellAlloc(papChip *chip,	// Chip to which the cell belongs
		      psString name	// Name of cell
    )
{
    papCell *cell = psAlloc(sizeof(papCell)); // The cell
    psMemSetDeallocator(cell, (psFreeFcn)p_papCellFree);

    // Push onto the array of chips
    chip->cells = psArrayAdd(chip->cells, 0, cell);

    // Fill in components
    *(int*)&cell->col0 = 0;		// Good enough for now
    *(int*)&cell->row0 = 0;		// Good enough for now

    cell->toChip = NULL;
    cell->fromChip = NULL;
    cell->toFPA = NULL;
    cell->toTP = NULL;
    cell->toSky = NULL;

    cell->values = psMetadataAlloc();
    cell->readouts = psArrayAlloc(0);
    cell->parent = psMemIncrRefCounter(chip);

    cell->extname = NULL;
    cell->pixels = NULL;
    cell->header = NULL;

    psMetadataAddStr(cell->values, PS_LIST_HEAD, "CELL.NAME", "Cell name added at papCellAlloc", name);

    return cell;
}

void p_papCellFree(papCell *cell)
{
    psFree(cell->toChip);
    psFree(cell->fromChip);
    psFree(cell->toFPA);
    psFree(cell->toTP);
    psFree(cell->toSky);

    psFree(cell->values);
    psFree(cell->readouts);
    psFree(cell->parent);

    psFree(cell->pixels);
    psFree(cell->header);
}

papReadout *papReadoutAlloc(papCell *cell, // Cell to which the readout belongs
			    int readoutNum, // Number of the readout
			    psImage *image, // The pixels
			    psList *overscans, // The overscan images
			    int col0, int row0, int colParity, int rowParity, int colBin, int rowBin // Data
    )
{
    papReadout *readout = psAlloc(sizeof(papReadout));
    psMemSetDeallocator(readout, (psFreeFcn)p_papReadoutFree);
    psArray *readouts = cell->readouts;
    if (readoutNum >= readouts->nalloc) {
	readouts = psArrayRealloc(readouts, readoutNum);
	cell->readouts = readouts;
    }
    readouts->data[readoutNum] = readout;
    
    // Set the components
    readout->image = image;
    readout->overscans = overscans;
    
    readout->values = psMetadataAlloc();
    
    *(int*)&readout->col0 = col0;
    *(int*)&readout->row0 = row0;
    *(int*)&readout->colParity = colParity;
    *(int*)&readout->rowParity = rowParity;
    *(int*)&readout->colBins = colBin;
    *(int*)&readout->rowBins = rowBin;

    return readout;
}

void p_papReadoutFree(papReadout *readout)
{
    psFree(readout->image);
    psFree(readout->overscans);
    psFree(readout->values);
}
