#include <stdio.h>
#include <assert.h>
#include <string.h>
#include "pslib.h"
#include "psMetadataItemParse.h"

#include "pmFPA.h"
#include "pmConcepts.h"
#include "pmFPAConstruct.h"
#include "pmFPAview.h"
#include "pmFPAUtils.h"

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

// Read data for a particular cell from the camera format description
static psMetadata *getCellData(const psMetadata *format, // The camera format description
                               const char *cellName // The name of the cell
                              )
{
    bool status = true;                 // Result of MD lookup
    psMetadata *cells = psMetadataLookupMD(&status, format, "CELLS"); // The CELLS
    if (! status) {
        psError(PS_ERR_IO, false, "Unable to determine CELLS of camera.\n");
        return NULL;
    }

    psMetadata *cellData = psMetadataLookupMD(&status, cells, cellName); // The data for the particular cell
    if (!status || !cellData) {
        psLogMsg(__func__, PS_LOG_WARN, "Unable to find specs for cell %s: ignored\n", cellName);
    }

    return cellData;
}

// Parse a list of first:second:third pairs in a string
static bool parseContent(psArray **first, // Array of the first values
                         psArray **second, // Array of the second values
                         psArray **third, // Array of the third values
                         const char *string // The string to parse
                        )
{
    bool allOK = true;                  // Everything was OK?
    psList *values = psStringSplit(string, " ,;"); // List of the parts
    *first = psArrayAlloc(values->n);
    *second = psArrayAlloc(values->n);
    *third = psArrayAlloc(values->n);
    int num = 0;
    psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false); // Iterator for values
    psString value = NULL;               // "first:second:third" string
    while ((value = psListGetAndIncrement(valuesIter))) {
        psList *firstSecondThird = psStringSplit(value, ":"); // List containing the first, second, third
        psArray *fst = psListToArray(firstSecondThird); // An array representation
        psFree(firstSecondThird);
        psString firstPart = NULL;      // The first part
        psString secondPart = NULL;     // The second part
        psString thirdPart = NULL;      // The third part
        switch (firstSecondThird->n) {
        case 1:
            thirdPart = fst->data[0];
            break;
        case 2:
            secondPart = fst->data[0];
            thirdPart = fst->data[1];
            break;
        case 3:
            firstPart = fst->data[0];
            secondPart = fst->data[1];
            thirdPart = fst->data[2];
            break;
        default:
            psLogMsg(__func__, PS_LOG_WARN, "Badly formated specifier: %s --- ignored.\n", value);
            allOK = false;
            psFree(fst);
            continue;
        }
        psArraySet(*first, num, firstPart);
        psArraySet(*second, num, secondPart);
        psArraySet(*third, num, thirdPart);
        num++;
        psFree(fst);
    }
    psFree(valuesIter);
    psFree(values);

    return allOK;
}

// Get the name of a PHU chip or cell from the header
static psString phuNameFromHeader(const char *name, // The name to lookup: "CELL.NAME" or "CHIP.NAME"
                                  const psMetadata *fileInfo, // FILE within the camera format description
                                  const psMetadata *header // Primary header
                                 )
{
    bool mdok = true;                   // Result of MD lookup
    psString keyword = psMetadataLookupStr(&mdok, fileInfo, name);
    if (!mdok || strlen(keyword) == 0) {
        return false;
    }
    psMetadataItem *resultItem = psMetadataLookup(header, keyword);
    if (! resultItem) {
        psError(PS_ERR_IO, false, "Unable to find %s in primary header to identify %s.\n", keyword, name);
        return NULL;
    }
    return psMetadataItemParseString(resultItem);
}

// Add an HDU to the FPA
static bool addHDUtoFPA(pmFPA *fpa,     // FPA to which to add
                        pmHDU *hdu      // HDU to be added
                       )
{
    assert(fpa);
    assert(hdu);

    if (fpa->hdu) {
        // Something's already here
        if (fpa->hdu != hdu) {
            psError(PS_ERR_IO, true, "Unable to add HDU since FPA already has one.\n");
        }
        return false;
    }
    fpa->hdu = psMemIncrRefCounter(hdu);
    if (hdu->header) {
        pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, NULL);
    }
    pmFPASetFileStatus(fpa, true);

    return true;
}

// Add an HDU to the chip
static bool addHDUtoChip(pmChip *chip,  // Chip to which to add
                         pmHDU *hdu     // HDU to be added
                        )
{
    assert(chip);
    assert(hdu);

    if (chip->hdu) {
        // Something's already here
        if (chip->hdu != hdu) {
            psError(PS_ERR_IO, true, "Unable to add HDU since chip already has one.\n");
        }
        return false;
    }
    chip->hdu = psMemIncrRefCounter(hdu);
    if (hdu->header) {
        pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, NULL);
    }
    pmChipSetFileStatus(chip, true);

    return true;
}

// Add an HDU to the cell
static bool addHDUtoCell(pmCell *cell,  // Cell to which to add
                         pmHDU *hdu     // HDU to be added
                        )
{
    assert(cell);
    assert(hdu);

    if (cell->hdu) {
        // Something's already here
        if (cell->hdu != hdu) {
            psError(PS_ERR_IO, true, "Unable to add HDU since cell already has one.\n");
        }
        return false;
    }
    cell->hdu = psMemIncrRefCounter(hdu);
    if (hdu->header) {
        pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, true, NULL);
    }
    pmCellSetFileStatus(cell, true);

    return true;
}


// Looks up the particular content based on the header
static const char *getContent(psMetadata *fileInfo, // The FILE from the camera format configuration
                              psMetadata *contents, // The CONTENTS from the camera format configuration
                              psMetadata *header // The (primary) header
                             )
{
    bool mdok = true;                   // Status of MD lookup
    const char *contentHeaders = psMetadataLookupStr(&mdok, fileInfo, "CONTENT"); // Headers for content
    if (!mdok || !contentHeaders || strlen(contentHeaders) == 0) {
        psError(PS_ERR_IO, true, "Unable to find CONTENT in FILE within camera format configuration.\n");
        return NULL;
    }

    psList *keywords = psStringSplit(contentHeaders, " ,;"); // List of keywords
    psListIterator *keywordsIter = psListIteratorAlloc(keywords, PS_LIST_HEAD, false); // Iterator
    psString keyword = NULL;        // Keyword, from iteration
    psString contentsKey = NULL;    // Key to the CONTENTS menu
    bool first = true;              // Is it the first value?
    while ((keyword = psListGetAndIncrement(keywordsIter))) {
        const char *value = psMetadataLookupStr(&mdok, header, keyword);
        if (first) {
            psStringAppend(&contentsKey, "%s", value);
            first = false;
        } else {
            psStringAppend(&contentsKey, "_%s", value);
        }
    }
    psFree(keywordsIter);
    psFree(keywords);
    psTrace(__func__, 5, "Looking up %s in the CONTENTS.\n", contentsKey);
    const char *content = psMetadataLookupStr(&mdok, contents, contentsKey);
    if (!mdok || !content || strlen(content) == 0) {
        psError(PS_ERR_IO, true, "Unable to find %s in the CONTENTS.\n", contentsKey);
        return NULL;
    }
    psFree(contentsKey);

    return content;
}


// Given a (string) list of contents "chip:cell:type chip:cell:type", put the HDU in the correct place and
// plug in the cell configuration information
static int processContents(pmFPA *fpa,  // The FPA
                           pmChip *chip, // The chip, or NULL
                           pmCell *cell, // The cell, or NULL
                           pmHDU *hdu,  // The HDU to be added
                           pmFPALevel level, // The level at which to add the HDU
                           const char *contents, // The contents line, consisting of a list of chip:cell
                           psMetadata *format // Camera format configuration
                          )
{
    assert(fpa);
    assert(contents && strlen(contents) > 0);
    assert(!cell || (cell && chip));    // Need both chip and cell if given a cell

    if (hdu && level == PM_FPA_LEVEL_FPA) {
        addHDUtoFPA(fpa, hdu);
    }

    // Parse the list of chip:cell:type
    psArray *chips = NULL;              // The chips (first bits)
    psArray *cells = NULL;              // The cells (second bits)
    psArray *types = NULL;              // The cell types (third bits)
    int numCells = 0;                   // Number of cells processed
    parseContent(&chips, &cells, &types, contents);
    for (int i = 0; i < types->n; i++) {
        psString chipName = chips->data[i]; // The name of the chip
        psString cellName = cells->data[i]; // The name of the cell
        psString cellType = types->data[i]; // The type of the cell

        // Get the chip
        pmChip *newChip = NULL;         // The chip specified
        if (chip) {
            newChip = chip;
        } else if (chipName) {
            // Find the chip
            int chipNum = pmFPAFindChip(fpa, chipName); // The chip we're looking for
            if (chipNum == -1) {
                psLogMsg(__func__, PS_LOG_WARN, "Unable to find chip %s in fpa --- ignored.\n", chipName);
                continue;
            }
            newChip = fpa->chips->data[chipNum];
        }

        if (!newChip) {
            psLogMsg(__func__, PS_LOG_WARN, "Unable to determine chip for entry %d of content (follows) --- "
                     "ignored.\n\t%s", i, contents);
            continue;
        }

        // Get the cell
        pmCell *newCell = NULL;         // The cell specified
        if (cell) {
            newCell = cell;
        } else if (cellName) {
            // Find the cell
            int cellNum = pmChipFindCell(newChip, cellName); // The cell we're looking for
            if (cellNum == -1) {
                psLogMsg(__func__, PS_LOG_WARN, "Unable to find cell %s in chip %s --- ignored.\n",
                         cellName, chipName);
                continue;
            }
            newCell = newChip->cells->data[cellNum];
        }
        if (!newCell) {
            psLogMsg(__func__, PS_LOG_WARN, "Unable to determine cell for entry %d of content (follows) --- "
                     "ignored.\n\t%s", i, contents);
            continue;
        }

        // Get the type
        if (!cellType) {
            psLogMsg(__func__, PS_LOG_WARN, "Unable to determine cell type for entry %d of content (follows) "
                     "--- ignored.\n\t%s", i, contents);
            continue;
        }
        psMetadata *cellData = getCellData(format, cellType); // Data for this cell

        // Put in the HDU
        if (hdu && level == PM_FPA_LEVEL_CHIP) {
            addHDUtoChip(newChip, hdu);
        }
        if (hdu && level == PM_FPA_LEVEL_CELL) {
            addHDUtoCell(newCell, hdu);
        }

        // Put in the cell data
        if (newCell->config) {
            psLogMsg(__func__, PS_LOG_WARN, "Overwriting cell data in chip %s, cell %s\n", chipName,
                     cellName);
            psFree(newCell->config); // Make way!
        }
        newCell->config = psMemIncrRefCounter(cellData);
        pmConceptsReadCell(newCell, PM_CONCEPT_SOURCE_CAMERA | PM_CONCEPT_SOURCE_DEFAULTS, false, NULL);
        numCells++;
    }
    psFree(chips);
    psFree(cells);
    psFree(types);

    return numCells;
}

static pmFPALevel hduLevel(psMetadata *format // The camera format configuration
                          )
{
    bool mdok = true;                   // Status of MD lookup
    psMetadata *file = psMetadataLookupMD(&mdok, format, "FILE"); // File information
    if (!mdok || !file) {
        psError(PS_ERR_IO, true, "Unable to find FILE information in camera format configuration.\n");
        return PM_FPA_LEVEL_NONE;
    }
    const char *extType = psMetadataLookupStr(&mdok, file, "EXTENSIONS");
    if (!mdok || !extType || strlen(extType) == 0) {
        psError(PS_ERR_IO, true, "Unable to find EXTENSIONS in the FILE information in the camera format"
                " configuration.\n");
        return PM_FPA_LEVEL_NONE;
    }

    // Where do we stick in the HDUs?
    pmFPALevel level = PM_FPA_LEVEL_NONE; // Level for HDU insertion
    if (strcasecmp(extType, "CHIP") == 0) {
        level = PM_FPA_LEVEL_CHIP;
    } else if (strcasecmp(extType, "CELL") == 0) {
        level = PM_FPA_LEVEL_CELL;
    } else if (strcasecmp(extType, "NONE") != 0) {
        psError(PS_ERR_IO, true, "EXTENSIONS is not CHIP or CELL or NONE.\n");
    }

    return level;
}

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

// Construct an FPA instance on the basis of a camera configuration
pmFPA *pmFPAConstruct(const psMetadata *camera // The camera configuration
                     )
{
    pmFPA *fpa = pmFPAAlloc(camera);    // The FPA to fill out

    bool mdok = true;                   // Status from MD lookups
    psMetadata *components = psMetadataLookupMD(&mdok, camera, "FPA"); // FPA components
    psMetadataIterator *componentsIter = psMetadataIteratorAlloc(components, PS_LIST_HEAD, NULL);
    psMetadataItem *componentsItem = NULL; // Item from components
    while ((componentsItem = psMetadataGetAndIncrement(componentsIter))) {
        const char *chipName = componentsItem->name; // Name of the chip
        if (componentsItem->type != PS_DATA_STRING) {
            psLogMsg(__func__, PS_LOG_WARN, "Element %s in FPA within the camera configuration is not of "
                     "type STR (type=%x) --- ignored.\n", chipName, componentsItem->type);
            continue;
        }
        pmChip *chip = pmChipAlloc(fpa, chipName); // The chip
        psList *cellNames = psStringSplit(componentsItem->data.V, " ,;"); // List of cell names
        psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false); // Iterator
        psString cellName = NULL;       // Name of cell
        while ((cellName = psListGetAndIncrement(cellNamesIter))) {
            pmCell *cell = pmCellAlloc(chip, cellName); // New cell
            psFree(cell);               // Drop reference
        }
        psFree(chip);                   // Drop reference
        psFree(cellNamesIter);
        psFree(cellNames);
    }
    psFree(componentsIter);

    return fpa;
}

bool pmFPAAddSourceFromView(pmFPA *fpa,   // The FPA
                            const pmFPAview *phuView, // The view, corresponding to the PHU
                            psMetadata *format // Format of file
                           )
{
    assert(fpa);
    assert(phuView);
    assert(format);

    // Where does the PHU go?
    bool mdok = true;                   // Status of MD lookup
    psMetadata *fileInfo = psMetadataLookupMD(&mdok, format, "FILE"); // File information from the format
    if (!mdok || !fileInfo) {
        psError(PS_ERR_IO, false, "Unable to find FILE information in the camera format configuration.\n");
        return false;
    }
    const char *phuType = psMetadataLookupStr(&mdok, fileInfo, "PHU"); // What is the PHU?
    if (!mdok || strlen(phuType) == 0) {
        psError(PS_ERR_IO, false, "Unable to find PHU in the FILE information of the camera format.\n");
        return false;
    }

    // Generate the PHU
    pmHDU *phdu = pmHDUAlloc("PHU");    // The primary header data unit
    phdu->format = psMemIncrRefCounter(format);

    // Put in the PHU
    pmChip *chip = NULL;                // The chip that corresponds to the PHU
    pmCell *cell = NULL;                // The cell that corresponds to the PHU
    if (strcasecmp(phuType, "FPA") == 0) {
        addHDUtoFPA(fpa, phdu);
        psFree(phdu);
    } else {
        psArray *chips = fpa->chips;    // Array of chips
        if (phuView->chip < 0 || phuView->chip > chips->n) {
            psError(PS_ERR_IO, true, "PHU specified by the camera format requires specification of a "
                    "particular chip, which cannot be determined from the view (%d).\n", phuView->chip);
            psFree(phdu);
            return false;
        }
        chip = chips->data[phuView->chip];
        if (strcasecmp(phuType, "CHIP") == 0) {
            addHDUtoChip(chip, phdu);
            psFree(phdu);
        } else if (strcasecmp(phuType, "CELL") == 0) {
            psArray *cells = chip->cells; // Array of cells
            if (phuView->cell < 0 || phuView->cell > cells->n) {
                psError(PS_ERR_IO, true, "PHU specified by the camera format requires specification of a "
                        "particular cell, which cannot be determined from the view (%d).\n", phuView->cell);
                psFree(phdu);
                return false;
            }
            addHDUtoCell(cell, phdu);
            psFree(phdu);
        } else {
            psError(PS_ERR_IO, true, "PHU in the camera configuration format is not FPA, CHIP or CELL.\n");
            psFree(phdu);
            return false;
        }
    }

    // Put in the extensions
    pmFPALevel level = hduLevel(format);// The level for the HDUs to go
    if (level == PM_FPA_LEVEL_NONE) {
        // No extensions --- we're done
        return true;
    }
    psMetadata *contents = psMetadataLookupMD(&mdok, format, "CONTENTS"); // The contents of the FITS file
    if (!mdok || !contents) {
        psError(PS_ERR_IO, false, "Unable to find CONTENTS in the camera format configuration.\n");
        return false;
    }

    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
    psMetadataItem *contentsItem = NULL;// Item from the contents iteration
    while ((contentsItem = psMetadataGetAndIncrement(contentsIter))) {
        if (contentsItem->type != PS_DATA_STRING) {
            psLogMsg(__func__, PS_LOG_WARN, "Content item %s is not of type STR --- ignored.\n",
                     contentsItem->name);
            continue;
        }
        pmHDU *hdu = pmHDUAlloc(contentsItem->name); // HDU to add
        hdu->format = psMemIncrRefCounter(format);
        const char *content = contentsItem->data.V; // The content data
        processContents(fpa, chip, cell, hdu, level, content, format);
        psFree(hdu);
    }
    psFree(contentsIter);

    return true;
}



// Add an input file to the FPA
pmFPAview *pmFPAAddSourceFromHeader(pmFPA *fpa, // The FPA
                                    psMetadata *phu, // Primary header of file
                                    psMetadata *format // Format of file
                                   )
{
    assert(fpa);
    assert(phu);
    assert(format);

    bool mdok = true;                   // Status from metadata lookups
    psMetadata *fileInfo = psMetadataLookupMD(&mdok, format, "FILE"); // The file information
    if (!mdok || !fileInfo) {
        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
        return NULL;
    }

    // Check the name of the FPA
    psString newFPAname = phuNameFromHeader("FPA.NAME", fileInfo, phu); // New name for the FPA
    const char *currentFPAname = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.NAME"); // Current name
    if (mdok && currentFPAname && strlen(currentFPAname) > 0 && strcmp(currentFPAname, newFPAname) != 0) {
        psLogMsg(__func__, PS_LOG_WARN, "FPA.NAME for new source (%s) doesn't match FPA.NAME for current "
                 "fpa (%s).\n", newFPAname, currentFPAname);
    }
    psMetadataAddStr(fpa->concepts, PS_LIST_HEAD, "FPA.NAME", PS_META_REPLACE, "Name of FPA", newFPAname);
    psFree(newFPAname);                 // Drop reference

    // Where does the PHU go?
    const char *phuType = psMetadataLookupStr(&mdok, fileInfo, "PHU"); // What is the PHU?
    if (!mdok || strlen(phuType) == 0) {
        psError(PS_ERR_IO, false, "Unable to find PHU in the format specification.\n");
        return NULL;
    }
    pmHDU *phdu = pmHDUAlloc("PHU");    // The primary header data unit
    phdu->header = psMemIncrRefCounter(phu);
    phdu->format = psMemIncrRefCounter(format);
    // Generate the view
    pmFPAview *view = pmFPAviewAlloc(0); // The FPA view corresponding to the PHU, to be returned
    view->chip = -1;
    view->cell = -1;
    view->readout = -1;

    // And what are the individual extensions?
    const char *extType = psMetadataLookupStr(&mdok, fileInfo, "EXTENSIONS"); // What's in the extns?
    if (!mdok || strlen(extType) == 0) {
        psError(PS_ERR_IO, false, "Unable to find EXTENSIONS in the format specification.\n");
        psFree(view);
        return NULL;
    }


    // This is a special case: PHU=FPA and EXTENSIONS=NONE.  The only reason to do this is in the case of
    // single CCD imagers, where the entire FPA is in the PHU image.  In this case, the CONTENTS is of type
    // STR (rather than METADATA), and has the usual chip:cell.
    if (strcasecmp(phuType, "FPA") == 0 && strcasecmp(extType, "NONE") == 0) {
        const char *contents = psMetadataLookupStr(&mdok, format, "CONTENTS"); // The contents of the file
        if (!mdok || !contents || strlen(contents) == 0) {
            psError(PS_ERR_IO, true, "Unable to find CONTENTS in the camera format configuration.\n");
            psFree(view);
            return NULL;
        }

        processContents(fpa, NULL, NULL, phdu, PM_FPA_LEVEL_FPA, contents, format);
        psFree(phdu);
        return view;
    }

    // In all other cases, the CONTENTS is of type METADATA, and is either a menu if EXTENSIONS=NONE, or
    // a list of extensions otherwise.
    psMetadata *contents = psMetadataLookupMD(&mdok, format, "CONTENTS"); // The contents of the FITS file
    if (!mdok || !contents) {
        psError(PS_ERR_IO, false, "Unable to find CONTENTS in the camera format configuration.\n");
        psFree(view);
        return NULL;
    }

    // No extensions --- only have the PHU.  In this case, the CONTENTS is a menu, with CONTENT indicating
    // header keywords that provides a key to the CONTENTS menu.
    if (strcasecmp(extType, "NONE") == 0) {
        // We have already dealt with the case PHU=FPA, in a special case, above.
        const char *content = getContent(fileInfo, contents, phu); // The content: string of chip:cell pairs

        // Need to look up what chip we have.
        psString chipName = phuNameFromHeader("CHIP.NAME", fileInfo, phu);
        psTrace(__func__, 5, "This is chip %s\n", chipName);
        int chipNum = pmFPAFindChip(fpa, chipName); // Chip number
        if (chipNum == -1) {
            psError(PS_ERR_IO, true, "Unable to find chip %s in FPA.\n", chipName);
            psFree(view);
            return NULL;
        }
        pmChip *chip = fpa->chips->data[chipNum]; // Chip of interest
        view->chip = chipNum;

        pmCell *cell = NULL;            // Cell of interest

        pmFPALevel level = PM_FPA_LEVEL_NONE; // Level for HDU to be added
        if (strcasecmp(phuType, "CHIP") == 0) {
            level = PM_FPA_LEVEL_CHIP;
        } else if (strcasecmp(phuType, "CELL") == 0) {
            level = PM_FPA_LEVEL_CELL;
            // Need to look up what cell we have.
            psString cellName = phuNameFromHeader("CELL.NAME", fileInfo, phu);
            int cellNum = pmChipFindCell(chip, cellName); // Cell number
            if (cellNum == -1) {
                psError(PS_ERR_IO, true, "Unable to find cell %s in chip %s.\n", cellName, chipName);
                psFree(view);
                return NULL;
            }
            cell = chip->cells->data[cellNum];
            view->cell = cellNum;
            psFree(cellName);
        } else {
            psError(PS_ERR_IO, true, "PHU is not FPA, CHIP or CELL.\n");
            psFree(view);
            return NULL;
        }
        psFree(chipName);

        processContents(fpa, chip, cell, phdu, level, content, format);
        psFree(phdu);
        return view;
    }


    // From here on, we have extensions that we iterate through.  The CONTENTS is a list of extensions.
    pmChip *chip = NULL;                // The chip of interest
    pmCell *cell = NULL;                // The cell of interest

    // First, put in the PHU
    if (strcasecmp(phuType, "FPA") == 0) {
        addHDUtoFPA(fpa, phdu);
    } else {
        // Get the chip
        psString chipName = phuNameFromHeader("CHIP.NAME", fileInfo, phu); // Name of the chip
        int chipNum = pmFPAFindChip(fpa, chipName); // Chip number
        if (chipNum == -1) {
            psError(PS_ERR_IO, true, "Unable to find chip %s in FPA.\n", chipName);
            psFree(view);
            return NULL;
        }
        chip = fpa->chips->data[chipNum]; // The specified chip
        view->chip = chipNum;

        if (strcasecmp(phuType, "CHIP") == 0) {
            addHDUtoChip(chip, phdu);
        } else if (strcasecmp(phuType, "CELL") == 0) {
            psString cellName = phuNameFromHeader("CELL.NAME", fileInfo, phu); // Name of the cell
            int cellNum = pmChipFindCell(chip, cellName); // Cell number
            if (cellNum == -1) {
                psError(PS_ERR_IO, true, "Unable to find cell %s in chip %s.\n", cellName, chipName);
                psFree(view);
                return NULL;
            }
            cell = chip->cells->data[cellNum]; // The specified cell
            view->cell = cellNum;
            psFree(cellName);

            addHDUtoCell(cell, phdu);
        } else {
            psError(PS_ERR_IO, true, "The format of the PHU (%s) is not FPA, CHIP or CELL.\n", phuType);
            psFree(view);
            return NULL;
        }
        psFree(chipName);
    }
    psFree(phdu);

    pmFPALevel level = hduLevel(format);// The level at which to plug in HDU

    // Now go through the contents
    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
    psMetadataItem *contentsItem = NULL; // Item from contents
    while ((contentsItem = psMetadataGetAndIncrement(contentsIter))) {
        const char *extName = contentsItem->name;
        if (contentsItem->type != PS_DATA_STRING) {
            psLogMsg(__func__, PS_LOG_WARN, "CONTENTS item %s is not of type STR --- ignored.\n", extName);
            continue;
        }

        pmHDU *hdu = pmHDUAlloc(extName); // The extension
        hdu->format = psMemIncrRefCounter(format);

        processContents(fpa, chip, cell, hdu, level, contentsItem->data.V, format);
        psFree(hdu);
    }
    psFree(contentsIter);

    pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS, NULL);

    return view;
}


// Print out the focal plane structure
void pmFPAPrint(pmFPA *fpa,             // FPA to print
                bool header,            // Print headers?
                bool concepts           // Print concepts?
               )
{
    psTrace(__func__, 1, "FPA:\n");
    if (fpa->hdu) {
        psTrace(__func__, 2, "---> FPA is extension %s.\n", fpa->hdu->extname);
        if (! fpa->hdu->images) {
            psTrace(__func__, 2, "---> NO PIXELS read in for extension %s\n", fpa->hdu->extname);
        }
        if (header) {
            if (fpa->hdu->header) {
                psTrace(__func__, 2, "---> Header:\n");
                psMetadataPrint(fpa->hdu->header, 8);
            } else {
                psTrace(__func__, 2, "---> NO HEADER read in for extension %s\n", fpa->hdu->extname);
            }
        }
    }
    if (concepts) {
        psMetadataPrint(fpa->concepts, 2);
    }

    psArray *chips = fpa->chips;        // Array of chips
    // Iterate over the FPA
    for (int i = 0; i < chips->n; i++) {
        psTrace(__func__, 3, "Chip: %d\n", i);
        pmChip *chip = chips->data[i]; // The chip
        if (chip->hdu) {
            psTrace(__func__, 4, "---> Chip is extension %s.\n", chip->hdu->extname);
            if (header) {
                if (chip->hdu->header) {
                    psTrace(__func__, 4, "---> Header:\n");
                    psMetadataPrint(chip->hdu->header, 8);
                } else {
                    psTrace(__func__, 4, "---> NO HEADER read in for extension %s\n", chip->hdu->extname);
                }
            }
            if (! chip->hdu->images) {
                psTrace(__func__, 4, "---> NO PIXELS read in for extension %s\n", chip->hdu->extname);
            }
        }
        if (concepts) {
            psMetadataPrint(chip->concepts, 4);
        }

        // Iterate over the chip
        psArray *cells = chip->cells;   // Array of cells
        for (int j = 0; j < cells->n; j++) {
            psTrace(__func__, 5, "Cell: %d\n", j);
            pmCell *cell = cells->data[j]; // The cell
            if (cell->hdu) {
                psTrace(__func__, 6, "---> Cell is extension %s.\n", cell->hdu->extname);
                if (header) {
                    if (cell->hdu->header) {
                        psTrace(__func__, 6, "---> Header:\n");
                        psMetadataPrint(cell->hdu->header, 8);
                    } else {
                        psTrace(__func__, 6, "---> NO HEADER read in for extension %s\n", cell->hdu->extname);
                    }
                }
                if (! cell->hdu->images) {
                    psTrace(__func__, 6, "---> NO PIXELS read in for extension %s\n", cell->hdu->extname);
                }
            }
            if (concepts) {
                psMetadataPrint(cell->concepts, 6);
            }

            psTrace(__func__, 7, "Readouts:\n");
            psArray *readouts = cell->readouts; // Array of readouts
            for (int k = 0; k < readouts->n; k++) {
                pmReadout *readout = readouts->data[k]; // The readout
                psImage *image = readout->image; // The image
                psList *bias = readout->bias; // The list of bias images
                if (image) {
                    psTrace(__func__, 8, "Image: [%d:%d,%d:%d] (%dx%d)\n", image->col0, image->col0 +
                            image->numCols, image->row0, image->row0 + image->numRows, image->numCols,
                            image->numRows);
                }
                if (bias) {
                    psListIterator *biasIter = psListIteratorAlloc(bias, PS_LIST_HEAD, false); // Iterator
                    psImage *biasImage = NULL; // Bias image from iteration
                    while ((biasImage = psListGetAndIncrement(biasIter))) {
                        psTrace(__func__, 8, "Bias:  [%d:%d,%d:%d] (%dx%d)\n", biasImage->col0,
                                biasImage->col0 + biasImage->numCols, biasImage->row0,
                                biasImage->row0 + biasImage->numRows, biasImage->numCols, biasImage->numRows);
                    }
                    psFree(biasIter);
                }
            } // Iterating over cell
        } // Iterating over chip
    } // Iterating over FPA

}
