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

#include "pmFPA.h"
#include "pmHDU.h"
#include "pmHDUUtils.h"
#include "pmConcepts.h"
#include "pmConceptsRead.h"
#include "psAdditionals.h"

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

static bool compareConcepts(psMetadataItem *compare, // Item to compare
                            psMetadataItem *standard // Standard for comparison
                           )
{
    // First order checks
    if (! compare || ! standard) {
        return false;
    }
    if (strcasecmp(compare->name, standard->name) != 0) {
        return false;
    }

    // Check the more boring types
    switch (compare->type) {
    case PS_TYPE_S32:
        switch (standard->type) {
        case PS_TYPE_S32:
            psTrace(__func__, 10, "Comparing %d and %d...\n", compare->data.S32, standard->data.S32);
            return (compare->data.S32 == standard->data.S32) ? true : false;
        case PS_TYPE_F32:
            psTrace(__func__, 10, "Comparing %d and %d...\n", compare->data.S32, (int)standard->data.F32);
            return (compare->data.S32 == (int)standard->data.F32) ? true : false;
        case PS_TYPE_F64:
            psTrace(__func__, 10, "Comparing %d and %d...\n", compare->data.S32, (int)standard->data.F64);
            return (compare->data.S32 == (int)standard->data.F64) ? true : false;
        default:
            return false;
        }
    case PS_TYPE_F32:
        switch (standard->type) {
        case PS_TYPE_S32:
            psTrace(__func__, 10, "Comparing %f and %f...\n", compare->data.F32, (float)standard->data.S32);
            return (compare->data.F32 == (float)standard->data.S32) ? true : false;
        case PS_TYPE_F32:
            psTrace(__func__, 10, "Comparing %f and %f...\n", compare->data.F32, standard->data.F32);
            return (compare->data.F32 == standard->data.F32) ? true : false;
        case PS_TYPE_F64:
            psTrace(__func__, 10, "Comparing %f and %f...\n", compare->data.F32, (float)standard->data.F64);
            return (compare->data.F32 == (float)standard->data.F64) ? true : false;
        default:
            return false;
        }
    case PS_TYPE_F64:
        switch (standard->type) {
        case PS_TYPE_S32:
            psTrace(__func__, 10, "Comparing %f and %f...\n", compare->data.F32, (double)standard->data.S32);
            return (compare->data.F64 == (double)standard->data.S32) ? true : false;
        case PS_TYPE_F32:
            psTrace(__func__, 10, "Comparing %f and %f...\n", compare->data.F32, (double)standard->data.F32);
            return (compare->data.F64 == (double)standard->data.F32) ? true : false;
        case PS_TYPE_F64:
            psTrace(__func__, 10, "Comparing %f and %f...\n", compare->data.F32, standard->data.F64);
            return (compare->data.F64 == standard->data.F64) ? true : false;
        default:
            return false;
        }
        return (compare->data.F64 == standard->data.F64) ? true : false;
    case PS_DATA_STRING:
        if (standard->type != PS_DATA_STRING) {
            return false;
        }
        psTrace(__func__, 10, "Comparing '%s' and '%s'\n", compare->data.V, standard->data.V);
        return (strcasecmp(compare->data.V, standard->data.V) == 0) ? true : false;
    case PS_DATA_LIST: {              // A list of strings
            // "compare" contains a list of psMetadataItems
            // "standard" likely contains just a string (but it might possibly be a list of strings)
            psList *cList = compare->data.V; // The list from comparison item
            psList *sList = NULL;         // The list from standard item
            switch (standard->type) {
            case PS_DATA_STRING:
                sList = psStringSplit(standard->data.V, " ;");
                break;
            case PS_DATA_LIST:
                sList = psMemIncrRefCounter(standard->data.V);
                break;
            default:
                return false;
            }
            if (cList->n != sList->n) {
                psFree(sList);
                return false;
            }
            psVector *match = psVectorAlloc(cList->n, PS_TYPE_U8); // Array indicating which values match
            psVectorInit(match, 0);
            psListIterator *cIter = psListIteratorAlloc(cList, PS_LIST_HEAD, false); // compare iterator
            psListIterator *sIter = psListIteratorAlloc(sList, PS_LIST_HEAD, false); // standard iterator
            psMetadataItem *cItem = NULL; // Item from compare list
            while ((cItem = psListGetAndIncrement(cIter))) {
                if (cItem->type != PS_DATA_STRING) {
                    psLogMsg(__func__, PS_LOG_WARN, "psMetadataItem from list is of type %x instead of "
                             "%x (PS_DATA_STRING) --- can't interpret.\n", cItem->type, PS_DATA_STRING);
                    psFree(cIter);
                    psFree(sIter);
                    psFree(match);
                    psFree(sList);
                    return false;
                }
                psString cString = cItem->data.V; // String from compare list
                psListIteratorSet(sIter, PS_LIST_HEAD);
                int index = 0;            // Index for list
                bool found = false;       // Found a match?
                for (psString sString = NULL; (sString = psListGetAndIncrement(sIter)) && !found; index++) {
                    if (strcasecmp(cString, sString) == 0) {
                        match->data.U8[index]++;
                        found = true;
                    }
                }
                if (! found) {
                    // Can give up immediately
                    psFree(cIter);
                    psFree(sIter);
                    psFree(match);
                    psFree(sList);
                    return false;
                }
            }
            // Make sure we got 100% matches in both directions
            bool allMatch = true;         // Did all of them match?
            for (int i = 0; i < match->n && allMatch; i++) {
                if (!match->data.U8[i]) {
                    allMatch = false;
                }
            }
            psFree(cIter);
            psFree(sIter);
            psFree(sList);
            psFree(match);
            return allMatch;
        }
    default:
        return false;
    }
    psAbort(__func__, "Should never get here.\n");
}


// Format a single concept
static psMetadataItem *conceptFormat(pmConceptSpec *spec, // The concept specification
                                     psMetadataItem *concept, // The concept to parse
                                     psMetadata *cameraFormat, // The camera format
                                     pmFPA *fpa, // The FPA
                                     pmChip *chip, // The chip
                                     pmCell *cell // The cell
                                    )
{
    if (concept) {
        psMetadataItem *formatted = NULL;  // The formatted concept
        if (spec->format) {
            formatted = spec->format(concept, cameraFormat, fpa, chip, cell);
        } else {
            formatted = psMemIncrRefCounter(concept);
        }

        return formatted;
    }
    return NULL;
}

// Write a single value to a header
static bool writeSingleHeader(pmHDU *hdu, // HDU for which to add to the header
                              const char *keyword, // Keyword to add
                              psMetadataItem *item // Item to add to the header
                             )
{
    switch (item->type) {
    case PS_DATA_STRING:
        return psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
                                item->data.V);
    case PS_DATA_S32:
        return psMetadataAddS32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
                                item->data.S32);
    case PS_DATA_F32:
        return psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
                                item->data.F32);
    case PS_DATA_F64:
        return psMetadataAddF64(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
                                item->data.F64);
    case PS_DATA_REGION: {
            psString region = psRegionToString(*(psRegion*)item->data.V);
            bool result = psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
                                           region);
            psFree(region);
            return result;
        }
    default:
        psLogMsg(__func__, PS_LOG_WARN, "Type of %s is not suitable for a FITS header --- not added.\n",
                 item->name);
        return false;
    }
}


// Write potentially multiple values to a header
static bool writeHeader(pmHDU *hdu,     // HDU for which to add to the header
                        const char *keywords, // Keywords to add
                        psMetadataItem *item // Item to add to the header
                       )
{
    bool status = true;                 // Status of writing headers, to be returned
    if (item->type == PS_DATA_LIST) {
        psList *values = item->data.V;  // List of outputs
        psList *keys = psStringSplit(keywords, " ,;"); // List of keywords
        if (keys->n != values->n) {
            psLogMsg(__func__, PS_LOG_WARN, "Number of keywords (%d) does not match number of values (%d).\n",
                     keys->n, values->n);
        }
        psListIterator *keysIter = psListIteratorAlloc(keys, PS_LIST_HEAD, false); // Iterator for keywords
        psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false); // Iterator for values
        psString key = NULL;            // Keyword from iteration
        psMetadataItem *value = NULL;   // Value from iteration
        while ((key = psListGetAndIncrement(keysIter)) && (value = psListGetAndIncrement(valuesIter))) {
            status |= writeSingleHeader(hdu, key, value);
        }
        psFree(keysIter);
        psFree(valuesIter);
        psFree(keys);
    } else {
        status = writeSingleHeader(hdu, keywords, item);
    }
    return status;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Public functions
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool pmConceptsWriteToCamera(psMetadata *specs, // The concept specifications
                             pmCell *cell,   // The cell
                             psMetadata *concepts // The concepts
                            )
{
    if (cell) {
        pmHDU *hdu = pmHDUGetLowest(NULL, NULL, cell); // The HDU at the lowest level
        if (!hdu) {
            return false;
        }
        psMetadata *cameraFormat = hdu->format; // The camera format
        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
        psMetadataItem *specItem = NULL;    // Item from the specs metadata
        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
            pmConceptSpec *spec = specItem->data.V; // The specification
            psString name = specItem->name; // The concept name
            psMetadataItem *cameraItem = psMetadataLookup(cell->config, name); // The concept from the camera,
            // or NULL
            if (cameraItem) {
                // Grab the concept
                psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The concept
                // Formatted version
                psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, NULL, NULL, cell);
                if (!formatted) {
                    continue;
                }
                psString nameSource = NULL; // String with the concept name and ".SOURCE" added
                psStringAppend(&nameSource, "%s.SOURCE", name);
                bool mdok = true;       // Status of MD lookup
                psString source = psMetadataLookupStr(&mdok, cell->config, nameSource); // The source
                if (mdok && strlen(source) > 0) {
                    psTrace(__func__, 8, "%s is %s\n", nameSource, source);
                    if (strcasecmp(source, "HEADER") == 0) {
                        if (cameraItem->type != PS_DATA_STRING) {
                            psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by header, but is not "
                                     "of type STR --- ignored.\n", conceptItem->name);
                            continue;
                        }
                        psTrace(__func__, 8, "Writing %s to header %s\n", name, cameraItem->data.V);
                        writeHeader(hdu, cameraItem->data.V, formatted);
                    } else if (strcasecmp(source, "VALUE") == 0) {
                        psTrace(__func__, 8, "Checking %s against camera format.\n", name);
                        if (! compareConcepts(formatted, cameraItem)) {
                            psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by value in the camera "
                                     "format, but the values don't match.\n", name);
                        }
                    } else {
                        psLogMsg(__func__, PS_LOG_WARN, "Concept source %s isn't HEADER or VALUE --- can't "
                                 "write\n", nameSource);
                    }
                } else if (! compareConcepts(formatted, cameraItem)) {
                    // Assume it's specified by value
                    psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by value in the camera "
                             "format, but the values don't match.\n", name);
                }
                psFree(formatted);
                psFree(nameSource);
            }

        }
        psFree(specsIter);
        return true;
    }
    return false;
}

bool pmConceptsWriteToDefaults(psMetadata *specs, // The concept specifications
                               pmFPA *fpa, // The FPA
                               pmChip *chip, // The chip
                               pmCell *cell, // The cell
                               psMetadata *concepts // The concepts
                              )
{
    pmHDU *hdu = pmHDUGetLowest(NULL, NULL, cell); // The HDU at the lowest level
    if (!hdu) {
        return false;
    }
    psMetadata *cameraFormat = hdu->format; // The camera format
    bool mdok = true;                   // Status of MD lookup
    psMetadata *defaults = psMetadataLookupMD(&mdok, cameraFormat, "DEFAULTS"); // The DEFAULTS spec
    if (mdok && defaults) {
        pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
        psMetadata *cameraFormat = hdu->format; // The camera format
        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
        psMetadataItem *specItem = NULL;    // Item from the specs metadata
        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
            pmConceptSpec *spec = specItem->data.V; // The specification
            psString name = specItem->name; // The concept name
            psMetadataItem *defaultItem = psMetadataLookup(defaults, name); // The item from the DEFAULTS
            if (defaultItem) {
                psMetadataItem *conceptItem = NULL; // The item from the concepts
                if (defaultItem->type == PS_DATA_METADATA) {
                    // It's a menu --- need to look up the .DEPEND
                    psString dependName = NULL; // The concept name with ".DEPEND" on the end
                    psStringAppend(&dependName, ".DEPEND");
                    psString dependKey = psMetadataLookupStr(&mdok, defaults, dependName); // The keyword
                    psFree(dependName);
                    if (!mdok || !dependKey || strlen(dependKey) == 0) {
                        psLogMsg(__func__, PS_LOG_WARN, "Can't find %s in the DEFAULTS for %s --- ignored.\n",
                                 dependName, name);
                        continue;
                    }
                    psString dependValue = psMetadataLookupStr(&mdok, concepts, dependName); // The value
                    if (!mdok || !dependKey || strlen(dependKey) == 0) {
                        psLogMsg(__func__, PS_LOG_WARN, "Concept %s specified by %s isn't of type STR -- "
                                 "ignored.\n", name, dependName);
                        continue;
                    }
                    conceptItem = psMetadataLookup(defaultItem->data.V, dependValue);
                } else {
                    conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
                }
                psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, fpa, chip, cell);
                if (!formatted) {
                    continue;
                }
                if (! compareConcepts(formatted, defaultItem)) {
                    psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by the DEFAULTS in the camera "
                             "format, but the values don't match.\n", name);
                }
                psFree(formatted);
            }
        }
        psFree(specsIter);
        return true;
    }
    return false;
}


bool pmConceptsWriteToHeader(psMetadata *specs, // The concept specifications
                             pmFPA *fpa, // The FPA
                             pmChip *chip, // The chip
                             pmCell *cell, // The cell
                             psMetadata *concepts // The concepts
                            )
{
    pmHDU *hdu = pmHDUGetLowest(NULL, NULL, cell); // The HDU at the lowest level
    if (!hdu) {
        return false;
    }
    psMetadata *cameraFormat = hdu->format; // The camera format
    bool mdok = true;                   // Status of MD lookup
    psMetadata *translation = psMetadataLookupMD(&mdok, cameraFormat, "TRANSLATION"); // The TRANSLATION spec
    if (mdok && translation) {
        pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
        psMetadata *cameraFormat = hdu->format; // The camera format
        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
        psMetadataItem *specItem = NULL;    // Item from the specs metadata
        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
            pmConceptSpec *spec = specItem->data.V; // The specification
            psString name = specItem->name; // The concept name
            psMetadataItem *headerItem = psMetadataLookup(translation, name); // The item from the TRANSLATION
            if (headerItem) {
                if (headerItem->type != PS_DATA_STRING) {
                    psLogMsg(__func__, PS_LOG_WARN, "TRANSLATION keyword for concept %s isn't of type STR ---"
                             " ignored.", name);
                    continue;
                }
                psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
                psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, fpa, chip, cell);
                if (!formatted) {
                    continue;
                }
                psList *keywords = psStringSplit(headerItem->data.V, " ,;"); // List of header keywords
                if (formatted->type == PS_DATA_LIST) {
                    psList *values = formatted->data.V; // The values for the headers
                    if (values->n != keywords->n) {
                        psLogMsg(__func__, PS_LOG_WARN, "Number of headers specified does not match number "
                                 "of values for concept %s.\n", name);
                    }
                    psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false); // Iterator
                    psListIterator *keywordsIter = psListIteratorAlloc(keywords, PS_LIST_HEAD, false);
                    psMetadataItem *valuesItem = NULL; // Item from list
                    while ((valuesItem = psListGetAndIncrement(valuesIter))) {
                        psString keyword = psListGetAndIncrement(keywordsIter); // Keyword from the list
                        if (strlen(keyword) > 0) {
                            writeHeader(hdu, keyword, formatted);
                        }
                    }
                    psFree(valuesIter);
                    psFree(keywordsIter);
                } else {
                    psString keyword = psListGet(keywords, PS_LIST_HEAD); // The keyword
                    writeHeader(hdu, keyword, formatted);
                }
                psFree(formatted);
                psFree(keywords);
            }
        }
        psFree(specsIter);
        return true;
    }
    return false;
}

// XXX Warning: This code has not been tested at all
bool pmConceptsWriteToDatabase(psMetadata *specs, // The concept specifications
                               pmFPA *fpa, // The FPA
                               pmChip *chip, // The chip
                               pmCell *cell, // The cell
                               psDB *db,// The database handle
                               psMetadata *concepts // The concepts
                              )
{
    pmHDU *hdu = pmHDUGetLowest(NULL, NULL, cell); // The HDU at the lowest level
    if (!hdu) {
        return false;
    }
    psMetadata *cameraFormat = hdu->format; // The camera format
    bool mdok = true;                   // Status of MD lookup
    psMetadata *database = psMetadataLookupMD(&mdok, cameraFormat, "DATABASE"); // The DATABASE spec
    if (mdok && database) {
        pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
        psMetadata *cameraFormat = hdu->format; // The camera format
        psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
        psMetadataItem *specItem = NULL;    // Item from the specs metadata
        while ((specItem = psMetadataGetAndIncrement(specsIter))) {
            pmConceptSpec *spec = specItem->data.V; // The specification
            psString name = specItem->name; // The concept name

            psMetadataItem *dbItem = psMetadataLookup(database, name); // The item from the DATABASE
            if (dbItem) {
                if (dbItem->type != PS_DATA_METADATA) {
                    psLogMsg(__func__, PS_LOG_WARN, "DATABASE keyword for concept %s isn't of type METADATA "
                             "--- ignored.\n", name);
                    continue;
                }

                psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
                psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, fpa, chip, cell);
                if (!formatted) {
                    continue;
                }

                psMetadata *dbLookup = dbItem->data.V; // How to look up the value of interest
                // Name of the table
                const char *tableName = psMetadataLookupStr(&mdok, dbLookup, "TABLE");
                // Name of "where" columns
                const char *givenCols = psMetadataLookupStr(&mdok, dbLookup, "GIVENDBCOL");
                // Values for "where" columns
                const char *givenPS = psMetadataLookupStr(&mdok, dbLookup, "GIVENPS");

                // 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", name);
                    } 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 *dependName = 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", name);
                            } else {
                                // Search for the value name
                                psMetadataItem *item = NULL; // The value
                                if (!item && cell) {
                                    item = psMetadataLookup(cell->concepts, dependName);
                                }
                                if (!item && chip) {
                                    item = psMetadataLookup(chip->concepts, dependName);
                                }
                                if (!item && fpa) {
                                    item = psMetadataLookup(fpa->concepts, dependName);
                                }
                                if (! item) {
                                    psLogMsg(__func__, PS_LOG_ERROR,
                                             "Unable to find the value name %s for DB "
                                             " lookup on %s --- ignored.\n", dependName, name);
                                } 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
                                    psMetadataAddItem(selection, formatted, PS_LIST_TAIL, PS_META_REPLACE);
                                    psFree(formatted);
                                }
                            }
                            psFree(dependName);
                            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", name);
                            return false;
                        } else {
                            if (dbResult->n > 1) {
                                psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s "
                                         "--- ignored.\n", name);
                            }
                            // Update the DB
                            psMetadata *update = psMetadataAlloc();
                            psMetadataAddItem(update, conceptItem, PS_LIST_HEAD, 0);
                            psDBUpdateRows(db, tableName, selection, update);
                            psFree(update);
                            return true;
                        }
                    }
                    psFree(cols);
                    psFree(values);
                } // Doing the "given"s.
            }
        }
        psFree(specsIter);
        return true;
    }
    return false;
}




#if 0

// 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->config) {
        return false;
    }
    if (cell) {
        psMetadataItem *item = psMetadataLookup(cell->config, 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;
        }
        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);
}

#endif
