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

#include "pmAstrometry.h"
#include "pmFPAConceptsGet.h"
#include "pmFPAConceptsSet.h"

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

#define COMPARE_REGIONS(a,b) (((a)->x0 == (b)->x0 && \
                               (a)->x1 == (b)->x1 && \
                               (a)->y0 == (b)->y0 && \
                               (a)->y1 == (b)->y1) ? true : false)


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 "set", but check to make sure it's there and matches
static bool setConceptInCamera(pmCell *cell, // The cell
                               psMetadataItem *concept // Concept
                              )
{
    if (cell) {
        psMetadataItem *item = psMetadataLookup(cell->camera, concept->name); // Info we want
        return compareConcepts(item, concept);
    }

    return false;
}


static bool setConceptInHeader(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
    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 "set", but check to see if it's there, and matches
static bool setConceptInDefault(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
    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 = p_pmFPAConceptGetFromHeader(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
static bool setConceptInDB(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
    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 = p_pmFPAConceptGetFromHeader(fpa, chip, cell, name);
                        if (! item) {
                            item = p_pmFPAConceptGetFromDefault(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 set from item
static bool setConceptItem(pmFPA *fpa, // The FPA
                           pmChip *chip,// The chip
                           pmCell *cell,        // The cell
                           psDB *db, // DB handle
                           psMetadataItem *concept // Concept item
                          )
{
    // Try headers, database, defaults in order
    psTrace(__func__, 3, "Trying to set concept %s...\n", concept->name);
    bool status = setConceptInCamera(cell, concept); // Status for return
    if (! status) {
        psTrace(__func__, 5, "Trying header....\n");
        status = setConceptInHeader(fpa, chip, cell, concept);
    }
    if (! status) {
        psTrace(__func__, 5, "Trying database....\n");
        status = setConceptInDB(fpa, chip, cell, db, concept);
    }
    if (! status) {
        psTrace(__func__, 5, "Checking defaults....\n");
        status = setConceptInDefault(fpa, chip, cell, concept);
    }

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

    return status;
}


// Concept set
static bool setConcept(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 setConceptItem(fpa, chip, cell, db, concept);
}


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

// Outget concepts from the FPA
void pmFPAOutgestConcepts(pmFPA *fpa,   // The FPA
                          psDB *db      // DB handle
                         )
{
    // FPA.NAME
    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.NAME");

    // FPA.AIRMASS
    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.AIRMASS");

    // FPA.FILTER
    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.FILTER");

    // FPA.POSANGLE
    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.POSANGLE");

    // FPA.RADECSYS
    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.RADECSYS");

    // These take some extra work

    // FPA.RA
    {
        double ra = psMetadataLookupF64(NULL, fpa->concepts, "FPA.RA"); // The RA

        // How to interpret the RA
        const psMetadata *camera = fpa->camera; // Camera configuration data
        bool mdok = true;               // Status of MD lookup
        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
        if (mdok && formats)
        {
            psString raFormat = psMetadataLookupStr(&mdok, formats, "FPA.RA");
            if (mdok && strlen(raFormat) > 0) {
                if (strcasecmp(raFormat, "HOURS") == 0) {
                    ra /= M_PI / 12.0;
                } else if (strcasecmp(raFormat, "DEGREES") == 0) {
                    ra /= M_PI / 180.0;
                } else if (strcasecmp(raFormat, "RADIANS") == 0) {
                    // No action required
                } else {
                    psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.RA in FORMATS (%s) --- assuming"
                             " HOURS.\n");
                    ra /= M_PI / 12.0;
                }
            } else {
                psError(PS_ERR_IO, false, "Unable to find FPA.RA in FORMATS --- assuming HOURS.\n");
                ra /= M_PI / 12.0;
            }
        } else
        {
            psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
                    "assuming format for FPA.RA is HOURS.\n");
            ra /= M_PI / 12.0;
        }

        // We choose to write sexagesimal format
        int big, medium;
        float small;
        big = (int)ra;
        medium = (int)(60.0*(ra - (double)big));
        small = 3600.0*(ra - (double)big - 60.0 * (double)medium);
        psString raString = psStringCopy("");
        psStringAppend(&raString, "%d:%d:%.2f", big, medium, small);
        psMetadataItem *raItem = psMetadataItemAllocStr("FPA.RA", "Right Ascension of the boresight",
                                 raString);
        setConceptItem(fpa, NULL, NULL, db, raItem);
        psFree(raItem);
        psFree(raString);
    }

    // FPA.DEC
    {
        double dec = psMetadataLookupF64(NULL, fpa->concepts, "FPA.DEC"); // The DEC

        // How to interpret the DEC
        const psMetadata *camera = fpa->camera; // Camera configuration data
        bool mdok = true;               // Status of MD lookup
        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
        if (mdok && formats)
        {
            psString decFormat = psMetadataLookupStr(&mdok, formats, "FPA.DEC");
            if (mdok && strlen(decFormat) > 0) {
                if (strcasecmp(decFormat, "HOURS") == 0) {
                    dec /= M_PI / 12.0;
                } else if (strcasecmp(decFormat, "DEGREES") == 0) {
                    dec /= M_PI / 180.0;
                } else if (strcasecmp(decFormat, "RADIANS") == 0) {
                    // No action required
                } else {
                    psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.DEC in FORMATS (%s) --- assuming"
                             " DEGREES.\n");
                    dec /= M_PI / 180.0;
                }
            } else {
                psError(PS_ERR_IO, false, "Unable to find FPA.DEC in FORMATS --- assuming DEGREES.\n");
                dec /= M_PI / 180.0;
            }
        } else
        {
            psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
                    "assuming format for FPA.DEC is HOURS.\n");
            dec /= M_PI / 12.0;
        }

        // We choose to write sexagesimal format
        int big, medium;
        float small;
        big = (int)dec;
        medium = (int)(60.0*(dec - (double)big));
        small = 3600.0*(dec - (double)big - 60.0 * (double)medium);
        psString decString = psStringCopy("");
        psStringAppend(&decString, "%d:%d:%.2f", big, medium, small);
        psMetadataItem *decItem = psMetadataItemAllocStr("FPA.DEC", "Right Ascension of the boresight",
                                  decString);
        setConceptItem(fpa, NULL, NULL, db, decItem);
        psFree(decItem);
        psFree(decString);

    }

    // Pau.
}


// Outgest concepts for the chip
void pmChipOutgestConcepts(pmChip *chip, // The chip
                           psDB *db     // DB handle
                          )
{
    //    pmFPA *fpa = chip->parent;          // The parent FPA

    // CHIP.NAME --- no need to do anything

    // Pau.
}


// Outgest concepts for the cell
void pmCellOutgestConcepts(pmCell *cell, // The cell
                           psDB *db     // DB handle
                          )
{
    pmChip *chip = cell->parent;        // The parent chip
    pmFPA *fpa = chip->parent;          // The parent FPA

    // CELL.NAME --- no need to do anything

    // CELL.GAIN
    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.GAIN");

    // CELL.READNOISE
    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.READNOISE");

    // CELL.SATURATION
    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.SATURATION");

    // CELL.BAD
    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.BAD");

    // CELL.XPARITY
    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.XPARITY");

    // CELL.YPARITY
    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.YPARITY");

    // CELL.READDIR
    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.READDIR");

    // These used to be pmReadoutGetExposure and pmReadoutGetDarkTime, but that doesn't really make sense at
    // the moment.  Maybe we need to add a "parent" link to the readouts.  But then how are the exposure times
    // REALLY derived?  They're not in the FITS headers, because a readout is a plane in a 3D image.  We'll
    // have to dream up some additional suffix to specify these, but for now....

    // CELL.EXPOSURE (used to be READOUT.EXPOSURE)
    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.EXPOSURE");

    // CELL.DARKTIME (used to be READOUT.DARKTIME)
    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.DARKTIME");

    // These take some extra work

    // CELL.TRIMSEC
    {
        psMetadataItem *trimsecItem = psMetadataLookup(cell->concepts, "CELL.TRIMSEC");
        psRegion *trimsec = trimsecItem->data.V; // The trimsec region
        psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE");
        if (! sourceItem)
        {
            psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.SOURCE.\n");
        } else if (sourceItem->type != PS_DATA_STRING)
        {
            psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
        } else
        {
            psString source = sourceItem->data.V; // The source string

            if (strcasecmp(source, "VALUE") == 0) {
                // Check that it's the same value as stored in the camera
                psString checkString = psMetadataLookupStr(NULL, cell->camera, "CELL.TRIMSEC");
                psRegion checkRegion = psRegionFromString(checkString);
                if (! COMPARE_REGIONS(&checkRegion, trimsec)) {
                    psError(PS_ERR_IO, true, "Target CELL.TRIMSEC is specified by value, and values don't "
                            "match.\n");
                }
            } else if (strcasecmp(source, "HEADER") == 0) {
                //                psString keyword = psMetadataLookupStr(NULL, cell->camera, "CELL.TRIMSEC");
                psMetadata *header = NULL; // The FITS header
                if (cell->hdu) {
                    header = cell->hdu->header;
                } else if (chip->hdu) {
                    header = chip->hdu->header;
                } else if (fpa->hdu) {
                    header = fpa->hdu->header;
                }
                if (! header) {
                    psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
                } else {
                    psMetadataAddItem(header, trimsecItem, PS_LIST_TAIL, PS_META_REPLACE);
                }
            } else {
                psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE (%s) is not HEADER or VALUE.\n", source);
            }
        }
    }

    // CELL.BIASSEC
    {
        psMetadataItem *biassecItem = psMetadataLookup(cell->concepts, "CELL.BIASSEC");
        psList *biassecs = biassecItem->data.V; // The biassecs region list
        psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.BIASSEC.SOURCE");
        if (! sourceItem)
        {
            psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.SOURCE.\n");
        } else if (sourceItem->type != PS_DATA_STRING)
        {
            psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
        } else
        {
            psString source = sourceItem->data.V; // The source string

            if (strcasecmp(source, "VALUE") == 0) {
                // Check that it's the same value as stored in the camera
                psString checkString = psMetadataLookupStr(NULL, cell->camera, "CELL.BIASSEC");
                psList *checkList = psStringSplit(checkString, " ;");
                if (biassecs->n != checkList->n) {
                    psError(PS_ERR_IO, true, "Target CELL.BIASSEC is specified by value, but number of "
                            "entries doesn't match.\n");
                } else {
                    // We don't care if the order matches or not
                    psVector *check = psVectorAlloc(biassecs->n, PS_TYPE_U8); // Vector to mark regions off
                    for (int i = 0; i < check->n; i++) {
                        check->data.U8[i] = 0;
                    }
                    psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD,
                                                   false); // Iterator
                    psListIterator *checkListIter = psListIteratorAlloc(checkList, PS_LIST_HEAD, false);
                    psRegion *biassec = NULL; // Region from iteration
                    while ((biassec = psListGetAndIncrement(biassecsIter))) {
                        psListIteratorSet(checkListIter, PS_LIST_HEAD);
                        psString checkRegionString = NULL; // Region string from iteration
                        int i = 0;              // Counter
                        while ((checkRegionString = psListGetAndIncrement(checkListIter))) {
                            psRegion checkRegion = psRegionFromString(checkRegionString);
                            psTrace(__func__, 7, "Checking [%.0f:%.0f,%.0f:%.0f] against "
                                    "[%.0f:%.0f,%.0f:%.0f]\n", biassec->x0, biassec->x1, biassec->y0,
                                    biassec->y1, checkRegion.x0, checkRegion.x1, checkRegion.y0,
                                    checkRegion.y1);
                            if (COMPARE_REGIONS(biassec, &checkRegion)) {
                                check->data.U8[i] = 1;
                                i++;
                                break;
                            }
                            i++;
                        }
                    }
                    for (int i = 0; i < check->n; i++) {
                        if (check->data.U8[i] == 0) {
                            psError(PS_ERR_IO, true, "Target CELL.BIASSEC is specified by value, but values "
                                    "don't match.\n");
                            break;
                        }
                    }
                    psFree(checkListIter);
                    psFree(biassecsIter);
                    psFree(check);
                }
                psFree(checkList);
            } else if (strcasecmp(source, "HEADER") == 0) {
                psString keywordsString = psMetadataLookupStr(NULL, cell->camera, "CELL.BIASSEC");
                psList *keywords = psStringSplit(keywordsString, " ,;");
                if (biassecs->n != keywords->n) {
                    psError(PS_ERR_IO, true, "Target CELL.BIASSEC is sepcified by headers, but the number "
                            "of headers doesn't match.\n");
                } else {
                    psMetadata *header = NULL; // The FITS header
                    if (cell->hdu) {
                        header = cell->hdu->header;
                    } else if (chip->hdu) {
                        header = chip->hdu->header;
                    } else if (fpa->hdu) {
                        header = fpa->hdu->header;
                    }
                    if (! header) {
                        psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
                    } else {
                        psListIterator *keywordsIter = psListIteratorAlloc(keywords, PS_LIST_HEAD, false);
                        psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false);
                        psString keyword = NULL; // Header keyword from list
                        while ((keyword = psListGetAndIncrement(keywordsIter))) {
                            // Update the header
                            psRegion *biassec = psListGetAndIncrement(biassecsIter);
                            psString biassecString = psRegionToString(*biassec);
                            psMetadataAdd(header, PS_LIST_TAIL, keyword, PS_DATA_STRING | PS_META_REPLACE,
                                          "Bias section", biassecString);
                            psFree(biassecString);
                        }
                        psFree(keywordsIter);
                        psFree(biassecsIter);
                    }
                }
                psFree(keywords);
            } else {
                psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE (%s) is not HEADER or VALUE.\n", source);
            }
        }
    }

    // CELL.XBIN and CELL.YBIN
    {
        psMetadataItem *xBinItem = psMetadataLookup(cell->concepts, "CELL.XBIN"); // Binning factor in x
        psMetadataItem *yBinItem = psMetadataLookup(cell->concepts, "CELL.YBIN"); // Binning factor in y

        // If these are specified by the header, we need to check to see if it's the same header keyword
        const psMetadata *camera = fpa->camera;
        psMetadata *translation = psMetadataLookupMD(NULL, camera, "TRANSLATION");
        psString xKeyword = psMetadataLookupStr(NULL, translation, "CELL.XBIN");
        psString yKeyword = psMetadataLookupStr(NULL, translation, "CELL.YBIN");
        if (strlen(xKeyword) > 0 && strcasecmp(xKeyword, yKeyword) != 0)
        {
            setConceptInHeader(fpa, chip, cell, xBinItem);
            xBinItem = NULL;
        }
        if (strlen(yKeyword) > 0 && strcasecmp(xKeyword, yKeyword) != 0)
        {
            setConceptInHeader(fpa, chip, cell, yBinItem);
            yBinItem = NULL;
        }
        if (strlen(xKeyword) > 0 && strlen(yKeyword) > 0 && strcasecmp(xKeyword, yKeyword) == 0)
        {
            psString binString = psStringCopy("");
            psStringAppend(&binString, "%d,%d", xBinItem->data.S32, yBinItem->data.S32);
            psMetadataItem *binItem = psMetadataItemAllocStr(xKeyword, "Binning factor in x and y",
                                      binString);
            psFree(binString);
            psMetadata *header = NULL; // The FITS header
            if (cell->hdu) {
                header = cell->hdu->header;
            } else if (chip->hdu) {
                header = chip->hdu->header;
            } else if (fpa->hdu) {
                header = fpa->hdu->header;
            }
            if (! header) {
                psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
            } else {
                psMetadataAddItem(header, binItem, PS_LIST_TAIL, PS_META_REPLACE);
                xBinItem = NULL;
                yBinItem = NULL;
            }
            psFree(binItem);
        }

        // Do it in the usual way if we have to
        if (xBinItem)
        {
            setConceptItem(fpa, chip, cell, db, xBinItem);
        }
        if (yBinItem)
        {
            setConceptItem(fpa, chip, cell, db, yBinItem);
        }
    }

    // CELL.TIMESYS
    {
        psMetadataItem *sysItem = psMetadataLookup(cell->concepts, "CELL.TIMESYS");
        psString sys = NULL;            // String to store
        switch (sysItem->data.S32)
        {
        case PS_TIME_TAI:
            sys = psStringCopy("TAI");
            break;
        case PS_TIME_UTC:
            sys = psStringCopy("UTC");
            break;
        case PS_TIME_UT1:
            sys = psStringCopy("UT1");
            break;
        case PS_TIME_TT:
            sys = psStringCopy("TT");
            break;
        default:
            sys = psStringCopy("Unknown");
        }
        psMetadataItem *newItem = psMetadataItemAllocStr("CELL.TIMESYS", "Time system", sys);
        setConceptItem(fpa, chip, cell, db, newItem);
        psFree(newItem);
        psFree(sys);
    }

    // CELL.TIME
    {
        psMetadataItem *timeItem = psMetadataLookup(cell->concepts, "CELL.TIME");
        psTime *time = timeItem->data.V; // The time
        psString dateTimeString = psTimeToISO(time); // String representation

        psMetadata *formats = psMetadataLookupMD(NULL, fpa->camera, "FORMATS");
        psString format = psMetadataLookupStr(NULL, formats, "CELL.TIME");

        if (strlen(format) == 0)
        {
            // No format specified --> do it in the usual way (maybe it's a DB lookup)
            psMetadataItem *newTimeItem = psMetadataItemAllocStr("CELL.TIME", "Time of observation",
                                          dateTimeString);
            setConceptItem(fpa, chip, cell, db, newTimeItem);
            psFree(newTimeItem);
        } else
        {
            if (strcasecmp(format, "ISO") == 0) {
                // dateTimeString contains an ISO time
                psMetadataItem *newTimeItem = psMetadataItemAllocStr("CELL.TIME", "Time of observation",
                                              dateTimeString);
                setConceptItem(fpa, chip, cell, db, newTimeItem);
                psFree(newTimeItem);
            } else if (strstr(format, "SEPARATE")) {
                // We're working with two separate headers
                psMetadata *header = NULL; // The FITS header
                if (cell->hdu) {
                    header = cell->hdu->header;
                } else if (chip->hdu) {
                    header = chip->hdu->header;
                } else if (fpa->hdu) {
                    header = fpa->hdu->header;
                }
                if (! header) {
                    psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
                } else {
                    // Get the headers
                    const psMetadata *camera = fpa->camera;
                    psMetadata *translation = psMetadataLookupMD(NULL, camera, "TRANSLATION");
                    psString keywords = psMetadataLookupStr(NULL, translation, "CELL.TIME");
                    psList *dateTimeKeywords = psStringSplit(keywords, " ,;");
                    psString dateKeyword = psListGet(dateTimeKeywords, PS_LIST_HEAD);
                    psString timeKeyword = psListGet(dateTimeKeywords, PS_LIST_TAIL);

                    psList *dateTime = psStringSplit(dateTimeString, " T"); // Find the middle T
                    psString dateString = psListGet(dateTime, PS_LIST_HEAD);
                    psString timeString = psListGet(dateTime, PS_LIST_TAIL);

                    // XXX: Couldn't be bothered doing these right now
                    if (strstr(format, "PRE2000")) {
                        psError(PS_ERR_IO, true, "Don't you realise it's the twenty-first century?\n");
                    }
                    if (strstr(format, "BACKWARDS")) {
                        psError(PS_ERR_IO, true, "You want it BACKWARDS?  Not right now, thanks.\n");
                    }

                    psMetadataItem *dateItem = psMetadataItemAllocStr(dateKeyword, "Date of observation",
                                               dateString);
                    psMetadataItem *timeItem = psMetadataItemAllocStr(timeKeyword, "Time of observation",
                                               timeString);
                    psMetadataAddItem(header, dateItem, PS_LIST_TAIL, PS_META_REPLACE);
                    psMetadataAddItem(header, timeItem, PS_LIST_TAIL, PS_META_REPLACE);

                    psFree(dateTimeKeywords);
                    psFree(dateTime);
                    psFree(dateItem);
                    psFree(timeItem);
                }
            } else if (strcasecmp(format, "MJD") == 0) {
                double mjd = psTimeToMJD(time);
                psMetadataItem *newTimeItem = psMetadataItemAllocF64("CELL.TIME", "MJD of observation", mjd);
                setConceptItem(fpa, chip, cell, db, newTimeItem);
                psFree(newTimeItem);
            } else if (strcasecmp(format, "JD") == 0) {
                double jd = psTimeToMJD(time);
                psMetadataItem *newTimeItem = psMetadataItemAllocF64("CELL.TIME", "JD of observation", jd);
                setConceptItem(fpa, chip, cell, db, newTimeItem);
                psFree(newTimeItem);
            } else {
                psError(PS_ERR_IO, true, "Not sure how to outgest CELL.TIME (%s) --- trying "
                        "ISO\n", dateTimeString);
                psMetadataItem *newTimeItem = psMetadataItemAllocStr("CELL.TIME", "Time of observation",
                                              dateTimeString);
                setConceptItem(fpa, chip, cell, db, newTimeItem);
                psFree(newTimeItem);
            }
        }

        psFree(dateTimeString);
    }

    // Remove corrective
    {
        const psMetadata *camera = fpa->camera;
        bool mdok = false;              // Result of MD lookup
        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
        if (mdok && formats)
        {
            psString format = psMetadataLookupStr(&mdok, formats, "CELL.X0");
            if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
                psMetadataItem *cellx0 = psMetadataLookup(cell->concepts, "CELL.X0");
                cellx0->data.S32 += 1;
            }
            format = psMetadataLookupStr(&mdok, formats, "CELL.Y0");
            if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
                psMetadataItem *celly0 = psMetadataLookup(cell->concepts, "CELL.Y0");
                celly0->data.S32 += 1;
            }
        }
    }
    // CELL.X0
    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.X0");
    // CELL.Y0
    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.Y0");

    // Pau.
}

