#include <stdio.h>
#include <strings.h>
#include "pslib.h"

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


static bool compareConcepts(psMetadataItem *item1, // First item to compare
                            psMetadataItem *item2 // Second item to compare
                           )
{
    // First order checks
    if (! item1 || ! item2) {
        return false;
    }
    if (strcasecmp(item1->name, item2->name) != 0) {
        return false;
    }

    // Check the more boring types
    switch (item1->type) {
    case PS_TYPE_S32:
        switch (item2->type) {
        case PS_TYPE_S32:
            return (item1->data.S32 == item2->data.S32) ? true : false;
        case PS_TYPE_F32:
            return (item1->data.S32 == (int)item2->data.F32) ? true : false;
        case PS_TYPE_F64:
            return (item1->data.S32 == (int)item2->data.F64) ? true : false;
        default:
            return false;
        }
    case PS_TYPE_F32:
        switch (item2->type) {
        case PS_TYPE_S32:
            return (item1->data.F32 == (float)item2->data.S32) ? true : false;
        case PS_TYPE_F32:
            return (item1->data.F32 == item2->data.F32) ? true : false;
        case PS_TYPE_F64:
            return (item1->data.F32 == (float)item2->data.F64) ? true : false;
        default:
            return false;
        }
    case PS_TYPE_F64:
        switch (item2->type) {
        case PS_TYPE_S32:
            return (item1->data.F64 == (double)item2->data.S32) ? true : false;
        case PS_TYPE_F32:
            return (item1->data.F64 == (double)item2->data.F32) ? true : false;
        case PS_TYPE_F64:
            return (item1->data.F64 == item2->data.F64) ? true : false;
        default:
            return false;
        }
        return (item1->data.F64 == item2->data.F64) ? true : false;
    case PS_DATA_STRING:
        if (item2->type != PS_DATA_STRING) {
            return false;
        }
        return (strcasecmp(item1->data.V, item2->data.V) == 0) ? true : false;
    default:
        return false;
    }
    psAbort(__func__, "Should never get here.\n");
}


// Well, not really "write", but check to make sure it's there and matches
bool pmConceptWriteToCamera(pmCell *cell, // The cell
                            psMetadataItem *concept // Concept
                           )
{
    if (! cell->camera) {
        return false;
    }
    if (cell) {
        psMetadataItem *item = psMetadataLookup(cell->camera, concept->name); // Info we want
        return compareConcepts(item, concept);
    }

    return false;
}

// Write the concept to the header in the appropriate location
bool pmConceptWriteToHeader(pmFPA *fpa, // The FPA that contains the chip
                            pmChip *chip, // The chip that contains the cell
                            pmCell *cell, // The cell
                            psMetadataItem *concept // Concept
                           )
{
    bool mdok = true;                   // Status of MD lookup
    bool status = false;                // Status of setting header
    if (! fpa->camera) {
        return false;
    }
    psMetadata *translation = psMetadataLookupMD(&mdok, fpa->camera, "TRANSLATION"); // FITS translation
    if (! mdok) {
        psError(PS_ERR_IO, false, "Unable to find TRANSLATION in camera configuration.\n");
        return false;
    }

    // Look for how to translate the concept into a FITS header name
    const char *keyword = psMetadataLookupStr(&mdok, translation, concept->name);
    if (mdok && strlen(keyword) > 0) {
        psTrace(__func__, 6, "It's in keyword %s\n", keyword);
        psMetadataItem *headerItem = NULL; // Item to add to header
        // XXX: Need to expand range of types
        switch (concept->type) {
        case PS_DATA_STRING:
            headerItem = psMetadataItemAllocStr(keyword, concept->comment, concept->data.V);
            break;
        case PS_DATA_S32:
            headerItem = psMetadataItemAllocS32(keyword, concept->comment, concept->data.S32);
            break;
        case PS_DATA_F32:
            headerItem = psMetadataItemAllocF32(keyword, concept->comment, concept->data.F32);
            break;
        case PS_DATA_F64:
            headerItem = psMetadataItemAllocF64(keyword, concept->comment, concept->data.F64);
            break;
        default:
            headerItem = psMetadataItemAlloc(keyword, concept->type, concept->comment,
                                             concept->data.V); // Item for the header
        }

        // We have a FITS header to look up --- search each level
        if (cell && cell->hdu) {
            psTrace(__func__, 7, "Adding to the cell level header...\n");
            psMetadataAddItem(cell->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
            status = true;
        } else if (chip && chip->hdu) {
            psTrace(__func__, 7, "Adding to the chip level header...\n");
            psMetadataAddItem(chip->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
            status = true;
        } else if (fpa->hdu) {
            psTrace(__func__, 7, "Adding to the FPA level header...\n");
            psMetadataAddItem(fpa->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
            status = true;
        } else {
            // In desperation, add to the PHU --- it HAS to be in the header somewhere!
            if (! fpa->phu) {
                fpa->phu = psMetadataAlloc();
            }
            psTrace(__func__, 7, "Adding to the PHU...\n");
            psMetadataAddItem(fpa->phu, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
            status = true;
        }
        psFree(headerItem);
    }

    // No header value
    return status;
}


// Well, not really "write", but check to see if it's there, and matches
bool pmConceptWriteToDefault(pmFPA *fpa, // The FPA that contains the chip
                             pmChip *chip, // The chip that contains the cell
                             pmCell *cell, // The cell
                             psMetadataItem *concept // Concept
                            )
{
    bool mdOK = true;                   // Status of MD lookup
    if (! fpa->camera) {
        return false;
    }
    psMetadata *defaults = psMetadataLookupMD(&mdOK, fpa->camera, "DEFAULTS");
    if (! mdOK || ! defaults) {
        psError(PS_ERR_IO, false, "Unable to find DEFAULTS in camera configuration.\n");
        return false;
    }

    psMetadataItem *defItem = psMetadataLookup(defaults, concept->name);
    bool status = false;                // Result of checking the database
    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->name);
            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 false;
            }
            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->name);
                return false;
            }
            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
        }

        status = compareConcepts(defItem, concept);
        if (! status) {
            psError(PS_ERR_IO, true, "Concept %s is specified by default in the camera configuration, "
                    "but doesn't match the actual value.\n", concept->name);
        }
    }

    // XXX: Need to clean up before returning
    return status;
}


// XXX: Not tested at all
// XXX I WOULD NOT TRUST THIS FUNCTION IN THE SLIGHTEST YET! --- PAP
bool pmConceptWriteToDB(pmFPA *fpa, // The FPA that contains the chip
                        pmChip *chip, // The chip that contains the cell
                        pmCell *cell, // The cell
                        psDB *db,    // DB handle
                        psMetadataItem *concept // Concept
                       )
{
    if (! db) {
        // No database initialised
        return false;
    }

    bool mdStatus = true;               // Status of MD lookup
    if (! fpa->camera) {
        return false;
    }
    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->name);
    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->name, 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);

                // Check first to make sure we're only going to touch one row
                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
                if (! dbResult || dbResult->n == 0) {
                    psLogMsg(__func__, PS_LOG_WARN, "Unable to find any rows in DB for %s --- ignored\n",
                             concept->name);
                    return false;
                } else {
                    if (dbResult->n > 1) {
                        psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
                                 " ignored.\n", concept->name);
                    }
                    // Update the DB
                    psMetadata *update = psMetadataAlloc();
                    psMetadataAddItem(update, concept, PS_LIST_HEAD, 0);
                    psDBUpdateRows(db, tableName, selection, update);
                    psFree(update);
                    return true;
                }
            }
            psFree(cols);
            psFree(values);
        }
    } // Doing the "given"s.

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


// Concept write from item
bool pmConceptWriteItem(pmFPA *fpa,     // The FPA
                        pmChip *chip,   // The chip
                        pmCell *cell,   // The cell
                        psDB *db,       // DB handle
                        psMetadataItem *concept // Concept item
                       )
{
    if (! fpa->camera) {
        return false;
    }

    // Try headers, database, defaults in order
    psTrace(__func__, 3, "Trying to set concept %s...\n", concept->name);
    bool status = pmConceptWriteToCamera(cell, concept); // Status for return
    if (! status) {
        psTrace(__func__, 5, "Trying header....\n");
        status = pmConceptWriteToHeader(fpa, chip, cell, concept);
    }
    if (! status) {
        psTrace(__func__, 5, "Trying database....\n");
        status = pmConceptWriteToDB(fpa, chip, cell, db, concept);
    }
    if (! status) {
        psTrace(__func__, 5, "Checking defaults....\n");
        status = pmConceptWriteToDefault(fpa, chip, cell, concept);
    }

    if (! status) {
        psError(PS_ERR_IO, true, "Unable to set %s (%s).\n", concept->name, concept->comment);
    }

    return status;
}


// Concept write
bool pmConceptWrite(pmFPA *fpa, // The FPA
                    pmChip *chip,// The chip
                    pmCell *cell,    // The cell
                    psDB *db, // DB handle
                    psMetadata *concepts, // Concepts MD from which to set
                    const char *name // Name of the concept
                   )
{
    psMetadataItem *concept = psMetadataLookup(concepts, name);
    if (! concept) {
        psError(PS_ERR_IO, true, "No such concept as %s\n", name);
        return false;
    }
    return pmConceptWriteItem(fpa, chip, cell, db, concept);
}

