#include <stdio.h>

#include "pslib.h"

#include "pmFPA.h"
#include "pmConceptsRead.h"
#include "psAdditionals.h"


psMetadataItem *pmConceptReadFromCamera(pmCell *cell, // The cell
                                        const char *concept // Name of concept
                                       )
{
    if (cell) {
        psMetadata *camera = cell->camera;      // Camera data
        psMetadataItem *item = psMetadataLookup(camera, concept);
        return item;
    }
    return NULL;
}

psMetadataItem *pmConceptReadFromHeader(pmFPA *fpa, // The FPA that contains the chip
                                        pmChip *chip, // The chip that contains the cell
                                        pmCell *cell, // The cell
                                        const char *concept // Name of concept
                                       )
{
    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 *keyword = psMetadataLookupStr(&mdStatus, translation, concept);
    if (mdStatus && strlen(keyword) > 0) {
        // We have a FITS header to look up --- search each level
        if (cell && cell->hdu) {
            psMetadataItem *cellItem = psMetadataLookup(cell->hdu->header, keyword);
            if (cellItem) {
                // XXX: Need to clean up before returning
                return cellItem;
            }
        }

        if (chip && chip->hdu) {
            psMetadataItem *chipItem = psMetadataLookup(chip->hdu->header, keyword);
            if (chipItem) {
                // XXX: Need to clean up before returning
                return chipItem;
            }
        }

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

        if (fpa->phu) {
            psMetadataItem *fpaItem = psMetadataLookup(fpa->phu, keyword);
            if (fpaItem) {
                // XXX: Need to clean up before returning
                return fpaItem;
            }
        }
    }

    // No header value
    return NULL;
}


// Look for a default
psMetadataItem *pmConceptReadFromDefault(pmFPA *fpa, // The FPA that contains the chip
        pmChip *chip, // The chip that contains the cell
        pmCell *cell, // The cell
        const char *concept // Name of concept
                                        )
{
    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, concept);
    if (defItem) {
        if (defItem->type == PS_DATA_METADATA) {
            // 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(concept);
            psStringAppend(&dependName, ".DEPEND");
            psString dependsOn = psMetadataLookupStr(&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 = pmConceptReadFromHeader(fpa, chip, cell, dependsOn);
            if (! depItem) {
                psError(PS_ERR_IO, true, "Unable to find value for %s (required for %s)\n", dependsOn,
                        concept);
                return NULL;
            }
            if (depItem->type != PS_DATA_STRING) {
                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
        }
    }

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


// Look for a database lookup
// XXX: Not tested
psMetadataItem *pmConceptReadFromDB(pmFPA *fpa, // The FPA that contains the chip
                                    pmChip *chip, // The chip that contains the cell
                                    pmCell *cell, // The cell
                                    psDB *db, // DB handle
                                    const char *concept // Name of concept
                                   )
{
    if (! 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, concept);
    if (dbLookup) {
        const char *tableName = psMetadataLookupStr(&mdStatus, dbLookup, "TABLE"); // Name of the table
        // const char *colName = psMetadataLookupStr(&mdStatus, dbLookup, "COLUMN"); // Name of the column
        const char *givenCols = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENDBCOL"); // Name of "where"
        // columns
        const char *givenPS = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENPS"); // Values for "where"
        // columns

        // Now, need to get the "given"s
        if (strlen(givenCols) || strlen(givenPS)) {
            psList *cols = psStringSplit(givenCols, ",;"); // List of column names
            psList *values = psStringSplit(givenPS, ",;"); // List of value names for the columns
            psMetadata *selection = psMetadataAlloc(); // The stuff to select in the DB
            if (cols->n != values->n) {
                psLogMsg(__func__, PS_LOG_WARN, "The GIVENDBCOL and GIVENPS entries for %s do not have "
                         "the same number of entries --- ignored.\n", concept);
            } 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))) {
                    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", concept);
                    } else {
                        // Search for the value name
                        psMetadataItem *item = pmConceptReadFromHeader(fpa, chip, cell, name);
                        if (! item) {
                            item = pmConceptReadFromDefault(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, concept);
                        } 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(concept, item->type,
                                                      item->comment, item->data.V);
                            psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
                            psFree(newItem);
                        }
                    }
                    psFree(name);
                    psFree(column);
                } // Iterating through the columns
                psFree(colsIter);
                psFree(valuesIter);

                psArray *dbResult = psDBSelectRows(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",
                             concept);
                } else {
                    if (dbResult-> n > 1) {
                        psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
                                 " using the first one only.\n", concept);
                    }
                    result = (psMetadataItem*)dbResult->data[0];
                }
                // XXX: Need to clean up before returning
                return result;
            }
            psFree(cols);
            psFree(values);
        }
    } // Doing the "given"s.

    psAbort(__func__, "Shouldn't ever get here.\n");
    return NULL;
}


// Concept lookup
psMetadataItem *pmConceptRead(pmFPA *fpa, // The FPA
                              pmChip *chip,// The chip
                              pmCell *cell, // The cell
                              psDB *db, // DB handle
                              const char *name // Concept name
                             )
{
    // Try headers, database, defaults in order
    psMetadataItem *item = pmConceptReadFromCamera(cell, name);
    if (! item) {
        item = pmConceptReadFromHeader(fpa, chip, cell, name);
    }
    if (! item) {
        item = pmConceptReadFromDB(fpa, chip, cell, db, name);
    }
    if (! item) {
        item = pmConceptReadFromDefault(fpa, chip, cell, name);
    }
    return item; // item is either NULL, or points to what was desired
}


float pmConceptReadF32(pmFPA *fpa,        // The FPA
                       pmChip *chip,      // The chip
                       pmCell *cell,      // The cell
                       psDB *db,          // DB handle
                       const char *name // Name of the concept
                      )
{
    psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
    float value = NAN;
    if (item) {
        switch (item->type) {
        case PS_DATA_F32:
            value = item->data.F32;
            break;
        case PS_DATA_F64:
            // Assume it's OK to truncate to floating point from double
            value = (float)item->data.F64;
            break;
        case PS_DATA_S32:
            // Promote to float
            value = (float)item->data.S32;
            break;
        default:
            psError(PS_ERR_IO, true, "Concept %s (%s) is not of floating point type (%x) --- treating as "
                    "undefined.\n", name, item->comment, item->type);
        }
        psTrace(__func__, 7, "Adding %s (%s): %f\n", name, item->comment, value);
    } else {
        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
    }

    return value;
}

double pmConceptReadF64(pmFPA *fpa,   // The FPA
                        pmChip *chip, // The chip
                        pmCell *cell, // The cell
                        psDB *db,     // DB handle
                        const char *name // Name of the concept
                       )
{
    psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
    double value = NAN;
    if (item) {
        switch (item->type) {
        case PS_TYPE_F64:
            value = item->data.F64;
            break;
        case PS_TYPE_F32:
            // Promote to double
            value = (double)item->data.F32;
            break;
        case PS_TYPE_S32:
            // Promote to double
            value = (double)item->data.S32;
            break;
        default:
            psError(PS_ERR_IO, true, "Concept %s (%s) is not of double-precision floating point type (%x) "
                    "--- treating as undefined.\n", name, item->comment, item->type);
        }
        psTrace(__func__, 7, "Adding %s (%s): %f\n", name, item->comment, value);
    } else {
        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
    }

    return value;
}

int pmConceptReadS32(pmFPA *fpa,   // The FPA
                     pmChip *chip, // The chip
                     pmCell *cell, // The cell
                     psDB *db,     // DB handle
                     const char *name // Name of the concept
                    )
{
    psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
    int value = 0;
    if (item) {
        switch (item->type) {
        case PS_TYPE_S32:
            value = item->data.S32;
            break;
        case PS_TYPE_F32:
            psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F32 --- converting.\n",
                     name, comment);
            value = (int)item->data.F32;
            break;
        case PS_TYPE_F64:
            psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F64 --- converting.\n",
                     name, comment);
            value = (int)item->data.F64;
            break;
        default:
            psError(PS_ERR_IO, true, "Concept %s (%s) is not of integer type (%x) --- treating as "
                    "undefined.\n", name, item->comment, item->type);
        }
        psTrace(__func__, 7, "Read concept %s (%s): %d\n", name, item->comment, value);
    } else {
        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
    }

    return value;
}

psString pmConceptReadString(pmFPA *fpa, // The FPA
                             pmChip *chip, // The chip
                             pmCell *cell, // The cell
                             psDB *db,  // DB handle
                             const char *name // Name of the concept
                            )
{
    psMetadataItem *item = pmConceptRead(fpa, chip, cell, db, name);
    psString value = NULL;
    if (item) {
        switch (item->type) {
        case PS_DATA_STRING:
            value = psMemIncrRefCounter(item->data.V);
            break;
        case PS_DATA_F32:
            psStringAppend(&value, "%f", item->data.F32);
            break;
        case PS_DATA_S32:
            psStringAppend(&value, "%d", item->data.S32);
            break;
        default:
            psError(PS_ERR_IO, true, "Concept %s (%s) is not of string type (%x) --- treating as "
                    "undefined.\n", name, item->comment, item->type);
        }
        psTrace(__func__, 7, "Read concept %s (%s): %s\n", name, item->comment, value);
    } else {
        psError(PS_ERR_IO, true, "Concept %s is not defined.\n", name);
        value = psStringCopy("");
    }

    return value;
}

