#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <strings.h>		/* for strn?casecmp */
#include <pslib.h>

#include "pmHDU.h"
#include "pmFPA.h"
#include "pmFPALevel.h"
#include "pmConcepts.h"
#include "pmConfigCamera.h"

#define TABLE_OF_CONTENTS "CONTENTS"    // Name for camera format metadata containing the contents
#define CHIP_TYPES "CHIPS"              // Name for camera format metadata containing the chip types
#define CELL_TYPES "CELLS"              // Name for camera format metadata containing the cell types

// local helper functions defined below
static void removeCellConceptsSources(psMetadata *source);
static void removeChipConceptsSources(psMetadata *source);

// Generate the Chip and FPA mosaicked version of a named camera configuration
bool pmConfigCameraMosaickedVersions(psMetadata *site, // The site configuration
                                     const char *name // Name of the un-mosaicked camera
                                    )
{
    PS_ASSERT_METADATA_NON_NULL(site, false);
    PS_ASSERT_STRING_NON_EMPTY(name, false);

    bool mdok;                          // Status of MD lookup
    psMetadata *cameras = psMetadataLookupMetadata(&mdok, site, "CAMERAS"); // List of cameras
    if (!mdok || !cameras) {
        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the site configuration.\n");
        return false;
    }
    if (!pmConfigGenerateMosaickedVersion(cameras, cameras, name, PM_FPA_LEVEL_CHIP)) {
        psError(PS_ERR_UNKNOWN, true, "Failed to build Chip mosaic camera description for %s\n", name);
        return false;
    }
    if (!pmConfigGenerateMosaickedVersion(cameras, cameras, name, PM_FPA_LEVEL_FPA)) {
        psError(PS_ERR_UNKNOWN, true, "Failed to build FPA mosaic camera description for %s\n", name);
        return false;
    }
    return true;
}

// the operation putting the new entries first is now implemented in pmConfigGenerateMosaickedVersion
// Generate the Chip and FPA mosaicked version of a named camera configuration
bool pmConfigCameraMosaickedVersionsAll(psMetadata *site)
{
    PS_ASSERT_METADATA_NON_NULL(site, false);

    bool mdok;                          // Status of MD lookup
    psMetadata *cameras = psMetadataLookupMetadata(&mdok, site, "CAMERAS"); // List of cameras
    if (!mdok || !cameras) {
        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find CAMERAS in the site configuration.\n");
        return false;
    }

    psMetadataIterator *camerasIter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL); // Iterator
    psMetadataItem *camerasItem = NULL; // Item from iteration
    psMetadata *new = psMetadataAlloc();// New cameras to add
    while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
        assert(camerasItem->type == PS_DATA_METADATA); // Only metadata are allowed here!
        if (!pmConfigGenerateMosaickedVersion(cameras, new, camerasItem->name, PM_FPA_LEVEL_CHIP)) {
	    psError(PS_ERR_UNKNOWN, true, "Failed to build Chip mosaic camera description for %s\n", camerasItem->name);
	    return false;
	}
        if (!pmConfigGenerateMosaickedVersion(cameras, new, camerasItem->name, PM_FPA_LEVEL_FPA)) {
	    psError(PS_ERR_UNKNOWN, true, "Failed to build FPA mosaic camera description for %s\n", camerasItem->name);
	    return false;
	}
    }
    psFree(camerasIter);

    // Now put the new cameras at the top of the list of cameras, so they get recognised first
    // Note: going from the top, and putting everything to the top as we get there, so that the last one on
    // goes to the top.  This preserves the original order of the cameras, putting the mosaicked versions
    // before the originals.
    camerasIter = psMetadataIteratorAlloc(new, PS_LIST_HEAD, NULL); // Iterator
    while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
        psMetadataAddItem(cameras, camerasItem, PS_LIST_HEAD, 0);
    }
    psFree(camerasIter);
    psFree(new);

    return true;
}

// Generate a mosaicked version of a camera configuration
bool pmConfigGenerateMosaickedVersion(psMetadata *oldCameras, // Old list of camera configurations
				      psMetadata *newCameras, // New list of camera configurations
				      const char *name, // Name of original camera configuration
				      pmFPALevel mosaicLevel // Level to which we are mosaicking
    )
{
    assert(oldCameras);
    assert(newCameras);
    assert(name);
    assert(mosaicLevel == PM_FPA_LEVEL_CHIP || mosaicLevel == PM_FPA_LEVEL_FPA);

    // See if the old one is there
    psMetadata *camera = psMetadataLookupMetadata(NULL, oldCameras, name); // The camera configuration
    if (!camera) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Can't find camera to be mosaicked in camera list.");
        return false;
    }

    // See if the new one is already there
    psString newName = NULL;       // Name of mosaicked camera
    psStringAppend(&newName, "_%s-%s", name, mosaicLevel == PM_FPA_LEVEL_CHIP ? "CHIP" : "FPA");
    if (psMetadataLookup(oldCameras, newName)) {
        return true;
    }

    psMetadata *new = psMetadataCopy(NULL, camera); // Copy of the camera description
    bool mdok;                          // Status of MD lookups

    // ** Fix up the contents of the FPA description to match the mosaicked camera **
    // select the FPA description
    psMetadata *fpa = psMetadataLookupMetadata(NULL, new, "FPA"); // FPA in the camera configuration
    if (!fpa) {
        psError(PS_ERR_UNEXPECTED_NULL, false, "Can't find FPA within camera configuration.");
        psFree(new);
        return false;
    }
    switch (mosaicLevel) {
        // For CHIP mosaic, replace the contents of each chip with a single cell
      case PM_FPA_LEVEL_CHIP: {
          psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iterator
          psMetadataItem *fpaItem = NULL;     // Item from iteration
          while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
              if (fpaItem->type != PS_DATA_STRING) {
                  psError(PS_ERR_UNKNOWN, true, "Element %s within FPA in camera configuration is not of type STR.",
                            fpaItem->name);
                  psFree(new);
                  return false;
              }

              psFree(fpaItem->data.str);
              fpaItem->data.str = psStringCopy("MosaickedCell");
              psFree(fpaItem->comment);
              fpaItem->comment = psStringCopy("Mosaicked cell; automatically generated");
          }
          psFree(fpaIter);
          break;
      }
        // For FPA mosaic, replace the contents of the FPA with a single chip containing a single cell
      case PM_FPA_LEVEL_FPA: {
          while (psListLength(fpa->list) > 0) {
              psMetadataRemoveIndex(fpa, PS_LIST_TAIL);
          }

          psMetadataAddStr(fpa, PS_LIST_HEAD, "MosaickedChip", 0,
                           "Mosaicked chip with mosaicked cell; automatically generated",
                           "MosaickedCell");
          break;
      }
    default:
        psAbort("Should never get here.\n");
    }

    // ** Update the camera formats : add a new (mosaicked) format for each existing camera format **
    // select the list of all camera formats
    psMetadata *formats = psMetadataLookupMetadata(NULL, new, "FORMATS"); // FORMATS in the configuration
    assert(formats);            // It had better be there --- we've already read them in
    // loop over each of the formats
    psMetadataIterator *formatsIter = psMetadataIteratorAlloc(formats, PS_LIST_HEAD, NULL); // Iterator
    psMetadataItem *formatsItem = NULL; // Item from iteration
    while ((formatsItem = psMetadataGetAndIncrement(formatsIter))) {
        assert(formatsItem->type == PS_DATA_METADATA); // We should have read it by now!
        psMetadata *format = formatsItem->data.V; // The camera format

        // Add a new RULE which uniquely describes the mosaicked format.  this is needed so
        // that when a mosaic is written to a FITS file, it can be recognised again when read.
        psMetadata *rule = psMetadataLookupMetadata(NULL, format, "RULE"); // Way to identify format from PHU
        if (!rule) {
            // a camera format without a rule is not allowed.
            psError(PS_ERR_UNKNOWN, false, "Camera format %s has no RULE", formatsItem->name);
            return false;
        }

        // the new rule is supplemented by the mosaicLevel
        switch (mosaicLevel) {
        case PM_FPA_LEVEL_CHIP:
            psMetadataAddStr(rule, PS_LIST_TAIL, "PSMOSAIC", 0, "Mosaicked level", "CHIP");
            break;
        case PM_FPA_LEVEL_FPA:
            psMetadataAddStr(rule, PS_LIST_TAIL, "PSMOSAIC", 0, "Mosaicked level", "FPA");
            break;
        default:
            psAbort("Should never get here.\n");
        }

        // Fix the FILE information: need to fix the levels for the PHU and EXTENSIONS.
        // both of these elements are required in the format; we raise an error if they are not found
        // If EXTENSIONS is NONE, then we need to change the CONTENT specifier to point to the chip name.
        psMetadata *file = psMetadataLookupMetadata(NULL, format, "FILE"); // File information
        if (!file) {
            psError(PS_ERR_UNKNOWN, false, "Camera format %s has no FILE", formatsItem->name);
            return false;
        }
        psMetadataItem *phuItem = psMetadataLookup(file, "PHU"); // PHU level
        if (!phuItem || phuItem->type != PS_DATA_STRING) {
            psError(PS_ERR_UNKNOWN, false, "Camera format %s is missing PHU in the FILE information", formatsItem->name);
            return false;
        }
        psMetadataItem *extensionsItem = psMetadataLookup(file, "EXTENSIONS"); // Extensions level
        if (!extensionsItem || extensionsItem->type != PS_DATA_STRING) {
            psError(PS_ERR_UNKNOWN, false, "Camera format %s is missing EXTENSIONS in the FILE information", formatsItem->name);
            return false;
        }

        // mosaicLevel == CHIP:
        // Case    PHU     EXTENSIONS     Modifications
        // ====    ===     ==========     ===========
        // 1.      FPA     CHIP           NONE
        // 2.      FPA     CELL           EXT->CHIP
        // 3.      FPA     NONE           NONE
        // 4.      CHIP    CELL           EXT->NONE
        // 5.      CHIP    NONE           NONE
        // 6.      CELL    NONE           PHU->CHIP
        // possible outcomes:
        //         FPA     CHIP
        //         FPA     NONE
        //         CHIP    NONE

        // mosaicLevel == FPA:
        // Case    PHU     EXTENSIONS     Modifications
        // ====    ===     ==========     ===========
        // 1.      FPA     CHIP           EXT->NONE
        // 2.      FPA     CELL           EXT->NONE
        // 3.      FPA     NONE           NONE
        // 4.      CHIP    CELL           PHU->FPA, EXT->NONE
        // 5.      CHIP    NONE           PHU->FPA
        // 6.      CELL    NONE           PHU->FPA
        // possible outcomes:
        //         FPA     NONE

        // modify the values of phuItem and extensionsItem
        switch (mosaicLevel) {
          case PM_FPA_LEVEL_CHIP:
            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "CHIP")) {
                break;
            }
            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "CELL")) {
                psFree(extensionsItem->data.str);
                extensionsItem->data.str = psStringCopy("CHIP");
                break;
            }
            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "NONE")) {
                break;
            }
            if (!strcasecmp(phuItem->data.str, "CHIP") && !strcasecmp(extensionsItem->data.str, "CELL")) {
                psFree(extensionsItem->data.str);
                extensionsItem->data.str = psStringCopy("NONE");
                break;
            }
            if (!strcasecmp(phuItem->data.str, "CHIP") && !strcasecmp(extensionsItem->data.str, "NONE")) {
                break;
            }
            if (!strcasecmp(phuItem->data.str, "CELL") && !strcasecmp(extensionsItem->data.str, "NONE")) {
                psFree(phuItem->data.str);
                phuItem->data.str = psStringCopy("CHIP");
                break;
            }
            psAbort ("should not reach here");

          case PM_FPA_LEVEL_FPA:
            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "CHIP")) {
                psFree(extensionsItem->data.str);
                extensionsItem->data.str = psStringCopy("NONE");
                break;
            }
            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "CELL")) {
                psFree(extensionsItem->data.str);
                extensionsItem->data.str = psStringCopy("NONE");
                break;
            }
            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "NONE")) {
                break;
            }
            if (!strcasecmp(phuItem->data.str, "CHIP") && !strcasecmp(extensionsItem->data.str, "CELL")) {
                psFree(phuItem->data.str);
                phuItem->data.str = psStringCopy("FPA");
                psFree(extensionsItem->data.str);
                extensionsItem->data.str = psStringCopy("NONE");
                break;
            }
            if (!strcasecmp(phuItem->data.str, "CHIP") && !strcasecmp(extensionsItem->data.str, "NONE")) {
                psFree(phuItem->data.str);
                phuItem->data.str = psStringCopy("FPA");
                break;
            }
            if (!strcasecmp(phuItem->data.str, "CELL") && !strcasecmp(extensionsItem->data.str, "NONE")) {
                psFree(phuItem->data.str);
                phuItem->data.str = psStringCopy("FPA");
                break;
            }
            psAbort ("should not reach here");

          default:
            psAbort("Should never get here.\n");
        }

#if 0
        // XXXX when do I need to adjust the value of CONTENT, CELL.NAME, CHIP.NAME?

        // Don't need a "CONTENT" to identify the content!
        if (psMetadataLookup(file, "CONTENT")) {
            psMetadataRemoveKey(file, "CONTENT");
        }
        psMetadataAddStr(file, PS_LIST_TAIL, "CONTENT", PS_META_REPLACE, "Key to CONTENTS menu",
                         "{CHIP.NAME}");
        // Don't need CELL.NAME for chip-mosaicked camera
        if (psMetadataLookup(file, "CELL.NAME")) {
            psMetadataRemoveKey(file, "CELL.NAME");
        }
        // Don't need CHIP.NAME or CELL.NAME for fpa-mosaicked camera
        if (psMetadataLookup(file, "CHIP.NAME")) {
            psMetadataRemoveKey(file, "CHIP.NAME");
        }
        if (psMetadataLookup(file, "CELL.NAME")) {
            psMetadataRemoveKey(file, "CELL.NAME");
        }
#endif

        // Fix up the CONTENTS to contain only the mosaicked cell for each chip
        switch (mosaicLevel) {
          case PM_FPA_LEVEL_FPA:
            psMetadataAddStr(format, PS_LIST_TAIL, TABLE_OF_CONTENTS, PS_META_REPLACE, NULL,
                             "MosaickedChip:MosaickedCell:_mosaic");
            break;
          case PM_FPA_LEVEL_CHIP:
            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "CHIP")) {
                // ensure the value of CONTENT in the FILE section has the right value
                psMetadataAddStr(file, PS_LIST_TAIL, "CONTENT", PS_META_REPLACE, "Key to CONTENTS menu", "{CHIP.NAME}");

                // List the chipName:chipType for each chip.
                psMetadata *contents = psMetadataAlloc(); // List of contents, with chipName:chipType

                psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iteratr
                psMetadataItem *fpaItem;    // Item from iteration
                while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
                    assert (fpaItem->type == PS_DATA_STRING);
                    psString content = NULL; // Content to add
                    psStringAppend(&content, "%s:_mosaicChip ", fpaItem->name);
                    psMetadataAddStr(contents, PS_LIST_TAIL, fpaItem->name, 0, NULL, content);
                    psFree(content);
                }
                psFree(fpaIter);
                psMetadataAddMetadata(format, PS_LIST_TAIL, TABLE_OF_CONTENTS, PS_META_REPLACE,
                                      "List of contents", contents);
                psFree(contents);

                psMetadata *chips = psMetadataAlloc(); // List of chip types, with cellName:cellType
                psMetadataAddStr(chips, PS_LIST_TAIL, "_mosaicChip", 0, NULL,
                                 "MosaickedCell:_mosaic");
                psMetadataAddMetadata(format, PS_LIST_TAIL, CHIP_TYPES, PS_META_REPLACE,
                                      "List of chip types", chips);
                psFree(chips);
                break;
            }
            if (!strcasecmp(phuItem->data.str, "FPA") && !strcasecmp(extensionsItem->data.str, "NONE")) {
                // List the contents on a single line
                psString contentsLine = NULL; // Contents of the PHU
                psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iteratr
                psMetadataItem *fpaItem;    // Item from iteration
                while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
                    assert (fpaItem->type == PS_DATA_STRING);
                    psStringAppend(&contentsLine, "%s:MosaickedCell:_mosaic ", fpaItem->name);
                }
                psFree(fpaIter);
                psMetadataAddStr(format, PS_LIST_TAIL, TABLE_OF_CONTENTS, PS_META_REPLACE,
                                 NULL, contentsLine);
                psFree(contentsLine);
                break;
            }
            if (!strcasecmp(phuItem->data.str, "CHIP") && !strcasecmp(extensionsItem->data.str, "NONE")) {
                // XXX recode this to match the structure above (FPA/CHIP)?
                // select and remove the old contents
                psMetadata *contents = psMetadataLookupMetadata(NULL, format, TABLE_OF_CONTENTS); // File contents
                if (!contents) {
                    psError(PS_ERR_UNKNOWN, false, "Couldn't find %s in the camera format %s.\n",
                            TABLE_OF_CONTENTS, formatsItem->name);
                    return false;
                }
                while (psListLength(contents->list) > 0) {
                    psMetadataRemoveIndex(contents, PS_LIST_TAIL);
                }

                // update with the new contents
                psMetadataIterator *fpaIter = psMetadataIteratorAlloc(fpa, PS_LIST_HEAD, NULL); // Iterator
                psMetadataItem *fpaItem;    // Item from iteration
                while ((fpaItem = psMetadataGetAndIncrement(fpaIter))) {
                    assert (fpaItem->type == PS_DATA_STRING);
                    psMetadataAddStr(contents, PS_LIST_TAIL, fpaItem->name, 0, NULL, "_mosaicChip");
                }
                psFree(fpaIter);

                psMetadata *chips = psMetadataAlloc(); // List of chip types, with cellName:cellType
                psMetadataAddStr(chips, PS_LIST_TAIL, "_mosaicChip", 0, NULL,
                                 "MosaickedCell:_mosaic");
                psMetadataAddMetadata(format, PS_LIST_TAIL, CHIP_TYPES, PS_META_REPLACE,
                                      "List of chip types", chips);
                psFree(chips);
                break;
            }
        default:
            psAbort("Should never get here.\n");
        }

        // Fix the cell type
        psMetadata *cells = psMetadataLookupMetadata(NULL, format, CELL_TYPES); // CELLS information
        if (!cells) {
            psError(PS_ERR_UNKNOWN, false, "Couldn't find CELLS of type METADATA in the camera format %s.\n", formatsItem->name);
            return false;
        }
        psMetadata *cell = psMetadataAlloc(); // Cell information
        psMetadataAddStr(cell, PS_LIST_TAIL, "CELL.TRIMSEC", 0, "Trim section", "TRIMSEC");
        psMetadataAddStr(cell, PS_LIST_TAIL, "CELL.BIASSEC", 0, "Bias section", "BIASSEC");
        psMetadataAddStr(cell, PS_LIST_TAIL, "CELL.TRIMSEC.SOURCE", 0, "Trim section source", "HEADER");
        psMetadataAddStr(cell, PS_LIST_TAIL, "CELL.BIASSEC.SOURCE", 0, "Bias section source", "HEADER");
        psMetadataAddMetadata(cells, PS_LIST_HEAD, "_mosaic", PS_META_REPLACE, "Mosaic cell information", cell);
        psFree(cell);                   // Drop reference

        // Update the concepts, so that they are all stored in the FITS headers, under headers of the same
        // name as the concept
        psMetadata *database = psMetadataLookupMetadata(&mdok, format, "DATABASE"); // DATABASE concepts
        psMetadata *defaults = psMetadataLookupMetadata(&mdok, format, "DEFAULTS"); // DEFAULTS concepts
        if (!mdok || !defaults) {
            psWarning("Couldn't find DEFAULTS of type METADATA in the camera format %s.\n",
                      formatsItem->name);
            continue;
        }
        psMetadata *translation = psMetadataLookupMetadata(&mdok, format, "TRANSLATION"); // TRANSLATION info
        if (!mdok || !translation) {
            psWarning("Couldn't find TRANSLATION of type METADATA in the camera format %s.\n",
                      formatsItem->name);
            continue;
        }

        removeCellConceptsSources(translation);
        removeCellConceptsSources(database);
        removeCellConceptsSources(defaults);

        // Add in the positioning concepts
        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.XPARITY", 0, NULL, 1);
        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.YPARITY", 0, NULL, 1);
        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.X0",      0, NULL, 0);
        psMetadataAddS32(defaults, PS_LIST_TAIL, "CELL.Y0",      0, NULL, 0);
        if (formats) {
            if (psMetadataLookup(formats, "CELL.X0")) {
                psMetadataRemoveKey(formats, "CELL.X0");
            }
            if (psMetadataLookup(formats, "CELL.Y0")) {
                psMetadataRemoveKey(formats, "CELL.Y0");
            }
        }

        if (mosaicLevel == PM_FPA_LEVEL_FPA) {
            removeChipConceptsSources(translation);
            removeChipConceptsSources(database);
            removeChipConceptsSources(defaults);
            psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.XPARITY", 0, NULL, 1);
            psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.YPARITY", 0, NULL, 1);
            psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.X0", 0, NULL, 0);
            psMetadataAddS32(defaults, PS_LIST_TAIL, "CHIP.Y0", 0, NULL, 0);
            if (formats) {
                if (psMetadataLookup(formats, "CHIP.X0")) {
                    psMetadataRemoveKey(formats, "CHIP.X0");
                }
                if (psMetadataLookup(formats, "CHIP.Y0")) {
                    psMetadataRemoveKey(formats, "CHIP.Y0");
                }
            }
        }

    }
    psFree(formatsIter);

    // New camera MUST go to the head of the metadata, so that it will be recognised first
    // The old camera doesn't contain the PSMOSAIC header, so it will match anything!
    psMetadataAddMetadata(newCameras, PS_LIST_HEAD, newName, PS_META_REPLACE,
                          "Automatically generated", new);
    psFree(newName);
    psFree(new);

    return true;
}

/*** Helper Functions ***/

// Remove a concept from the list of sources.  Need to check to see if it exists first, to avoid a warning.
static void removeConcept(psMetadata *source, // Source from which to remove concept
                          const char *concept // Concept name to remove
                         )
{
    assert(source);
    assert(concept && strlen(concept) > 0);

    if (psMetadataLookup(source, concept)) {
        psMetadataRemoveKey(source, concept);
    }

    return;
}

// Remove certain concepts from the list of sources.  These concepts are important in the mosaicking process,
// and are added explicitly to the defaults (elsewhere) so that the user can't get them wrong.
static void removeCellConceptsSources(psMetadata *source // Source for concepts
    )
{
    if (!source) {
        return;
    }

    removeConcept(source, "CELL.BIASSEC");
    removeConcept(source, "CELL.TRIMSEC");
    removeConcept(source, "CELL.XPARITY");
    removeConcept(source, "CELL.YPARITY");
    removeConcept(source, "CELL.X0");
    removeConcept(source, "CELL.Y0");

    // For the sake of the defaults, include the .DEPEND
    removeConcept(source, "CELL.XPARITY.DEPEND");
    removeConcept(source, "CELL.YPARITY.DEPEND");
    removeConcept(source, "CELL.X0.DEPEND");
    removeConcept(source, "CELL.Y0.DEPEND");

    return;
}

// Remove certain concepts from the list of sources.  These concepts are important in the mosaicking process,
// and are added explicitly to the defaults (elsewhere) so that the user can't get them wrong.
static void removeChipConceptsSources(psMetadata *source // Source for concepts
    )
{
    if (!source) {
        return;
    }

    removeConcept(source, "CHIP.XPARITY");
    removeConcept(source, "CHIP.YPARITY");
    removeConcept(source, "CHIP.X0");
    removeConcept(source, "CHIP.Y0");

    // For the sake of the defaults, include the .DEPEND
    removeConcept(source, "CHIP.XPARITY.DEPEND");
    removeConcept(source, "CHIP.YPARITY.DEPEND");
    removeConcept(source, "CHIP.X0.DEPEND");
    removeConcept(source, "CHIP.Y0.DEPEND");

    return;
}
