# include <pslib.h>

main (int argc, char **argv) {

    psArray *headers;
    psMetadata *base, *site, *camera, *recipe;

    pmConfigLoadBasic (&site, &base);

    fits = psFitsAlloc (input);
    headers = psFitsReadHeaderSet (fits);

    pmConfigLoadCamera (&camera, site, headers->data[0]);
    pmConfigLoadRecipe (&recipe, camera, "PHASE2");

    // check the validity of the given set of headers based on expectactions for this camera
    pmCameraValidateHeaders (headers, camera);

    // construct a complete, empty (ie, no pixel data) FPA structure for this camera / header set
    FPA = pmFPAfromHeader (headers, camera);
  
    /* ideally, the sequence of cells will be the same as the sequence of extensions */
    for (i = 0; i < FPA[0].chips.n; i++) {
	chip = FPA[0].chips.data[i];
	for (j = 0; j < chip[0].cells.n; j++) {
	    cell = chip[0].cells.data[j];

	    cellMD  = psMetadataLookupPtr (cell->metadata, "CELL");
	    Nextend = psMetadataLookupS32 (cellMD, "EXT.NUMBER");
	    f = psFitsMoveExtNum(f, extNum, FALSE); // this should not move if we are already there

	    for (k = 0; k < cell[0].readouts.n; k++) {
		readout = cell[0].readouts[k];

		// load the specific readout pixels.  put this in a function?  
		cell[0].readouts[k] = pmReadoutLoad (cell[0].readouts[k], fits, cell, k);
		bias_subtract_readout (readout, NULL, recipe);
		
	    }
	}
    }	
}

// bias subtract the given readout according to the recipe (bias image may be NULL)
// no mask needed yet
bias_subtract_readout (psReadout *readout, psImage *bias, psMetadata *recipe) {
  
    char *biassec;
    psImage *overscan;
    psList *overscanlist;
    pmOverscanAxis direction;
    psRectangle biassec;

    // determine overscan option
    biassec = pmConfigLookupRegion (recipe, readout[0].metadata, "BIAS.OVERSCAN"); 
    overscan = psImageSubsection (readout[0].image, biassec);

    psListAdd (overscanlist, overscan, PS_LIST_HEAD);             // list of overscans (allocate list first)

    // the direction is implied by noting the long dimension of the biassec
    direction = DirectionFromOverscan (biassec);  // weak, but do we want a function to deal with this or add it inline?

    // the type of statistic is defined by the recipe: BIAS.OVERSCAN.STATS
    statname = psMetadataLookup (recipe, "BIAS.OVERSCAN.STATS");  // string defining overscan stats
    stats = DefineStats (statname);

    // identify the type of fit requested and relevant parameters
    // the type of fit is defined by the recipe: BIAS.OVERSCAN.STATS
    fitname = psMetadataLookup (recipe, "BIAS.OVERSCAN.FIT");  // string defining overscan stats

    if (!strcmp (fitname, "NONE")) {
	fitType = PM_FIT_NONE;
	fitSpec = NULL;
	nBin = 0;
    }

    if (!strcmp (fitname, "SPLINE")) {
	fitType = PM_FIT_SPLINE;
	nBin = psMetadataLookup (recipe, "BIAS.OVERSCAN.FIT.NBIN");
	nPts = psMetadataLookup (recipe, "BIAS.OVERSCAN.FIT.NPTS");
	order = psMetadataLookup (recipe, "BIAS.OVERSCAN.FIT.ORDER");  // only 1 or 3 is allowed for 'order'
	fitSpec = psSpline1DAlloc (nPts, 3, 0, Npix);                  // Npix is either NAXIS1 or NAXIS2 (Nx or Ny);
    }

    if (!strcmp (fitname, "POLYNOMIAL")) {
	fitType = PM_FIT_POLYNOMIAL;
	nBin = psMetadataLookup (recipe, "BIAS.OVERSCAN.FIT.NBIN");
	nPts = psMetadataLookup (recipe, "BIAS.OVERSCAN.FIT.NPTS");
	order = psMetadataLookup (recipe, "BIAS.OVERSCAN.FIT.ORDER");
	fitSpec = psPolynomial1DAlloc (nPts, PS_POLYNOMIAL_CHEB);
    }

    readout[0].image = pmSubtractBias (readout[0].image, fitSpec, overscanlist, direction, stats, nBin, PM_FIT_NONE, bias);

}

// subtract dark image using the appropriate scaling factor for this exposure time
// no mask needed yet
dark_subtract_readout (psReadout *readout, psImage *dark, psMetadata *recipe) {
  
    char *biassec;
    psImage *overscan;
    psList *overscanlist;
    pmOverscanAxis direction;

    if (dark == NULL) {
	return;
    }
    
    // need to get a lookup table from the database, apply exptime for readout and dark
    factor_source = psMetadataLookup (recipe, "DARK.FACTOR"); // use the recipe to determine the dark scaling
    factor = getScalingFactor (readout, dark, factor_source); 

    // out = in - dark * factor
    tmpdark = psBinaryOp (NULL, dark, "*", factor);
    readout[0].image = psBinaryOp (readout[0].image, readout[0].image, "-", tmpdark);

    psFree (tmpdark);
}

// this corrects the non-linear response, but probably should use data determined at the cell level
// we need to set a mask value for pixels which have were out of range (TBD)
nonlinearity_correct_readout (psReadout *readout, psReadout *mask, psMetadata *recipe) {
  
    char *method;
    psPolynomial1D correction;
    psVector inFlux, outFlux;

    method = psMetadataLookup (recipe, "NONLINEAR.METHOD");

    // use a polynomial; get coeffs from recipe
    if (!strcmp (method, "POLYNOMIAL")) {
	correction.n = psMetadataLookup (recipe, "NONLINEAR.ORDER"); 
	correction.coeffs = psMetadataLookup (recipe, "NONLINEAR.COEFFS"); 
	pmNonLinearityPolynomial (readout, correction);
    }

    if (!strcmp (method, "LOOKUP")) {
	lookup_table = DetrendDatabaseLookup (time, cell, camera, etc?);
	inFlux = psFITSTableRead (lookup_table, "IN_FLUX"); 
	outFlux = psFITSTableRead (lookup_table, "OUT_FLUX"); 
	pmNonLinearityLookup (readout, inFlux, outFlux);
    }
}

// trim the image and mask based on the requested region
trim_readout (psReadout *readout, psReadout *mask, psMetadata *recipe) {
  
    datasec = pmRecipeDefineSection (recipe, readout[0].metadata, "TRIM.DATASEC"); 

    psImageTrim (readout[0].image, datasec);
    psImageTrim (mask[0].image, datasec);

}

// apply the bad pixel mask to the image mask
mask_readout (psReadout *readout, psImage *mask, psImage *badpix, psKernel *kernel, psMetadata *recipe) {
  
}

psImage *load_bias_image (psMetadata *recipe, psMetadata *header, char *extname) {

    // load in the complete corresponding bias image (this should be done once per cell)
    // we are implying that the bias image is guaranteed to have the corresponding EXTNAME
    bias_source = psMetadataLookup (recipe, "BIAS.IMAGE");
    biasname = NULL;
    if (!strncmp (bias_source, "FILE:", 5)) {
	biasname = &bias_source[5];
    }
    if (!strcmp (bias_source, "DB:BEST")) {
	biasname = DetrendDatabaseLookup ("best", header);   // these APIs need to be seriously fleshed out!!
    }
    if (!strcmp (bias_source, "DB:CLOSE")) {
	biasname = DetrendDatabaseLookup ("close", header);   // these APIs need to be seriously fleshed out!!
    }
    if (biasname != NULL) {
	bias     = psImageReadSection (NULL, 0, 0, 0, 0, 0, extname, 0, biasname);
    }

    return (bias);
}

psImage *load_dark_image (psMetadata *recipe, psMetadata *header, char *extname) {

    // load in the complete corresponding bias image (this should be done once per cell)
    // we are implying that the bias image is guaranteed to have the corresponding EXTNAME
    dark_source = psMetadataLookup (recipe, "DARK.IMAGE");
    darkname = NULL;
    if (!strncmp (dark_source, "FILE:", 5)) {
	darkname = &dark_source[5];
    }
    if (!strcmp (dark_source, "DB:BEST")) {
	darkname = DetrendDatabaseLookup ("best", header);   // these APIs need to be seriously fleshed out!!
    }
    if (!strcmp (dark_source, "DB:CLOSE")) {
	darkname = DetrendDatabaseLookup ("close", header);   // these APIs need to be seriously fleshed out!!
    }
    if (darkname != NULL) {
	dark     = psImageReadSection (NULL, 0, 0, 0, 0, 0, extname, 0, darkname);
    }

    return (dark);
}

bool pmConfigLoadSite (psMetadata **site, int *argc, char **argv) {

    int N;
    char *sitefile;

    if ((N = psArgsGet (argc, argv, "-site"))) {
	if (N == argc - 1) {
	    psError ("syntax error: -site (file)");
	    return (FALSE);
	}
	psArgsRemove (&argc, argv, N);
	sitefile = psStringCopy (argv[N]);
	psArgsRemove (&argc, argv, N);
    }

    if (sitefile == NULL) {
	sitefile = psStringCopy ("site.cnf");
    }
    *site = psMetadataParseConfig (NULL, sitefile, FALSE);  
    return (TRUE);
}

bool pmConfigLoadCamera (psMetadata **camera, psMetadata *site, psMetadata *header) {
  
    int N;
    char *cameraname = NULL;
    char *camerafile = NULL;

    if (site == NULL) return (FALSE);

    // try to determine camera from command-line arguments 
    if ((N = psArgsGet (argc, argv, "-camera"))) {
	if (N == argc - 1) {
	    psError ("syntax error: -camera (file)");
	    return (FALSE);
	}
	psArgsRemove (&argc, argv, N);
	cameraname = psStringCopy (argv[N]);
	psArgsRemove (&argc, argv, N);
    }

    // try to determine camera from header 
    if ((cameraname == NULL) && (header != NULL)) {
	cameraname = pmCameraFromHeader (header);
    }

    // try to determine camera from general configuration information
    if (cameraname == NULL) {
	cameraname = psMetadataLookupPtr (site, "CAMERA");
    }

    // if there is no source for camera info, fail
    if (cameraname == NULL) {
	return (FALSE);
    }

    // find appropriate camera definition file
    camerafile = psMetadataLookup (site, "CAMERA.%s", cameraname);

    // load the camera details for the specific camera
    *camera = psMetadataParseConfig (NULL, camerafile, FALSE);

    return (TRUE);
}

bool pmConfigLoadRecipe (psMetadata **recipe, psMetadata *camera, int *argc, char **argv, char *script) {

    int N;
    char *recipename = NULL;
    char *recipefile = NULL;

    if (camera == NULL) return (FALSE);

    // try to determine camera from command-line arguments 
    if ((N = psArgsGet (argc, argv, "-recipe"))) {
	if (N == argc - 1) {
	    psError ("syntax error: -recipe (file)");
	    return (FALSE);
	}
	psArgsRemove (&argc, argv, N);
	recipefile = psStringCopy (argv[N]);
	psArgsRemove (&argc, argv, N);
    }

    // recipefile is defined in the camera metadata, if not specified on command line
    if (recipefile == NULL) {
	recipename = psAlloc (sizeof(script) + 8);
	sprintf (recipename, "RECIPE.%s", script);

	// option 1: recipe is in the camera definition 
	recipefile = psMetadataLookup (camera, recipename);
	psFree (recipename);

	// option 2: recipe is in the metadata database (equiv to a detrend file)
	// this option requires a different API (header data, etc)
	// recipefile = DetrendDatabaseLookup (recipename, site, camera, header);
    }

    *recipe = psMetadataParseConfig (NULL, recipefile, FALSE);  
    psFree (recipefile);

    return (TRUE);
}
