#include <stdio.h>

#include "pslib.h"

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


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


// Read FPA.RA and translate
psMetadataItem *pmConceptRead_FPA_RA(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    double ra = NAN;                    // The Right Ascension
    psMetadataItem *raItem = pmConceptRead(fpa, NULL, NULL, db, "FPA.RA"); // The FPA.RA item
    if (raItem) {
        switch (raItem->type) {
        case PS_TYPE_F32:
            ra = raItem->data.F32;
            break;
        case PS_TYPE_F64:
            ra = raItem->data.F64;
            break;
        case PS_DATA_STRING:
            // Sexagesimal format
            {
                int big, medium;
                float small;
                // XXX: Upgrade path is to allow dd:mm.mmm
                if (sscanf(raItem->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
                        sscanf(raItem->data.V, "%d %d %f", &big, &medium, &small) != 3)
                {
                    psError(PS_ERR_IO, true, "Cannot interpret FPA.RA: %s\n", raItem->data.V);
                    break;
                }
                ra = abs(big) + (float)medium/60.0 + small/3600.0;
                if (big < 0)
                {
                    ra *= -1.0;
                }
            }
            break;
        default:
            psError(PS_ERR_IO, true, "FPA.RA is of an unexpected type: %x\n", raItem->type);
        }

        // 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;
        }
    } else {
        psError(PS_ERR_IO, false, "Couldn't find FPA.RA.\n");
    }

    return psMetadataItemAllocF64("FPA.RA", "Right Ascension of boresight", ra);
}

// Read FPA.DEC and translate
psMetadataItem *pmConceptRead_FPA_DEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    double dec = NAN;               // The DEC
    psMetadataItem *decItem = pmConceptRead(fpa, NULL, NULL, db, "FPA.DEC"); // The FPA.DEC item
    if (decItem) {
        switch (decItem->type) {
        case PS_TYPE_F32:
            dec = decItem->data.F32;
            break;
        case PS_TYPE_F64:
            dec = decItem->data.F64;
            break;
        case PS_DATA_STRING:
            // Sexagesimal format
            {
                int big, medium;
                float small;
                // XXX: Upgrade path is to allow dd:mm.mmm
                if (sscanf(decItem->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
                        sscanf(decItem->data.V, "%d %d %f", &big, &medium, &small) != 3)
                {
                    psError(PS_ERR_IO, true, "Cannot interpret FPA.DEC: %s\n", decItem->data.V);
                    break;
                }
                dec = abs(big) + (float)medium/60.0 + small/3600.0;
                if (big < 0)
                {
                    dec *= -1.0;
                }
            }
            break;
        default:
            psError(PS_ERR_IO, true, "FPA.DEC is of an unexpected type: %x\n", decItem->type);
        }

        // 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 FORMATS metadata in camera configuration --- "
                    "assuming format for FPA.DEC is DEGREES.\n");
            dec *= M_PI / 180.0;
        }
    } else {
        psError(PS_ERR_IO, false, "Couldn't find FPA.DEC.\n");
    }

    return psMetadataItemAllocF64("FPA.DEC", "Declination of boresight", dec);
}


bool pmConceptWrite_FPA_RA(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    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);
    pmConceptWriteItem(fpa, NULL, NULL, db, raItem);
    psFree(raItem);
    psFree(raString);

    return true;
}

bool pmConceptWrite_FPA_DEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    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);
    pmConceptWriteItem(fpa, NULL, NULL, db, decItem);
    psFree(decItem);
    psFree(decString);

    return true;
}


psMetadataItem *pmConceptRead_CELL_TRIMSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    psRegion *trimsec = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed by value)

    psMetadataItem *secItem = pmConceptRead(fpa, chip, cell, db, "CELL.TRIMSEC");
    if (! secItem) {
        psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.\n");
        *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
    } else if (secItem->type != PS_DATA_STRING) {
        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not of type STR (%x)\n", secItem->type);
        *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
    } else {
        psString section = secItem->data.V; // The section string

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

            if (strcasecmp(source, "VALUE") == 0) {
                *trimsec = psRegionFromString(section);
            } else if (strcasecmp(source, "HEADER") == 0) {
                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");
                    *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
                } else {
                    bool mdok = true;               // Status of MD lookup
                    psString secValue = psMetadataLookupStr(&mdok, header, section);
                    if (! mdok || ! secValue) {
                        psError(PS_ERR_IO, false, "Unable to locate header %s\n", section);
                        *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
                    } else {
                        *trimsec = psRegionFromString(secValue);
                    }
                }
            } else {
                psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
                        "VALUE.\n", source);
                *trimsec = psRegionFromString(section);
            } // Value of CELL.TRIMSEC.SOURCE
        } // Looking up CELL.TRIMSEC.SOURCE
    } // Looking up CELL.TRIMSEC

    psMetadataItem *item = psMetadataItemAllocPtr("CELL.TRIMSEC", PS_DATA_UNKNOWN, "Trim section", trimsec);
    psFree(trimsec);
    return item;
}


psMetadataItem *pmConceptRead_CELL_BIASSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    psList *biassecs = psListAlloc(NULL); // List of bias sections
    psMetadataItem *item = psMetadataItemAlloc("CELL.BIASSEC", PS_DATA_LIST, "Bias sections", biassecs);
    psFree(biassecs);               // Drop reference

    psMetadataItem *secItem = pmConceptRead(fpa, chip, cell, db, "CELL.BIASSEC");
    if (! secItem) {
        psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.\n");
        return item;
    }
    if (secItem->type != PS_DATA_STRING) {
        psError(PS_ERR_IO, true, "CELL.BIASSEC is not of type STR (%x)\n", secItem->type);
        return item;
    }

    psString sections = secItem->data.V; // The section string

    psMetadataItem *sourceItem = pmConceptRead(fpa, chip, cell, db, "CELL.BIASSEC.SOURCE");
    if (! sourceItem) {
        psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.SOURCE.\n");
        return item;
    } else if (sourceItem->type != PS_DATA_STRING) {
        psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
        return item;
    }

    psString source = sourceItem->data.V; // The source string

    if (! strcasecmp(source, "NONE")) {
        return item;                // There is no biassec
    }

    psList *secList = psStringSplit(sections, " ;"); // List of sections
    psListIterator *secIter = psListIteratorAlloc(secList, PS_LIST_HEAD, false); // Iterator over
    // sections
    psString aSection = NULL; // A section from the list
    while ((aSection = psListGetAndIncrement(secIter))) {
        psRegion *region = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed by
        // value)

        if (strcasecmp(source, "VALUE") == 0) {
            *region = psRegionFromString(aSection);
        } else if (strcasecmp(source, "HEADER") == 0) {
            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");
                *region = psRegionSet(0.0,0.0,0.0,0.0);
            } else {
                bool mdok = true;           // Status of MD lookup
                psString secValue = psMetadataLookupStr(&mdok, header, aSection);
                if (! mdok || ! secValue) {
                    psError(PS_ERR_IO, false, "Unable to locate header %s\n", aSection);
                    *region = psRegionSet(0.0,0.0,0.0,0.0);
                } else {
                    *region = psRegionFromString(secValue);
                }
            }
        } else {
            psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
                    "VALUE.\n", source);
            *region = psRegionFromString(aSection);
        } // Value of CELL.BIASSEC.SOURCE

        psListAdd(biassecs, PS_LIST_TAIL, region);
        psFree(region);
    } // Iterating over multiple sections
    psFree(secIter);
    psFree(secList);

    return item;
}


psMetadataItem *pmConceptRead_CELL_XBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    int xBin = 1;                   // Binning factor in x
    psMetadataItem *binItem = pmConceptRead(fpa, chip, cell, db, "CELL.XBIN");
    if (! binItem) {
        psError(PS_ERR_IO, false, "Couldn't find CELL.XBIN.\n");
    } else if (binItem->type == PS_DATA_STRING) {
        psString binString = binItem->data.V; // The string containing the binning
        if (sscanf(binString, "%d %*d", &xBin) != 1 &&
                sscanf(binString, "%d,%*d", &xBin) != 1) {
            psError(PS_ERR_IO, true, "Unable to read string to get x binning: %s\n", binString);
        }
    } else if (binItem->type == PS_TYPE_S32) {
        xBin = binItem->data.S32;
    } else {
        psError(PS_ERR_IO, true, "Note sure how to interpret CELL.XBIN of type %x --- assuming 1.\n",
                binItem->type);
    }

    return psMetadataItemAllocS32("CELL.XBIN", "Binning in x", xBin);
}

psMetadataItem *pmConceptRead_CELL_YBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    int yBin = 1;                   // Binning factor in y
    psMetadataItem *binItem = pmConceptRead(fpa, chip, cell, db, "CELL.YBIN");
    if (! binItem) {
        psError(PS_ERR_IO, false, "Couldn't find CELL.YBIN.\n");
    } else if (binItem->type == PS_DATA_STRING) {
        psString binString = binItem->data.V; // The string containing the binning
        if (sscanf(binString, "%*d %d", &yBin) != 1 &&
                sscanf(binString, "%*d,%d", &yBin) != 1) {
            psError(PS_ERR_IO, true, "Unable to read string to get y binning: %s\n", binString);
        }
    } else if (binItem->type == PS_TYPE_S32) {
        yBin = binItem->data.S32;
    } else {
        psError(PS_ERR_IO, true, "Note sure how to interpret CELL.YBIN of type %x --- assuming 1.\n",
                binItem->type);
    }

    return psMetadataItemAllocS32("CELL.YBIN", "Binning in y", yBin);
}

psMetadataItem *pmConceptRead_CELL_TIMESYS(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    psTimeType timeSys = PS_TIME_UTC;   // The time system
    psString sys = pmConceptReadString(fpa, chip, cell, db, "CELL.TIMESYS"); // The time system as a string
    if (! sys || strlen(sys) <= 0) {
        psError(PS_ERR_IO, true, "Can't interpret CELL.TIMESYS --- assuming UTC.\n");
    } else if (strcasecmp(sys, "TAI") == 0) {
        timeSys = PS_TIME_TAI;
    } else if (strcasecmp(sys, "UTC") == 0) {
        timeSys = PS_TIME_UTC;
    } else if (strcasecmp(sys, "UT1") == 0) {
        timeSys = PS_TIME_UT1;
    } else if (strcasecmp(sys, "TT") == 0) {
        timeSys = PS_TIME_TT;
    } else {
        psError(PS_ERR_IO, true, "Can't interpret CELL.TIMESYS --- assuming UTC.\n");
    }

    psFree(sys);
    return psMetadataItemAllocS32("CELL.TIMESYS", "Time system", timeSys);
}


psMetadataItem *pmConceptRead_CELL_TIME(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    // Need CELL.TIMESYS first
    bool mdok = false;                  // Result of MD lookup
    psTimeType timeSys = psMetadataLookupS32(&mdok, cell->concepts, "CELL.TIMESYS"); // The time system
    if (!mdok) {
        psLogMsg(__func__, PS_LOG_WARN, "Unable to find CELL.TIMESYS in concepts --- assuming UTC.\n");
        timeSys = PS_TIME_UTC;
    }
    psTime *time = NULL;                // The time

    psMetadataItem *timeItem = pmConceptRead(fpa, chip, cell, db, "CELL.TIME");
    if (! timeItem) {
        psError(PS_ERR_IO, false, "Couldn't find CELL.TIME.\n");
    } else {
        // Get format
        const psMetadata *camera = fpa->camera; // The camera configuration data
        bool mdok = true;               // Status of MD lookup
        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
        if (mdok && formats) {
            psString timeFormat = psMetadataLookupStr(&mdok, formats, "CELL.TIME");
            if (mdok && strlen(timeFormat) > 0) {
                switch (timeItem->type) {
                case PS_DATA_STRING: {
                        psString timeString = timeItem->data.V;   // String with the time
                        if (strcasecmp(timeFormat, "ISO") == 0) {
                            // timeString contains an ISO time
                            time = psTimeFromISO(timeString, timeSys);
                        } else if (strcasecmp(timeFormat, "JD") == 0) {
                            double timeValue = strtod (timeString, NULL);
                            time = psTimeFromJD(timeValue);
                        } else if (strcasecmp(timeFormat, "MJD") == 0) {
                            double timeValue = strtod (timeString, NULL);
                            time = psTimeFromMJD(timeValue);
                        } else if (strstr(timeFormat, "SEPARATE")) {
                            // timeString contains headers for the date and time
                            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
                                char *stuff1 = strpbrk(timeString, " ,;");
                                psString dateName = psStringNCopy(timeString,
                                                                  strlen(timeString) - strlen(stuff1));
                                char *stuff2 = strpbrk(stuff1, "abcdefghijklmnopqrstuvwxyz"
                                                       "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
                                psString timeName = psStringCopy(stuff2);

                                bool mdok = true; // Status of MD lookup
                                psString dateString = psMetadataLookupStr(&mdok, header, dateName);
                                psFree(dateName);
                                int day = 0, month = 0, year = 0;
                                if (sscanf(dateString, "%d-%d-%d", &day, &month, &year) != 3 &&
                                        sscanf(dateString, "%d/%d/%d", &day, &month, &year) != 3) {
                                    psError(PS_ERR_IO, true, "Unable to read date: %s\n", dateString);
                                } else {
                                    if (strstr(timeFormat, "BACKWARDS")) {
                                        int temp = day;
                                        day = year;
                                        year = temp;
                                    }
                                    if (strstr(timeFormat, "PRE2000") || year < 2000) {
                                        year += 2000;
                                    }

                                    psMetadataItem *timeItem = psMetadataLookup(header, timeName);
                                    if (! timeItem) {
                                        psError(PS_ERR_IO, false, "Unable to find time header: %s\n",
                                                timeName);
                                    } else if (timeItem->type == PS_DATA_STRING) {
                                        // Time is a string, in the usual way:
                                        psStringAppend(&dateString, "T%s", timeItem->data.V);
                                    } else {
                                        // Assume that time is specified in Second of Day
                                        double seconds = NAN;
                                        switch (timeItem->type) {
                                        case PS_TYPE_S32:
                                            seconds = timeItem->data.S32;
                                            break;
                                        case PS_TYPE_F32:
                                            seconds = timeItem->data.F32;
                                            break;
                                        case PS_TYPE_F64:
                                            seconds = timeItem->data.F64;
                                            break;
                                        default:
                                            psError(PS_ERR_IO, true, "Time header (%s) is not of an "
                                                    "expected type: %x\n", timeName, timeItem->type);
                                        }
                                        // Now print to timeString as "hh:mm:ss.ss"
                                        int hours = seconds / 3600;
                                        seconds -= (double)hours * 3600.0;
                                        int minutes = seconds / 60;
                                        seconds -= (double)minutes * 60.0;
                                        psStringAppend(&dateString, "T%02d:%02d:%02f", hours, minutes,
                                                       seconds);
                                    }
                                    time = psTimeFromISO(dateString, timeSys);
                                } // Reading date and time
                                psFree(timeName);
                            } // Reading headers
                        } else {
                            psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%s) --- trying "
                                    "ISO\n", timeString);
                            time = psTimeFromISO(timeString, timeSys);
                        } // Interpreting the time string
                    }
                    break;
                case PS_TYPE_F32: {
                        double timeValue = (double)timeItem->data.F32;
                        if (strcasecmp(timeFormat, "JD") == 0) {
                            time = psTimeFromJD(timeValue);
                        } else if (strcasecmp(timeFormat, "MJD") == 0) {
                            time = psTimeFromMJD(timeValue);
                        } else {
                            psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
                                    "JD\n", timeValue);
                            time = psTimeFromJD(timeValue);
                        }
                    }
                    break;
                case PS_TYPE_F64: {
                        double timeValue = (double)timeItem->data.F64;
                        if (strcasecmp(timeFormat, "JD") == 0) {
                            time = psTimeFromJD(timeValue);
                        } else if (strcasecmp(timeFormat, "MJD") == 0) {
                            time = psTimeFromMJD(timeValue);
                        } else {
                            psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
                                    "JD\n", timeValue);
                            time = psTimeFromJD(timeValue);
                        }
                    }
                    break;
                default:
                    psError(PS_ERR_IO, true, "Unable to parse CELL.TIME.\n");
                }
            } else {
                psError(PS_ERR_IO, false, "Unable to find CELL.TIME in FORMATS.\n");
            } // Getting the format
        } else {
            psError(PS_ERR_IO, false, "Unable to find FORMATS in camera configuration.\n");
        } // Getting the formats
    } // Getting CELL.TIME

    psMetadataItem *item = psMetadataItemAllocPtr("CELL.TIME", PS_DATA_TIME, "Time of exposure", time);
    psFree(time);
    return item;
}

// Correct a position --- in case the user wants FORTRAN indexing (like the FITS standard...)
static int fortranCorr(pmFPA *fpa,       // FPA, contains the camera configuration
                       const char *name // Name of concept to check for FORTRAN indexing
                      )
{
    bool mdok = false;                  // Result of MD lookup
    psMetadata *formats = psMetadataLookupMD(&mdok, fpa->camera, "FORMATS");
    if (mdok && formats) {
        psString format = psMetadataLookupStr(&mdok, formats, name);
        if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
            return -1;
        }
    }
    return 0;
}

psMetadataItem *pmConceptRead_CELL_X0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    int x0 = pmConceptReadS32(fpa, chip, cell, db, "CELL.X0");
    x0 += fortranCorr(fpa, "CELL.X0");
    return psMetadataItemAllocS32("CELL.X0", "Position of (0,0) on the chip", x0);
}


psMetadataItem *pmConceptRead_CELL_Y0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    int y0 = pmConceptReadS32(fpa, chip, cell, db, "CELL.Y0");
    y0 += fortranCorr(fpa, "CELL.X0");
    return psMetadataItemAllocS32("CELL.Y0", "Position of (0,0) on the chip", y0);
}


bool pmConceptWrite_CELL_TRIMSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    psMetadataItem *trimsecItem = psMetadataLookup(cell->concepts, "CELL.TRIMSEC");
    psRegion *trimsec = trimsecItem->data.V; // The trimsec region
    psString source = pmConceptReadString(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE"); // 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");
            return false;
        }
        return true;
    }

    if (strcasecmp(source, "HEADER") == 0) {
        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");
            return false;
        }
        psMetadataAddItem(header, trimsecItem, PS_LIST_TAIL, PS_META_REPLACE);
        return true;
    }

    psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE (%s) is not HEADER or VALUE.\n", source);
    return false;
}

bool pmConceptWrite_CELL_BIASSEC(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    psMetadataItem *biassecItem = psMetadataLookup(cell->concepts, "CELL.BIASSEC");
    psList *biassecs = biassecItem->data.V; // The biassecs region list

    if (biassecs->n == 0) {
        psMetadataItem *noneItem = psMetadataItemAllocStr("CELL.BIASSEC", "Bias section", "NONE");
        pmConceptWriteItem(fpa, chip, cell, db, noneItem);
        return true;
    }

    psString source = pmConceptReadString(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE"); // 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");
            psFree(checkList);
            return false;
        }

        // 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++;
            }
        }

        bool allMatch = true;           // Does everything match?
        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");
                allMatch = false;
            }
        }
        // Clean up
        psFree(checkListIter);
        psFree(checkList);
        psFree(biassecsIter);
        psFree(check);

        return allMatch;
    }

    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");
            psFree(keywords);
            return false;
        }

        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");
            psFree(keywords);
            return false;
        }

        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);

        return true;
    }

    psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE (%s) is not HEADER or VALUE.\n", source);
    return false;
}

// This function actually does both CELL.XBIN and CELL.YBIN, since if CELL.XBIN and CELL.YBIN are specified by
// the same header, we need to check to update them together.
bool pmConceptWrite_CELL_XBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    psMetadataItem *xBinItem = psMetadataLookup(cell->concepts, "CELL.XBIN"); // Binning factor in x
    psMetadataItem *yBinItem = psMetadataLookup(cell->concepts, "CELL.YBIN"); // Binning factor in y

    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) {
        pmConceptWriteToHeader(fpa, chip, cell, xBinItem);
        xBinItem = NULL;
    }
    if (strlen(yKeyword) > 0 && strcasecmp(xKeyword, yKeyword) != 0) {
        pmConceptWriteToHeader(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");
            return false;
        }
        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) {
        pmConceptWriteItem(fpa, chip, cell, db, xBinItem);
    }
    if (yBinItem) {
        pmConceptWriteItem(fpa, chip, cell, db, yBinItem);
    }

    return true;
}

// This is a dummy function, since CELL.YBIN is done by CELL.XBIN
bool pmConceptWrite_CELL_YBIN(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    return true;
}



bool pmConceptWrite_CELL_TIMESYS(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    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);
    bool success = pmConceptWriteItem(fpa, chip, cell, db, newItem);
    psFree(newItem);
    psFree(sys);

    return success;
}

bool pmConceptWrite_CELL_TIME(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    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);
        bool success = pmConceptWriteItem(fpa, chip, cell, db, newTimeItem);
        psFree(newTimeItem);
        psFree(dateTimeString);
        return success;
    }
    if (strcasecmp(format, "ISO") == 0) {
        // dateTimeString contains an ISO time
        psMetadataItem *newTimeItem = psMetadataItemAllocStr("CELL.TIME", "Time of observation",
                                      dateTimeString);
        bool success = pmConceptWriteItem(fpa, chip, cell, db, newTimeItem);
        psFree(newTimeItem);
        psFree(dateTimeString);
        return success;
    }
    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");
            psFree(dateTimeString);
            return false;
        }

        // 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");
            psFree(dateTimeString);
            // Should free other stuff, but this is work in progress
            return false;
        }
        if (strstr(format, "BACKWARDS")) {
            psError(PS_ERR_IO, true, "You want it BACKWARDS?  Not right now, thanks.\n");
            psFree(dateTimeString);
            // Should free other stuff, but this is work in progress
            return false;
        }

        bool success = true;
        psMetadataItem *dateItem = psMetadataItemAllocStr(dateKeyword, "Date of observation", dateString);
        psMetadataItem *timeItem = psMetadataItemAllocStr(timeKeyword, "Time of observation", timeString);
        success = 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);

        return success;
    }
    if (strcasecmp(format, "MJD") == 0) {
        double mjd = psTimeToMJD(time);
        psMetadataItem *newTimeItem = psMetadataItemAllocF64("CELL.TIME", "MJD of observation", mjd);
        bool success = pmConceptWriteItem(fpa, chip, cell, db, newTimeItem);
        psFree(newTimeItem);
        psFree(dateTimeString);
        return success;
    }
    if (strcasecmp(format, "JD") == 0) {
        double jd = psTimeToMJD(time);
        psMetadataItem *newTimeItem = psMetadataItemAllocF64("CELL.TIME", "JD of observation", jd);
        bool success = pmConceptWriteItem(fpa, chip, cell, db, newTimeItem);
        psFree(newTimeItem);
        psFree(dateTimeString);
        return success;
    }

    psError(PS_ERR_IO, true, "Not sure how to write concept CELL.TIME (%s)\n", dateTimeString);
    return false;
}

bool pmConceptWrite_CELL_X0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    psMetadataItem *x0item = psMetadataLookup(cell->concepts, "CELL.X0");
    psMetadataItem *newItem = psMetadataItemAllocS32("CELL.X0", "Position of (0,0) on the chip",
                              x0item->data.S32 - fortranCorr(fpa, "CELL.X0"));
    return pmConceptWriteItem(fpa, chip, cell, db, newItem);
}

bool pmConceptWrite_CELL_Y0(pmFPA *fpa, pmChip *chip, pmCell *cell, psDB *db)
{
    psMetadataItem *y0item = psMetadataLookup(cell->concepts, "CELL.Y0");
    psMetadataItem *newItem = psMetadataItemAllocS32("CELL.Y0", "Position of (0,0) on the chip",
                              y0item->data.S32 - fortranCorr(fpa, "CELL.Y0"));
    return pmConceptWriteItem(fpa, chip, cell, db, newItem);
}

