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


// Flip the chip in x
static psImage *xFlipImage(psImage *image // Image to be flipped
    )
{
    psImage *flipped = psAlloc(image->numCols, image->numRows, image->type.type); // Flipped image
    switch (image->type.type) {
      case PS_TYPE_U16:
	for (int x = 0; x < image->numCols; x++) {
	    flipped->data.U16[y][image->numCols - x - 1] = image->data.U16[y][x];
	}
	break;
      default:
	psError(PS_ERR_IO, true, "Image type %x not yet supported for flip.\n", image->type.type);
    }
}

// Flip the chip in y
static psImage *yFlipImage(psImage *image // Image to be flipped
    )
{
    psImage *flipped = psAlloc(image->numCols, image->numRows, image->type.type); // Flipped image
    switch (image->type.type) {
      case PS_TYPE_U16:
	for (int y = 0; y < image->numRows; y++) {
	    flipped->data.U16[image->numRows - y - 1][x] = image->data.U16[y][x];
	}
	break;
      default:
	psError(PS_ERR_IO, true, "Image type %x not yet supported for flip.\n", image->type.type);
    }
}

// Return a list of the region strings and the method for interpreting them
static psList *getRegions(psString **method, // Method for evaluating the regions
			  psString *methodValues // The METHOD:VALUES string
    )
{
    char *values = strchr(methodValues, ':'); // The VALUES
    *method = psStringNCopy(methodValues, strlen(methodValues) - strlen(values) - 1); // The METHOD
    psList *regionStrings = NULL;	// List of the region strings
    if (strncmp(method, "HEADER", 6) == 0 || strncmp(method, "HD", 2) == 0) {
	regionStrings = papSplit(values, ", ");
    } else if (strncmp(method, "VALUE", 5) == 0 || strncmp(method, "VAL", 3) == 0) {
	regionStrings = papSplit(values, "; ");
    }

    return regionStrings;
}


// Check the image regions
static bool checkRegions(papCell *toCell, // Destination cell
			 papCell *fromCell, // Source cell
			 psMetadata *toHeader, // Destination header
			 psMetadata *fromHeader	// Source header
    )
{
    // Cases:
    // 1. Destination regions are specified by headers:
    //    In this case, set the Destination headers appropriately.
    // 2. Destination regions are specified by values:
    //    In this case, check that the Destination values are consistent.
    
    // Note that none of this accounts for negative indices in the region, as is generally
    // allowed in psLib.  This could be alleviated by using the psRegionForImage() function
    // on the fromImage.

    psString toTrimString = psCellGetValueString(toCell, "CELL.TRIMSEC"); // TRIMSEC from the TO
    psString fromTrimString = psCellGetValueString(fromCell, "CELL.TRIMSEC"); // TRIMSEC from the FROM
    psString toBiasString = psCellGetValueString(toCell, "CELL.BIASSEC"); // BIASSEC from the TO
    psString fromTrimString = psCellGetValueString(fromCell, "CELL.BIASSEC"); // BIASSEC from the FROM
    psString fromTrim = NULL;	// The actual TRIMSEC region in string format from the FROM

    if (strncmp(fromTrimString, "HEADER:", 7) == 0 || strncmp(fromTrimString, "HD:", 3) {
	char *keyword = strchr(fromTrimString, ':') + 1;
	bool mdOK = false;	// Result of MD lookup
	fromTrim = psMetadataLookupString(&mdOK, fromHeader, keyword);
	if (! mdOK) {
	    panic();
	}
    } else if (strncmp(fromTrimString, "VALUE:", 6) == 0 || strncmp(fromTrimString, "VAL:", 4) {
	char *value = strchr(fromTrimString, ':') + 1;
	fromTrim = psStringCopy(value);
    }
	       
	       if (strncmp(toTrimString, "HEADER:", 7) == 0 || strncmp(toTrimString, "HD:", 3) {
		   char *keyword = strchr(toTrimString, ':') + 1;
		   psMetadataAdd(toHeader, PS_LIST_TAIL, keyword, PS_META_STR | PS_META_REPLACE,
				 "Trimsec region from pmFPAMorph", fromTrim);
	       } else if (strncmp(toTrimString, "VALUE:", 6) == 0 || strncmp(toTrimString, "VAL:", 4) {
		   char *toTrim = strchr(toTrimString, ':') + 1;
		   psRegion *toRegion = psRegionFromString(toTrim);
		   psRegion *fromRegion = psRegionFromString(fromTrim);
		   if (toRegion.x1 - toRegion.x0 != fromRegion.x1 - fromRegion.x0 ||
		       toRegion.y1 - toRegion.y0 != fromRegion.y1 - fromRegion.y0) {
		       psError(PS_ERR_IO, true, "Cell size of output is already specified, but does not match "
			       "that of the input (chip %d, cell%d: %dx%d vs %dx%d\n", i, j,
			       toRegion.x1 - toRegion.x0, toRegion.y1 - toRegion.y0,
			       fromRegion.x1 - fromRegion.x0, fromRegion.y1 - fromRegion.y0);
		       return false;
		   }
	       }
		    


			  }

bool pmFPAMorph(papFPA *toFPA,		// FPA structure to which to morph
		papFPA *fromFPA,	// FPA structure from which to morph
		int chipNum,		// Chip number, in case the scopes are different
		int cellNum		// Cell number, in case the scopes are different
    )
{

    // First, check that the sizes of the cells are the same
    for (int i = 0; i < toChips->n; i++) {
	papChip *toChip = toChips->data[i];
	papChip *fromChip = NULL;
	// Select the correct chip
	if (toChips->n == 1 && fromChips->n > chipNum) {
	    fromChip = fromChips->data[chipNum];
	} else if (fromChips->n == 1 && toChips->n > chipNum) {
	    fromChip = fromChips->data[0];
	} else if (toChips->n == fromChips->n) {
	    fromChip = fromChips->data[i];
	} else {
	    psError(PS_ERR_IO, true, "Unable to discern intended chip.\n");
	    return false;
	}
	
	psArray *toCells = toChip->cells; // Array of cells
	psArray *fromCells = fromChip->cells; // Array of cells

	for (int j = 0; j < toCells->n; j++) {
	    papCell *toCell = toCells->data[i];
	    papCell *fromCell = NULL;
	    // Select the correct cell
	    if (toCells->n == 1 && fromCells->n > cellNum) {
		fromCell = fromCells->data[cellNum];
	    } else if (fromCells->n == 1 && toCells->n > cellNum) {
		fromCell = fromCells->data[0];
	    } else if (toCells->n == fromCells->n) {
		fromCell = fromCells->data[i];
	    } else {
		psError(PS_ERR_IO, true, "Unable to discern intended cell.\n");
		return false;
	    }

	    // Copy the cell contents over
	    toCell->readouts = psMemIncrRefCounter(fromCell->readouts);
	    
	    if (!checkRegions(toCell, fromCell, toHeader, fromHeader)) {
		return false;
	    }

		



	    // Change parity if necessary
	    int toXParity = psCellGetValueS32(toCell, "CELL.XPARITY");
	    int toYParity = psCellGetValueS32(toCell, "CELL.YPARITY");
	    int fromXParity = psCellGetValueS32(toCell, "CELL.XPARITY");
	    int fromYParity = psCellGetValueS32(toCell, "CELL.YPARITY");

	    if (toXParity == 0 || fromXParity == 0 || toYParity == 0 || fromYParity == 0) {
		psError(PS_ERR_IO, false, "Unable to determine parity of cell %d in chip %d\n", j, i);
	    }

	    if (toXParity != fromXParity) {
		for (int i = 0; i < readouts->n; i++) {
		    papReadout *readout = readouts->data[i]; // The readout to be flipped

		    // Flip the image
		    psImage *temp = readout->image; // Temporary image
		    readout->image = xFlipImage(temp);
		    psFree(temp);

		    // Flip each of the bias regions
		    psListIterator *overscansIter = psListIteratorAlloc(readout->overscans, PS_LIST_HEAD,
									false);	// Iterator for overscans
		    psList *overscans = psListAlloc(void); // List of flipped overscans
		    psImage *overscan = NULL; // Overscan to flip
		    while (overscan = psListGetAndIncrement(overscansIter)) {
			psImage *flipped = xFlipImage(overscan);
			psListAdd(overscans, PS_LIST_TAIL, flipped);
		    }
		    // Replace the list with the list of flipped images
		    psFree(readout->overscans);
		    readout->overscans = overscans;
		}

	    }

	    if (toYParity != fromYParity) {
		for (int i = 0; i < readouts->n; i++) {
		    papReadout *readout = readouts->data[i]; // The readout to be flipped

		    // Flip the image
		    psImage *temp = readout->image; // Temporary image
		    readout->image = yFlipImage(temp);
		    psFree(temp);

		    // Flip each of the bias regions
		    psListIterator *overscansIter = psListIteratorAlloc(readout->overscans, PS_LIST_HEAD,
									false);	// Iterator for overscans
		    psList *overscans = psListAlloc(void); // List of flipped overscans
		    psImage *overscan = NULL; // Overscan to flip
		    while (overscan = psListGetAndIncrement(overscansIter)) {
			psImage *flipped = yFlipImage(overscan);
			psListAdd(overscans, PS_LIST_TAIL, flipped);
		    }
		    // Replace the list with the list of flipped images
		    psFree(readout->overscans);
		    readout->overscans = overscans;
		}

	    }
















    psArray *pixels = NULL;		// Pixels to copy from lower levels
    psMetadata *header = NULL;		// Header to copy from lower levels

    psArray *toChips = toFPA->chips;	// Array of chips
    psArray *fromChips = fromFPA->chips; // Array of chips

    if (toFPA->extname) {
	// Need to stick pixels in at the FPA level by splicing stuff at lower levels
	toFPA->pixels = pixels;
	toFPA->header = header;
    } else {
	for (int i = 0; i < toChips->n; i++) {
	    papChip *toChip = toChips->data[i];
	    papChip *fromChip = NULL;
	    // Select the correct chip
	    if (toChips->n == 1 && fromChips->n > chipNum) {
		fromChip = fromChips->data[chipNum];
	    } else if (fromChips->n == 1 && toChips->n > chipNum) {
		fromChip = fromChips->data[0];
	    } else if (toChips->n == fromChips->n) {
		fromChip = fromChips->data[i];
	    } else {
		psError(PS_ERR_IO, true, "Unable to discern intended chip.\n");
		return false;
	    }
	
	    psArray *toCells = toChip->cells; // Array of cells
	    psArray *fromCells = fromChip->cells; // Array of cells

	    if (toChip->extname) {
		// Need to stick pixels in at the chip level by splicing stuff at lower levels
		toChip->pixels = pixels;
		toChip->header = header;
	    } else {

		psList *toBeSpliced = psListAlloc(void); // List of cells to be spliced
		int numReadouts = 0;	// Number of readouts in each cell
		psVector *columns = psVectorAlloc(fromCells->n, PS_TYPE_U32); // Number of columns in cells
		psVector *rows = psVectorAlloc(fromCells->n, PS_TYPE_U32); // Number of rows in cells

		int splicedCols = 0;	// Number of columns in spliced image
		int splicedRows = 0;	// Number of rows in spliced image
		bool scanIsRows = true;	// Is the scan done in rows (true) or columns (false)
		int numScans = 0;	// Number of scans (either in rows or columns)

		for (int j = 0; j < toCells->n; j++) {
		    
		    papCell *toCell = toCells->data[i];
		    papCell *fromCell = NULL;
		    // Select the correct cell
		    if (toCells->n == 1 && fromCells->n > cellNum) {
			fromCell = fromCells->data[cellNum];
		    } else if (fromCells->n == 1 && toCells->n > cellNum) {
			fromCell = fromCells->data[0];
		    } else if (toCells->n == fromCells->n) {
			fromCell = fromCells->data[i];
		    } else {
			psError(PS_ERR_IO, true, "Unable to discern intended cell.\n");
			return false;
		    }

		    if (toCell->extname) {
			// Need to stick pixels in at the cell level
			// Since the levels match, we can just stuff them in if the dimensions match
			// Actually, don't worry about checking the dimensions: if the bias sections
			// are hard-wired, then we will later throw an error if the dimensions don't match,
			// and if they are based on a header, we will fix the header.
			if (checkDimensions(toCell, fromCell)) {
			    toCell->readouts = fromCell->readouts;
			} else {
			    psError(PS_ERR_IO, true, "Cell size mismatch (chip %d, cell %d)\n", i, j);
			    continue;
			}
		    } else {
			// Put it on the list to be spliced together

			// Check the number of readouts corresponds
			if (numReadouts == 0) {
			    numReadouts = fromCell->readouts->n;
			} else if (fromCell->readouts->n != numReadouts) {
			    psError(PS_ERR_IO, true, "Determined that cells have %d readouts, but chip %d, "
				    "cell %d has %d readouts --- ignored.\n", numReadouts, i, j,
				    fromCell->readouts->n);
			    continue;
			}

			papReadout *readout = fromCell->readouts->data[0]; // The first readout

			columns->data.U32[k] = readout->image->numCols; // Number of columns in image
			rows->data.U32[k] = readout->image->numRows; // Number of rows in image



			if (numScans != 0) {
			    // Work out the scan direction
			    if (splicedCols


			psVector *biasRows = psVectorAlloc(numReadouts, PS_TYPE_U32); // Number of bias rows
			psVector *biasCols = psVectorAlloc(numReadouts, PS_TYPE_U32); // Number of bias columns

			psList *overscans = readout->overscans; // List of overscans
			psListIterator *overscanIter = psListIteratorAlloc(overscans, PS_LIST_HEAD, false);
			psImage *overscan = NULL; // Overscan image section
			int k = 0;	// Overscan number
			while (overscan = psListGetAndIncrement(overscanIter)) {
			    if (k == 0) {
				// On first overscan, decide which is the scan direction
				if (overscan->numRows == imageRows) {
				    // Scan done in columns
				    scanIsRows = false;
				} else if (overscan->numCols == imageCols) {
				    // Scan done in rows
				    scanIsRows = true;
				} else {
				    psLogMsg(__func__, PS_LOG_WARN, "Unable to determine relation between "
					     "overscans and image for chip %d, cell %d.\n", i, j);
				    // Assume that the scan is done in rows (it usually is)
				}
			    }

			    if (scanIsRows) {
				splicedRows += overscan->numCols;
			    } else {
				splicedCols += overscan->numRows;
			    }

			    k++;
			}

			// Should check that all readouts have the same size
			
			psListAdd(toBeSpliced, PS_LIST_TAIL, fromCell);
		    }
		}

		// Having gone through all the cells in the lowest level, we can now
		// splice the cells together
		psListIterator *spliceIter = psListIteratorAlloc(toBeSpliced, PS_LIST_HEAD, true);
		
		while (
		    

}
