/** @file  psAstrometry.c
*
*  @brief This file defines the basic types for astronomical coordinate
*  transformation
*
*  @ingroup AstroImage
*
*  @author GLG, MHPCC
*
*  @version $Revision: 1.1.4.1 $ $Name: not supported by cvs2svn $
*  @date $Date: 2005-11-21 19:34:44 $
*
*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
*/

/******************************************************************************/
/*  INCLUDE FILES                                                             */
/******************************************************************************/
#include <string.h>
#include <math.h>
#include "pslib.h"
#include "psDB.h"
#include "pmAstrometry.h"

/*****************************************************************************
checkValidImageCoords(): this is a private function which simply determines if
the supplied x,y coordinates are in the range for the supplied psImage.
 
XXX: What about col0 and row0
XXX: This should return a psBool.
 *****************************************************************************/
static psS32 checkValidImageCoords(double x,
                                   double y,
                                   psImage* tmpImage)
{
    PS_ASSERT_IMAGE_NON_NULL(tmpImage, 0);

    if ((x < 0.0) || (x > (double)tmpImage->numCols) ||
            (y < 0.0) || (y > (double)tmpImage->numRows)) {
        return (0);
    }

    return (1);
}

/******************************************************************************
XXX: Is this the correct way to free metadata?
XXX: Is this the correct way to free database structs?
 
 
 
XXX: The memory dereferencing is not quite right with these functions.  If I
call the alloc functions with non-NULL pointers, there will be memory leaks.
 *****************************************************************************/
static void FPAFree(pmFPA *fpa)
{
    if (fpa != NULL) {
        psFree(fpa->fromTangentPlane);
        psFree(fpa->toTangentPlane);
        psFree(fpa->projection);
        psFree(fpa->concepts);
        psFree(fpa->analysis);
        psFree(fpa->camera);
        //
        // Set the parent to NULL in all fpa->chips before psFree(fpa->chips)
        // in order to avoid memory reference counter problems.
        //
        for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
            pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
            tmpChip->parent = NULL;
        }
        psFree(fpa->chips);
        psFree(fpa->header);
        psFree(fpa->db);
    }
}

static void chipFree(pmChip* chip)
{
    if (chip != NULL) {
        psFree(chip->toFPA);
        psFree(chip->fromFPA);
        psFree(chip->concepts);
        psFree(chip->analysis);
        //
        // Set the parent to NULL in all chip->cells before psFree(chip->cells)
        // in order to avoid memory reference counter problems.
        //
        for (psS32 i = 0 ; i < chip->cells->n ; i++) {
            pmCell *tmpCell = (pmCell *) chip->cells->data[i];
            tmpCell->parent = NULL;
        }
        psFree(chip->cells);
        psFree(chip->parent);
        psFree(chip->extname);
        psFree(chip->pixels);
        psFree(chip->header);
    }
}

static void cellFree(pmCell *cell)
{
    if (cell != NULL) {
        psFree(cell->toChip);
        psFree(cell->toFPA);
        psFree(cell->toSky);
        psFree(cell->concepts);
        psFree(cell->analysis);
        //
        // Set the parent to NULL in all cell->readouts before psFree(cell->readouts)
        // in order to avoid memory reference counter problems.
        //
        for (psS32 i = 0 ; i < cell->readouts->n ; i++) {
            pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
            tmpReadout->parent = NULL;
        }
        psFree(cell->readouts);
        psFree(cell->parent);
        psFree(cell->extname);
        psFree(cell->pixels);
        psFree(cell->header);
    }
}

static void readoutFree(pmReadout *readout)
{
    if (readout != NULL) {
        psFree(readout->image);
        psFree(readout->mask);
        psFree(readout->analysis);
        psFree(readout->concepts);
        psFree(readout->parent);
    }
}

// XXX: Verify these default values for row0, col0, rowBins, colBins
pmReadout *pmReadoutAlloc(pmCell *cell)
{
    pmReadout *tmpReadout = (pmReadout *) psAlloc(sizeof(pmReadout));

    tmpReadout->col0 = -1;
    tmpReadout->row0 = -1;
    tmpReadout->colBins = -1;
    tmpReadout->rowBins = -1;
    tmpReadout->image = NULL;
    tmpReadout->mask = NULL;
    tmpReadout->analysis = psMetadataAlloc();
    tmpReadout->concepts = psMetadataAlloc();
    tmpReadout->parent = cell;
    if (cell != NULL) {
        cell->readouts = psArrayAdd(cell->readouts, 1, (psPtr) tmpReadout);
    }
    psMemSetDeallocator(tmpReadout, (psFreeFunc) readoutFree);
    return(tmpReadout);
}

pmCell *pmCellAlloc(pmChip *chip)
{
    pmCell *tmpCell = (pmCell *) psAlloc(sizeof(pmCell));

    tmpCell->col0 = -1;
    tmpCell->row0 = -1;
    tmpCell->toChip = NULL;
    tmpCell->toFPA = NULL;
    tmpCell->toSky = NULL;
    tmpCell->analysis = psMetadataAlloc();
    tmpCell->concepts = psMetadataAlloc();
    tmpCell->readouts = psArrayAlloc(0);
    tmpCell->parent = chip;
    if (chip != NULL) {
        chip->cells = psArrayAdd(chip->cells, 1, (psPtr) tmpCell);
    }
    tmpCell->valid = false;
    tmpCell->extname = NULL;
    tmpCell->pixels = NULL;
    tmpCell->header = NULL;

    psMemSetDeallocator(tmpCell, (psFreeFunc) cellFree);
    return(tmpCell);
}

pmChip *pmChipAlloc(pmFPA *fpa)
{
    pmChip *tmpChip = (pmChip *) psAlloc(sizeof(pmChip));

    tmpChip->col0 = -1;
    tmpChip->row0 = -1;
    tmpChip->toFPA = NULL;
    tmpChip->fromFPA = NULL;
    tmpChip->concepts = psMetadataAlloc();
    tmpChip->analysis = psMetadataAlloc();
    tmpChip->cells = psArrayAlloc(0);
    tmpChip->parent = fpa;
    if (fpa != NULL) {
        fpa->chips = psArrayAdd(fpa->chips, 1, (psPtr) tmpChip);
    }
    tmpChip->valid = false;
    tmpChip->extname = NULL;
    tmpChip->pixels = NULL;
    tmpChip->header = NULL;

    psMemSetDeallocator(tmpChip, (psFreeFunc) chipFree);
    return(tmpChip);
}

pmFPA *pmFPAAlloc(
    const psMetadata *camera,
    psDB *db)
{
    pmFPA *tmpFPA = (pmFPA *) psAlloc(sizeof(pmFPA));

    tmpFPA->fromTangentPlane = NULL;
    tmpFPA->toTangentPlane = NULL;
    tmpFPA->projection = NULL;
    tmpFPA->concepts = psMetadataAlloc();
    tmpFPA->analysis = psMetadataAlloc();
    tmpFPA->camera = camera;
    psMemIncrRefCounter((psPtr) camera);
    tmpFPA->chips = psArrayAlloc(0);
    tmpFPA->header = NULL;
    tmpFPA->db = db;
    psMemIncrRefCounter((psPtr) db);

    psMemSetDeallocator(tmpFPA, (psFreeFunc) FPAFree);
    return(tmpFPA);
}

bool cellCheckParents(pmCell *cell)
{
    if (cell == NULL) {
        return(true);
    }
    psBool flag = true;

    for (psS32 i = 0 ; i < cell->readouts->n ; i++) {
        pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpReadout, false);
        if (tmpReadout->parent != cell) {
            tmpReadout->parent = cell;
            flag = false;
        }
    }
    return(flag);
}

bool chipCheckParents(pmChip *chip)
{
    if (chip == NULL) {
        return(true);
    }
    psBool flag = true;

    for (psS32 i = 0 ; i < chip->cells->n ; i++) {
        pmCell *tmpCell = (pmCell *) chip->cells->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpCell, false);
        if (tmpCell->parent != chip) {
            tmpCell->parent = chip;
            flag = false;
        }

        flag &= cellCheckParents(tmpCell);
    }
    return(flag);
}

bool pmFPACheckParents(pmFPA *fpa)
{
    if (fpa == NULL) {
        return(true);
    }
    psBool flag = true;

    for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
        pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpChip, false);
        if (tmpChip->parent != fpa) {
            tmpChip->parent = fpa;
            flag = false;
        }

        flag &= chipCheckParents(tmpChip);
    }
    return(flag);
}



// HEY
/*****************************************************************************/
/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
/*****************************************************************************/

pmCell* pmCellInFPA(const psPlane* fpaCoord,
                    const pmFPA* FPA)
{
    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(FPA, NULL);

    pmChip* tmpChip = NULL;
    psPlane chipCoord;
    pmCell* outCell = NULL;

    // Determine which chip contains the fpaCoords.
    tmpChip = pmChipInFPA(fpaCoord, FPA);
    if (tmpChip == NULL) {
        return(NULL);
    }

    // Convert to those chip coordinates.
    psCoordFPAToChip(&chipCoord, fpaCoord, tmpChip);

    // Determine which cell contains those chip coordinates.
    outCell = pmCellInChip(&chipCoord, tmpChip);

    return (outCell);
}

pmChip* pmChipInFPA(const psPlane* fpaCoord,
                    const pmFPA* FPA)
{
    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(FPA, NULL);
    PS_ASSERT_PTR_NON_NULL(FPA->chips, NULL);

    psArray* chips = FPA->chips;
    psS32 nChips = chips->n;
    psPlane chipCoord;
    pmCell *tmpCell = NULL;

    // Loop through every chip in this FPA.  Convert the original FPA
    // coordinates to chip coordinates for that chip.  Then, determine if any
    // cells in that chip contain those chip coordinates.

    for (psS32 i = 0; i < nChips; i++) {
        pmChip* tmpChip = chips->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpChip, NULL);
        PS_ASSERT_PTR_NON_NULL(tmpChip->fromFPA, NULL);

        psPlaneTransformApply(&chipCoord, tmpChip->fromFPA, fpaCoord);

        tmpCell = pmCellInChip(&chipCoord, tmpChip);
        if (tmpCell != NULL) {
            return(tmpChip);
        }
    }

    // XXX: Print warning here?
    return (NULL);
}


pmCell* pmCellInChip(const psPlane* chipCoord,
                     const pmChip* chip)
{
    PS_ASSERT_PTR_NON_NULL(chipCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(chip, NULL);

    psPlane cellCoord;
    psArray* cells;

    cells = chip->cells;
    if (cells == NULL) {
        return NULL;
    }

    // We loop over each cell in the chip.  We transform the chipCoord into
    // a cellCoord for that cell and determine if that cellCoord is valid.
    // If so, then we return that cell.

    for (psS32 i = 0; i < cells->n; i++) {
        pmCell* tmpCell = (pmCell* ) cells->data[i];
        PS_ASSERT_PTR_NON_NULL(tmpCell, NULL);

        psPlaneTransform *chipToCell = NULL;
        if (1 != p_psIsProjectionLinear(tmpCell->toChip)) {
            // XXX: Generate warning message.
            // XXX: Can we use the following function to derive a transform?
            // chipToCell = psPlaneTransformInvert(NULL, tmpCell->toChip, NULL, -1);
        } else {
            chipToCell = p_psPlaneTransformLinearInvert(tmpCell->toChip);
        }

        PS_ASSERT_PTR_NON_NULL(chipToCell, NULL);
        psArray* readouts = tmpCell->readouts;

        if (readouts != NULL) {
            for (psS32 j = 0; j < readouts->n; j++) {
                pmReadout* tmpReadout = readouts->data[j];
                PS_ASSERT_READOUT_NON_NULL(tmpReadout, NULL);

                psPlaneTransformApply(&cellCoord,
                                      chipToCell,
                                      chipCoord);

                if (checkValidImageCoords(cellCoord.x,
                                          cellCoord.y,
                                          tmpReadout->image)) {
                    return (tmpCell);
                }
            }
        }
        psFree(chipToCell);
    }

    return (NULL);
}


psPlane* psCoordCellToChip(psPlane* outCoord,
                           const psPlane* inCoord,
                           const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);

    return (psPlaneTransformApply(outCoord, cell->toChip, inCoord));
}


psPlane* psCoordChipToFPA(psPlane* outCoord,
                          const psPlane* inCoord,
                          const pmChip* chip)
{
    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(chip, NULL);

    return (psPlaneTransformApply(outCoord, chip->toFPA, inCoord));
}



psPlane* psCoordFPAToTP(psPlane* outCoord,
                        const psPlane* inCoord,
                        double color,
                        double magnitude,
                        const pmFPA* fpa)
{
    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(fpa, NULL);

    return(psPlaneDistortApply(outCoord, fpa->toTangentPlane, inCoord,
                               color, magnitude));
}

/*****************************************************************************
XXX: What about units for the (x,y) coords?
 *****************************************************************************/
psSphere* psCoordTPToSky(psSphere* outSphere,
                         const psPlane* tpCoord,
                         const psProjection *projection
                        )
{
    PS_ASSERT_PTR_NON_NULL(tpCoord, NULL);

    if (outSphere == NULL) {
        outSphere = (psSphere* ) psAlloc(sizeof(psSphere));
    }

    // XXX: this was done by a SLALIB call -- needs to be reimplemented
    // psWarning("Warning!  psCoordTPToSky functionality is no longer implemented");
    // slaAopqk(tpCoord->x, tpCoord->y, (double*)grommit,
    //         &AOB, &ZOB, &HOB, &outSphere->r, &outSphere->d);

    // XXX EAM : basic TPToSky is performed by applying the projection
    //           when the EOC code is ready, the full atmosphere, precession, nutation
    //           model can be applied.

    // XXX EAM : psDeproject needs to have a psSphere *out argument:
    //           psPlane* psProject(psSphere *out, const psPlane *coord, const psProjection *projection);

    psSphere *tmp = psDeproject (tpCoord, projection);
    *outSphere = *tmp;
    psFree (tmp);

    return (outSphere);
}



psPlane* psCoordCellToFPA(psPlane* fpaCoord,
                          const psPlane* cellCoord,
                          const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);

    return (psPlaneTransformApply(fpaCoord, cell->toFPA, cellCoord));
}



psSphere* psCoordCellToSky(psSphere* skyCoord,
                           const psPlane* cellCoord,
                           double color,
                           double magnitude,
                           const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->toFPA, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent->parent, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->toTangentPlane, NULL);
    //    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->exposure, NULL);

    psPlane* fpaCoord = NULL;
    psPlane* tpCoord = NULL;
    pmFPA* parFPA = (cell->parent)->parent;
    //    psGrommit* tmpGrommit = NULL;

    // Convert the input cell coordinates to FPA coordinates.
    fpaCoord = psPlaneTransformApply(fpaCoord, cell->toFPA, cellCoord);

    // Convert the FPA coordinates to tangent plane Coordinates.
    tpCoord = psPlaneDistortApply(tpCoord, parFPA->toTangentPlane,
                                  fpaCoord, color, magnitude);

    // Generate a grommit for this FPA.
    //    tmpGrommit = psGrommitAlloc(parFPA->exposure);

    // Convert the tangent plane Coordinates to sky coordinates.
    //    skyCoord = psCoordTPToSky(skyCoord, tpCoord, tmpGrommit);

    psFree(fpaCoord);
    psFree(tpCoord);
    //    psFree(tmpGrommit);

    return(skyCoord);
}


psSphere* psCoordCellToSkyQuick(psSphere* outSphere,
                                const psPlane* cellCoord,
                                const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->toSky, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent->parent, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->projection, NULL);

    psLogMsg(__func__, PS_LOG_WARN,
             "WARNING: psCoordCellToSkyQuick(): This function is not fully specified in the SDRS.  Returning NULL.\n");
    return(NULL);

    /*
        if (cell->toSky) {
            // XXX: Should we use toTP or toSky?
            psLogMsg(__func__, PS_LOG_WARN,
                     "WARNING: psCoordCellToSkyQuick(): The cell->toSky transform is ignored.  The cell->toTP transform is being used.");
        }
     
        psPlane *tpCoord = NULL;
        pmChip *chip = cell->parent;
        pmFPA *FPA = chip->parent;
        psProjectionType oldProjectionType;
     
        if (outSphere == NULL) {
            outSphere = (psSphere* ) psAlloc(sizeof(psSphere));
        }
     
        // Determine the tangent plane coordinates.
        tpCoord = psPlaneTransformApply(NULL, cell->toTP, cellCoord);
     
        // Save the old projection type and set the new projection type to TAN.
        oldProjectionType = FPA->projection->type;
        FPA->projection->type = PS_PROJ_TAN;
     
        // Deproject the tangent plane coordinates a sphere.
        outSphere = psDeproject(tpCoord, FPA->projection);
     
        // Restore old projection type.  Free memory.
        FPA->projection->type = oldProjectionType;
        psFree(tpCoord);
     
        return (outSphere);
    */
}


/*****************************************************************************
XXX: What about units for the (x,y) coords?
 *****************************************************************************/
psPlane* psCoordSkyToTP(psPlane* tpCoord,
                        const psSphere* in,
                        const psProjection *projection)
{
    PS_ASSERT_PTR_NON_NULL(in, NULL);
    //    PS_ASSERT_PTR_NON_NULL(grommit, NULL);

    // char* type = "RA";

    if (tpCoord == NULL) {
        tpCoord = (psPlane* ) psAlloc(sizeof(psPlane));
    }

    // XXX: this was done by a SLALIB call -- needs to be reimplemented
    // psWarning("Warning!  psCoordSkyToTP functionality is no longer implemented");
    // slaOapqk(type, in->r, in->d, (double*)grommit, &tpCoord->x, &tpCoord->y);

    psPlane *tmp = psProject (in, projection);
    *tpCoord = *tmp;
    psFree (tmp);

    return(tpCoord);
}


psPlane* psCoordTPToFPA(psPlane* fpaCoord,
                        const psPlane* tpCoord,
                        double color,
                        double magnitude,
                        const pmFPA* fpa)
{
    PS_ASSERT_PTR_NON_NULL(tpCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
    PS_ASSERT_PTR_NON_NULL(fpa->fromTangentPlane, NULL);

    return (psPlaneDistortApply(fpaCoord, fpa->fromTangentPlane,
                                tpCoord, color, magnitude));
}


psPlane* psCoordFPAToChip(psPlane* chipCoord,
                          const psPlane* fpaCoord,
                          const pmChip* chip)
{
    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(chip, NULL);
    PS_ASSERT_PTR_NON_NULL(chip->fromFPA, NULL);

    chipCoord = psPlaneTransformApply(chipCoord, chip->fromFPA, fpaCoord);
    return(chipCoord);
}


psPlane* psCoordChipToCell(psPlane* cellCoord,
                           const psPlane* chipCoord,
                           const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(chipCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);

    pmCell *tmpCell = pmCellInChip(chipCoord, cell->parent);
    PS_ASSERT_PTR_NON_NULL(tmpCell->toChip, NULL);
    psPlaneTransform *tmpChipToCell = p_psPlaneTransformLinearInvert(tmpCell->toChip);
    PS_ASSERT_PTR_NON_NULL(tmpChipToCell, NULL);
    cellCoord = psPlaneTransformApply(cellCoord, tmpChipToCell, chipCoord);
    psFree(tmpChipToCell);
    return(cellCoord);
}


psPlane* psCoordSkyToCell(psPlane* cellCoord,
                          const psSphere* skyCoord,
                          float color,
                          float magnitude,
                          const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(skyCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent->parent, NULL);
    //    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->grommit, NULL);

    pmChip *parChip = cell->parent;
    pmFPA *parFPA = parChip->parent;

    // Convert the skyCoords to tangent plane coords.
    psPlane *tpCoord = psCoordSkyToTP(NULL, skyCoord, parFPA->projection);

    // Convert the tangent plane coords to FPA coords.
    psPlane *fpaCoord = psCoordTPToFPA(NULL, tpCoord, color, magnitude, parFPA);

    // Convert the FPA coords to chip coords.
    psPlane *chipCoord = psCoordFPAToChip(NULL, fpaCoord, parChip);

    // Convert the chip coords to cell coords.
    cellCoord = psCoordChipToCell(cellCoord, chipCoord, cell);

    psFree(tpCoord);
    psFree(fpaCoord);
    psFree(chipCoord);

    return (cellCoord);
}


psPlane* psCoordSkyToCellQuick(psPlane* cellCoord,
                               const psSphere* skyCoord,
                               const pmCell* cell)
{
    PS_ASSERT_PTR_NON_NULL(skyCoord, NULL);
    PS_ASSERT_PTR_NON_NULL(cell, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent->parent, NULL);
    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->projection, NULL);


    psLogMsg(__func__, PS_LOG_WARN,
             "WARNING: psCoordSkyToCellQuick(): This function is not fully specified in the SDRS.  Returning NULL.\n");
    return(NULL);
    /*
     
        if (cell->toSky) {
            // XXX: Should we use toTP or toSky?
            psLogMsg(__func__, PS_LOG_WARN,
                     "WARNING: psCoordSkyToCellQuick: The cell->toSky transform is ignored.  The cell->toTP transform is being used.");
        }
     
        psPlane *tpCoord = NULL;
        pmChip *whichChip = cell->parent;
        pmFPA  *whichFPA = whichChip->parent;
        psProjectionType oldProjectionType;
        psPlaneTransform *TPtoCell = NULL;
     
        // Save the old projection type and set the new projection type to TAN.
        oldProjectionType = whichFPA->projection->type;
        whichFPA->projection->type = PS_PROJ_TAN;
     
        if (cellCoord == NULL) {
            cellCoord = (psPlane* ) psAlloc(sizeof(psPlane));
        }
     
        tpCoord = psProject(skyCoord, whichFPA->projection);
     
        // generate an error if cell->toTP is not linear.
        if (0 == p_psIsProjectionLinear(cell->toTP)) {
            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                    PS_ERRORTEXT_psAstrometry_NONLINEAR_TRANSFORM,
                    "cell to tangent plane");
        }
     
        TPtoCell = p_psPlaneTransformLinearInvert(cell->toTP);
        cellCoord = psPlaneTransformApply(cellCoord, TPtoCell, tpCoord);
     
        // Restore old projection type.  Free memory.
        whichFPA->projection->type = oldProjectionType;
        psFree(tpCoord);
        return (cellCoord);
    */
}

/*
psMetadataItem* psMetadataLookup(
    const psMetadata * md,             ///< Metadata collection to lookup meta!
    const char * key                   ///< Name of metadata key.
);
*/

// XXX: How should we handle errors?  What if psMetadataLookup() is NULL?
psMetadataItem *pmReadoutGetConcept(pmReadout *readout, const char *concept)
{
    return(psMetadataLookup(readout->concepts, concept));
}

psMetadataItem *pmCellGetConcept(pmCell *cell, const char *concept)
{
    return(psMetadataLookup(cell->concepts, concept));
}

psMetadataItem *pmChipGetConcept(pmChip *chip, const char *concept)
{
    return(psMetadataLookup(chip->concepts, concept));
}

psMetadataItem *pmFPAGetConcept(pmFPA *fpa, const char *concept)
{
    return(psMetadataLookup(fpa->concepts, concept));
}


float pmFPAGetAirmass(pmFPA *fpa) // FPA.AIRMASS
{
    psMetadataItem *tmp = pmFPAGetConcept(fpa, "FPA.AIRMASS");
    return((float) tmp->data.F32);
}

psString pmFPAGetFilter(pmFPA *fpa) // FPA.FILTER
{
    psMetadataItem *tmp = pmFPAGetConcept(fpa, "FPA.FILTER");
    return((psString) tmp->data.V);
}

float pmFPAGetPosAngle(pmFPA *fpa) // FPA.POSANGLE
{
    psMetadataItem *tmp = pmFPAGetConcept(fpa, "FPA.POSANGLE");
    return((float) tmp->data.F32);
}

double pmFPAGetRA(pmFPA *fpa) // FPA.RA
{
    psMetadataItem *tmp = pmFPAGetConcept(fpa, "FPA.RA");
    return((float) tmp->data.F32);
}

double pmFPAGetDec(pmFPA *fpa) // FPA.DEC
{
    psMetadataItem *tmp = pmFPAGetConcept(fpa, "FPA.DEC");
    return((float) tmp->data.F32);
}

psString pmFPAGetRADecSys(pmFPA *fpa) // FPA.RADECSYS
{
    psMetadataItem *tmp = pmFPAGetConcept(fpa, "FPA.RADECSYS");
    return((psString) tmp->data.V);
}

psString pmFPAGetName(pmFPA *fpa) // FPA.NAME
{
    psMetadataItem *tmp = pmFPAGetConcept(fpa, "FPA.NAME");
    return((psString) tmp->data.V);
}

psString pmChipGetName(pmChip *chip) // CHIP.NAME
{
    psMetadataItem *tmp = pmChipGetConcept(chip, "CHIP.NAME");
    return((psString) tmp->data.V);
}

psString pmCellGetName(pmCell *cell) // CELL.NAME
{
    psMetadataItem *tmp = pmCellGetConcept(cell, "CELL.NAME");
    return((psString) tmp->data.V);
}

psTime *pmCellGetTime(pmCell *cell) // CELL.TIME
{
    psMetadataItem *tmp = pmCellGetConcept(cell, "CELL.TIME");
    return((psTime *) tmp->data.V);
}

psList *pmCellGetBiasSec(pmCell *cell) // CELL.BIASSEC
{
    psMetadataItem *tmp = pmCellGetConcept(cell, "CELL.BIASSEC");
    return((psList *) tmp->data.list);
}

psRegion pmCellGetTrimSec(pmCell *cell) // CELL.TRIMSEC
{
    psMetadataItem *tmp = pmCellGetConcept(cell, "CELL.TRIMSEC");
    return((psRegion) *((psRegion *) (tmp->data.V)));
}

float pmCellGetGain(pmCell *cell) // CELL.GAIN
{
    psMetadataItem *tmp = pmCellGetConcept(cell, "CELL.GAIN");
    return((float) tmp->data.F32);
}

float pmCellGetReadNoise(pmCell *cell) // CELL.READNOISE
{
    psMetadataItem *tmp = pmCellGetConcept(cell, "CELL.READNOISE");
    return((float) tmp->data.F32);
}

float pmCellGetSaturation(pmCell *cell) // CELL.SATURATION
{
    psMetadataItem *tmp = pmCellGetConcept(cell, "CELL.SATURATION");
    return((float) tmp->data.F32);
}

float pmCellGetBad(pmCell *cell) // CELL.BAD
{
    psMetadataItem *tmp = pmCellGetConcept(cell, "CELL.BAD");
    return((float) tmp->data.F32);
}


psPixelCoord pmCellGetBin(pmCell *cell) // CELL.BIN
{
    psMetadataItem *tmp = pmCellGetConcept(cell, "CELL.BIN");
    return((psPixelCoord)  *((psPixelCoord *) (tmp->data.V)));
}

psPixelCoord pmCellGetParity(pmCell *cell) // CELL.PARITY
{
    psMetadataItem *tmp = pmCellGetConcept(cell, "CELL.PARITY");
    return((psPixelCoord)  *((psPixelCoord *) (tmp->data.V)));
}

float pmReadoutGetExposure(pmReadout *readout) // READOUT.EXPOSURE
{
    psMetadataItem *tmp = pmReadoutGetConcept(readout, "READOUT.EXPOSURE");
    return((float) tmp->data.F32);
}

float pmReadoutGetDarkTime(pmReadout *readout) // READOUT.DARKTIME
{
    psMetadataItem *tmp = pmReadoutGetConcept(readout, "READOUT.DARKTIME");
    return((float) tmp->data.F32);
}




/*
typedef struct
{
    float x0;
}
psJunk;
 
psJunk *pmCellTmp(pmCell *cell) // CELL.TRIMSEC
{
    psMetadataItem *tmp;
    return((psJunk *) tmp->data.V);
}
 
*/
