#include <stdio.h>
#include <string.h>
#include "pslib.h"
#include "psmodule.h"
#include "papStuff.h"			// For papSplit


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

// Look for a value already cached
static psMetadataItem *getValueFromCache(const pmFPA *fpa, // The FPA that contains the chip
					 const pmChip *chip, // The chip that contains the cell
					 const pmCell *cell, // The cell
					 const char *valueName // Name of value
    )
{
    // Search the cell
    psMetadataItem *item = psMetadataLookup(cell->concepts, valueName); // Item from the lookup
    if (item) {
	return item;
    }
    // Search the chip
    item = psMetadataLookup(chip->concepts, valueName);
    if (item) {
	return item;
    }
    // Search the FPA
    item = psMetadataLookup(fpa->concepts, 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(pmFPA *fpa, // The FPA that contains the chip
			    pmChip *chip, // The chip that contains the cell
			    pmCell *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->concepts, newItem, PS_LIST_TAIL, PS_META_REPLACE);
    }
    if (chip && strncmp(valueName, "CHIP", 4) == 0) {
	psMetadataAddItem(chip->concepts, newItem, PS_LIST_TAIL, PS_META_REPLACE);
    }
    if (cell && strncmp(valueName, "CELL", 4) == 0) {
	psMetadataAddItem(cell->concepts, newItem, PS_LIST_TAIL, PS_META_REPLACE);
    }

}


static psMetadataItem *getValueFromHeader(pmFPA *fpa, // The FPA that contains the chip
					  pmChip *chip, // The chip that contains the cell
					  pmCell *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(pmFPA *fpa, // The FPA that contains the chip
					   pmChip *chip, // The chip that contains the cell
					   pmCell *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(pmFPA *fpa, // The FPA that contains the chip
				      pmChip *chip, // The chip that contains the cell
				      pmCell *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(pmCell *cell, // The cell
			       const char *valueName // Name of value
    )
{
    psTrace(__func__, 3, "Trying to retrieve %s...\n", valueName);

    pmChip *chip = cell->parent;	// The chip
    pmFPA *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(pmCell *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(pmCell *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(pmCell *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(pmCell *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;
}
