package com.elphel.imagej.cuas.rt;

import java.io.File;
import java.nio.file.Path;
import java.util.Arrays;

import com.elphel.imagej.cameras.CLTParameters;
import com.elphel.imagej.cameras.EyesisCorrectionParameters.CorrectionParameters;
import com.elphel.imagej.common.ShowDoubleFloatArrays;
import com.elphel.imagej.cuas.CuasMotion;
import com.elphel.imagej.cuas.CuasRanging;
import com.elphel.imagej.ims.UasLogReader;
import com.elphel.imagej.tileprocessor.QuadCLT;
import com.elphel.imagej.tileprocessor.QuadCLTCPU;

import ij.ImagePlus;
import ij.ImageStack;

public class CuasDetectRT {
	public static String SUFFIX_FPIXELS_TIFF = "-CUAS-MERGED-CUAS.tiff"; 
	public static String SUFFIX_LOG = "-LoG";
	public static String SUFFIX_CONV2D = "-CONV2D";
	public static String SUFFIX_CONV3D3 = "-CONV3D3";
	CuasRanging     cuasRanging;
	UasLogReader    uasLogReader;
	CLTParameters   clt_parameters;
	String          model_directory;
//	String []       model_names;
	QuadCLT         master_CLT;
	String          fpixels_file = null;
	String          base_name = null; // add suffix and 
	float [][]      fpixels; 
	double [][]     dpixels; // same as fpixels for faster calculations on 64-bit processors 
	int             width;
	int             height;
	String []       time_stamps;
	double          infinity;
	
	public CuasDetectRT(
			CLTParameters     clt_parameters,
			UasLogReader      uasLogReader,
			String            model_directory,
			int               debugLevel) {
		this.uasLogReader =   uasLogReader;
		this.clt_parameters = clt_parameters;
		this.model_directory = model_directory;
		this.infinity = clt_parameters.imp.cuas_infinity;
		String [] fpixels_paths = CorrectionParameters.getFilesByExtensionAsArray(model_directory, SUFFIX_FPIXELS_TIFF);
		if (fpixels_paths.length > 0) {
			if (fpixels_paths.length > 1) {
				System.out.println((fpixels_paths.length)+" files ending with \""+SUFFIX_FPIXELS_TIFF+"\" found in "+model_directory+", will use the first one.");
			}
			fpixels_file = fpixels_paths[0];
			ImagePlus imp = new ImagePlus(fpixels_file);
			width =  imp.getWidth();
			height = imp.getHeight();
			if (width > 0) {
					ImageStack stack = imp.getStack();
					// Skip non-ts ("average") slices;
					int first_slice = 1;
					int num_slices = stack.getSize();
					for (; !Character.isDigit(stack.getSliceLabel(first_slice).charAt(0)); first_slice++);
					time_stamps = new String [num_slices - first_slice + 1];
					fpixels = new float [time_stamps.length][width*height];
					for (int i = 0; i < time_stamps.length; i++) {
						time_stamps[i] = stack.getSliceLabel(first_slice + i);
						fpixels[i] = (float[]) stack.getPixels (first_slice + i);
					}
					String fpixels_name = Path.of(fpixels_file).getFileName().toString();
					base_name = fpixels_name.substring(0,fpixels_name.length() - SUFFIX_FPIXELS_TIFF.length());
			} else {
				System.out.println("Failed to read image data from "+fpixels_file);
				fpixels_file = null;
				fpixels = null;
			}
		} else {
			System.out.println("No files ending with \""+SUFFIX_FPIXELS_TIFF+"\" found in "+model_directory);
			fpixels_file = null;
			fpixels = null;
		}
		if (fpixels != null) {
			dpixels = new double [fpixels.length][width*height];
			for (int i = 0; i < fpixels.length; i++) for (int pix = 0; pix < fpixels[i].length; pix++) {
				dpixels[i][pix] = fpixels[i][pix]; 	
			}
		} else {
			dpixels = null;
		}
		return;
	}
	
	public int getWidth() {
		return width;
	}
	public int getHeight() {
		return height;
	}
	String [] getTimeStamps() {
		return time_stamps;
	}

	String [] getTimeStamps(int from_indx) {
		String [] time_stamps_from = new String[time_stamps.length - from_indx];
		System.arraycopy(time_stamps, from_indx, time_stamps_from, 0, time_stamps_from.length);
		return time_stamps_from;
	}
	String [] getTimeStamps(int from_indx, int tdecimate) {
		String [] time_stamps_decimated = new String[time_stamps.length / tdecimate];
		for (int i = 0; i < time_stamps_decimated.length; i++) {
			time_stamps_decimated[i] = time_stamps[(i+1)* tdecimate - 1];
		}
		return time_stamps_decimated;
	}

	
	
	public double [][] getDPpixels(){
		return dpixels;
	}
	
	public float [][] getFPixels() {
		return fpixels;
	}
	
	public String getBaseName() {
		return base_name;
	}
	
	public String getModelDirectory() {
		return model_directory;
	}
	
	public CuasMotion detectTargets(
			CLTParameters     clt_parameters,
			boolean           batch_mode,
			int               debugLevel) {
		boolean  save_linear_cuas =  true;
		int      curt_pyramid =      clt_parameters.imp.curt_pyramid;
		double   curt_psf_radius =   clt_parameters.imp.curt_psf_radius;
		double   curt_n_sigma =      clt_parameters.imp.curt_n_sigma;
		double   curt_rleak0 =       clt_parameters.imp.curt_rleak0;
		int      curt_pix_decimate = clt_parameters.imp.curt_pix_decimate;
		int      curt_vel_decimate = clt_parameters.imp.curt_vel_decimate;
		int      curt_vel_radius =   clt_parameters.imp.curt_vel_radius;
		double[] curt_temp_weights = clt_parameters.imp.curt_temp_weights.clone();
      	
// Initialize LoG and other kernel parameters, calculate LoG first, show results
       	int      kernel3d3_rad = 1;
       	
       	CuasRTUtils cuasRTUtils = new CuasRTUtils (
    			curt_psf_radius,          // double   psf_radius,
				curt_n_sigma,             // double   n_sigma,
    			kernel3d3_rad,            // int      kernel3d3_rad,
    			curt_temp_weights,        // double[] temporal_weights,
                curt_pix_decimate,        // int      pixel_decimate,
                curt_vel_decimate,        // int      velocity_decimate,
                curt_vel_radius,          // int      velocity_radius,
    			getWidth (),  // int      width,
    			getHeight()); // int      height)
       	// For testing, create whole arrays?
       	boolean save_LoG =        debugLevel> -4;
       	boolean save_LoG_pixels = debugLevel> -4;
       	boolean save_3d3_pixels = debugLevel> -4;
       	
       	if (save_LoG) {
       		String title_log = getBaseName()+SUFFIX_LOG+"-PSF"+cuasRTUtils.getPsfRadius()+"-KR"+cuasRTUtils.getKernel2dRadius();
       		ImagePlus imp_log = cuasRTUtils.showKernel2d(title_log);
       		QuadCLTCPU.saveImagePlusInDirectory(imp_log,getModelDirectory());
       	}
       	double [][] dpixels = getDPpixels();
       	double [][] dpixels_log = new double [dpixels.length][dpixels[0].length];
    	double []   kernel2d = cuasRTUtils.getKernel2d();
    	double alpha0 = 1.0 - clt_parameters.imp.curt_rleak0;
       	for (int nseq = 0; nseq < dpixels_log.length; nseq++) {
       		cuasRTUtils.convolve2DLReLU(
       				dpixels[nseq],     // final double [] data,
       				kernel2d,          // final double [] kernel, // square
       				dpixels_log[nseq], // double []       result_in,
       				alpha0);            // final double    alpha)
       	}
       	if (save_LoG_pixels) {
       		String title_conv2d = getBaseName()+SUFFIX_CONV2D+"-PSF"+cuasRTUtils.getPsfRadius()+
       				"-KR"+cuasRTUtils.getKernel2dRadius()+"-ALPHA0_"+alpha0;
       		
			ImagePlus imp_conv2d = ShowDoubleFloatArrays.makeArrays(
					dpixels_log, // float[][] pixels,
					getWidth(),  // int width,
					getHeight(), // int height,
					title_conv2d,					
					getTimeStamps()); //String [] titles)
			imp_conv2d.setDisplayRange(CuasRTUtils.IMP_MINMAX[0],CuasRTUtils.IMP_MINMAX[1]);

       		QuadCLTCPU.saveImagePlusInDirectory(imp_conv2d, getModelDirectory());
       	}

       	double w3d3_now =  clt_parameters.imp.curt_3d3_wnow;
       	double w3d3_prev = clt_parameters.imp.curt_3d3_wprev;
    	double alpha1 = 1.0 - clt_parameters.imp.curt_rleak1;
       	int    dim3d3 = (2*cuasRTUtils.get3d3Radius()+1);
//       	int    size3x3 = dim3d3 * dim3d3;
       	double [][][] dpixels_3d3 = new double [dpixels.length-1][width*height][dim3d3 * dim3d3];
   		String title_conv3d3 = getBaseName()+SUFFIX_CONV3D3+"-PSF"+cuasRTUtils.getPsfRadius()+
   				"-KR"+cuasRTUtils.getKernel2dRadius()+"-ALPHA0_"+alpha0+"+N"+w3d3_now+":"+w3d3_prev+"-ALPHA1_"+alpha1;
   		// getTimeStamps(int from_indx)
   		int dbg_seq = 480;
       	for (int nseq = 1; nseq < dpixels_log.length; nseq++) {
       		int dbg_lev = (nseq == dbg_seq) ? 1 : -10;
       		cuasRTUtils.convolve3D3LReLU(
   				dpixels_log[nseq],   // final double [] data,
   				dpixels_log[nseq-1], // final double [] data_prev,
   				dpixels_3d3[nseq-1], // double [][]     result_in,
   				w3d3_now,            // final double    w_now, // normally 0.5, so center will propagate to the next pyramid levels
   				w3d3_prev,           // final double    w_prev,// normally 0.5, so center will propagate to the next pyramid levels
   				alpha1,              // final double    alpha) {
   				dbg_lev); // debugLevel);         // final int       debugLevel)
   		}
   		if (save_3d3_pixels) {
   			ImagePlus imp_3d3 = cuasRTUtils.render3d3Hyperstack(
   					dpixels_3d3,      // double [][][] coarse_vel_seq, // [nser][npix][nvel]
   					getTimeStamps(1), // String []     scene_names,
   					title_conv3d3);   // String        title)
       		QuadCLTCPU.saveImagePlusInDirectory(imp_3d3, getModelDirectory());
   		}
       	
       	// building pyramid
   		int pyramid_levels = 	clt_parameters.imp.curt_pyramid;
   		
   		double [][][]   dpixels_pyramid =     new double [pyramid_levels][][];
   		String [][]     ts_pyramid =          new String [pyramid_levels][];
   		double [][][][] dpixels_3d3_pyramid = new double [pyramid_levels][][][];
   		ts_pyramid[0] =          getTimeStamps(1);
   		dpixels_3d3_pyramid[0] = dpixels_3d3;
   		int center9 = 4;
   		for (int nlev = 0; nlev < pyramid_levels; nlev++) {
   			// copy centers
   			dpixels_pyramid[nlev] = new double [dpixels_3d3_pyramid[nlev].length][width*height];
   			for (int nseq = 0; nseq < dpixels_pyramid[nlev].length; nseq++) {
   				for (int npix = 0; npix <  dpixels_pyramid[nlev][nseq].length; npix++) {
   					dpixels_pyramid[nlev][nseq][npix] = dpixels_3d3_pyramid[nlev][nseq][npix][center9]; 
   				}
   			}
   	       	if (save_LoG_pixels) {
   	       		String title_conv2d_lev = getBaseName()+SUFFIX_CONV2D+"-PSF"+cuasRTUtils.getPsfRadius()+
   	       				"-KR"+cuasRTUtils.getKernel2dRadius()+"-ALPHA0_"+alpha0+"-LEV"+nlev;
   	       		
   				ImagePlus imp_conv2d = ShowDoubleFloatArrays.makeArrays(
   						dpixels_pyramid[nlev], // float[][] pixels,
   						getWidth(),  // int width,
   						getHeight(), // int height,
   						title_conv2d_lev,					
   						ts_pyramid[nlev]); //String [] titles)
   				imp_conv2d.setDisplayRange(CuasRTUtils.IMP_MINMAX[0],CuasRTUtils.IMP_MINMAX[1]);
   	       		QuadCLTCPU.saveImagePlusInDirectory(imp_conv2d, getModelDirectory());
   	       	}
   			
   			// run next pyramid level (use half of the temporal slices)
   			if (nlev < (pyramid_levels -1)) {
   				int num_lev_scenes = (dpixels_pyramid[nlev].length)/2 -1;
   				dpixels_3d3_pyramid[nlev+1] = new double [num_lev_scenes][width*height][dim3d3 * dim3d3];
   				ts_pyramid[nlev+1] = new String[num_lev_scenes];
   				for (int nseq = 0; nseq < num_lev_scenes; nseq++) {
   					ts_pyramid[nlev+1][nseq] = ts_pyramid[nlev][2*(nseq + 1)];
   					cuasRTUtils.convolve3D3LReLU(
   							dpixels_pyramid[nlev][2*nseq+2],   // final double [] data,
   							dpixels_pyramid[nlev][2*nseq],     // final double [] data_prev,
   							dpixels_3d3_pyramid[nlev+1][nseq], // double [][]     result_in,
   							w3d3_now,                          // final double    w_now, // normally 0.5, so center will propagate to the next pyramid levels
   							w3d3_prev,                         // final double    w_prev,// normally 0.5, so center will propagate to the next pyramid levels
   			   				alpha1,              // final double    alpha) {
   			   				debugLevel);         // final int       debugLevel)
   				}
   		   		if (save_3d3_pixels) {
   		   			ImagePlus imp_3d3 = cuasRTUtils.render3d3Hyperstack(
   		   					dpixels_3d3_pyramid[nlev+1],      // double [][][] coarse_vel_seq, // [nser][npix][nvel]
   		   					ts_pyramid[nlev+1], // String []     scene_names,
   		   					title_conv3d3+"-LEV"+(nlev+1));   // String        title)
   		   			QuadCLTCPU.saveImagePlusInDirectory(imp_3d3, getModelDirectory());
   		   		}
   			}
   		}
   		return null;
   		/*
		CuasMotion cuasMotion = new CuasMotion (
				clt_parameters,              // CLTParameters     clt_parameters,
				getTimeStamps(),                // String []         scene_titles,
				cuasRanging.getCenter_CLT(), // QuadCLT           parentCLT,
				null, // fpixels_avg,                 // float []          fpixels_avg,
				uasLogReader,                // UasLogReader      uasLogReader,
				debugLevel);                 // int               debugLevel)
		return cuasMotion;
		*/
	}
}
/*
					uasLogReader,   // UasLogReader    uasLogReader,
batch_mode);    // boolean         batch_mode)

*/