package com.elphel.imagej.cuas.rt;

import java.awt.Rectangle;
import java.nio.file.Path;

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 final String SUFFIX_FPIXELS_TIFF = "-CUAS-MERGED-CUAS.tiff"; 
	public static final String SUFFIX_LOG = "-LoG";
	public static final String SUFFIX_CONV2D = "-CONV2D";
	public static final String SUFFIX_CONV3D3 = "-CONV3D3";
	public static final String SUFFIX_CONV5D =  "-CONV5D";
	public static final String [] CONV3D3_MODES = {"plain", "winner", "power"};
	public static final int CONV3D3_MODE_PLAIN =  0;
	public static final int CONV3D3_MODE_WINNER = 1;
	public static final int CONV3D3_MODE_POWER =  2;
	public static final int MAXDECIMALS =         4;
	
	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 static String d2s(double d, int maxDecimalsPlaces) {
		String fmt = String.format("%%.%df", maxDecimalsPlaces);
		String s = String.format(fmt, d);
		s = s.trim();
		while (s.endsWith("0")) {
			s = s.substring(0,s.length()-1);
		}
		if (s.endsWith(".")) {
			s = s.substring(0,s.length()-1);
		}
		return s;
	}

	public static String d2s(double d) {
		return d2s(d, MAXDECIMALS);
	}
	
	
	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();
      	
		boolean  curt_save_c5full =  clt_parameters.imp.curt_save_c5full;
		boolean  curt_save_c5rect =  clt_parameters.imp.curt_save_c5rect;
		Rectangle curt_save_select =  clt_parameters.imp.curt_save_select;	
		
// 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"+d2s(cuasRTUtils.getPsfRadius())+"-KR"+d2s(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;
    	double alpha_pyr = 1.0 - clt_parameters.imp.curt_rleak_pyr;
    	int    curt_3d3_mode = clt_parameters.imp.curt_3d3_mode;
    	double curt_3d3_frac = clt_parameters.imp.curt_3d3_frac;
    	double curt_3d3_thrsh = clt_parameters.imp.curt_3d3_thrsh;
    	double curt_3d3_power = clt_parameters.imp.curt_3d3_power;
       	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;

    	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"+d2s(cuasRTUtils.getPsfRadius())+
       				"-KR"+d2s(cuasRTUtils.getKernel2dRadius())+"-ALPHA0_"+d2s(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());
       	}

       	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"+d2s(cuasRTUtils.getPsfRadius())+
   				"-KR"+d2s(cuasRTUtils.getKernel2dRadius())+"-ALPHA0_"+d2s(alpha0)+
   				"-ALPHAPYR_"+d2s(alpha_pyr)+"-M"+curt_3d3_mode+"-F"+d2s(curt_3d3_frac)+"-T"+d2s(curt_3d3_thrsh)+"-P"+d2s(curt_3d3_power)+
   				"-N"+d2s(w3d3_now)+":"+d2s(w3d3_prev)+
   				"-ALPHA1_"+d2s(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,
   				curt_3d3_mode,       // final int       mode_3d3,
   				curt_3d3_frac,       // final double   curt_3d3_frac, //  =        0.5;   // for stronger signals give this fraction of the total (of 9) weight to the 1-of-9 winner
   				curt_3d3_thrsh,      //final double   curt_3d3_thrsh, //  =       1.0;   // scale down curt_3d3_frac for weaker signals (in ratio of min(data[now]/curt_3d3_thrsh,1.0)
   				curt_3d3_power,      //final double    curt_3d3_power, //  = 2.0; // in mode_3d3=2, redistribute coarse velocities weights as the specified power of their positive values (ignore negative ones)
   				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][][][];
   		double [][][][][] dpixels_5d_pyramid = new double [pyramid_levels][][][][];
   		ts_pyramid[0] =          getTimeStamps(1);
   		dpixels_3d3_pyramid[0] = dpixels_3d3;
   		dpixels_pyramid[0] = new double[dpixels_log.length-1][width*height];
		for (int nseq = 1; nseq < dpixels_log.length; nseq++) {
			cuasRTUtils.temporalAverageLReLU(
					dpixels_log[nseq],          // final double [] data_now,
					dpixels_log[nseq-1],        // final double [] data_prev,
					dpixels_pyramid[0][nseq-1], // double []       result_in,
					alpha_pyr);                 // final double    alpha)
		}
//   		System.arraycopy(dpixels_log, 1, dpixels_pyramid[0], 0, dpixels_pyramid[0].length);
   		int center9 = 4;
   		int num_hist_5d = cuasRTUtils.getNumHist();
   		boolean save_5d_pixels = curt_save_c5full; // debugLevel > -4;
   		String title_conv5d = getBaseName()+SUFFIX_CONV5D+"-PSF"+d2s(cuasRTUtils.getPsfRadius())+
   				"-KR"+d2s(cuasRTUtils.getKernel2dRadius())+"-NHIST"+num_hist_5d+
   				"-VELRAD"+curt_vel_radius+"-VELDEC"+curt_vel_decimate;
   		// Level-0 5D convolution (fine velocity from coarse-velocity history)
   		int num_5d_lev0 = Math.max(0, dpixels_3d3.length - num_hist_5d + 1);
   		dpixels_5d_pyramid[0] = new double[num_5d_lev0][][][];
   		String[] ts_5d_lev0 = new String[num_5d_lev0];
   		for (int n5d = 0; n5d < num_5d_lev0; n5d++) {
   			double[][][] window = new double[num_hist_5d][][];
   			for (int h = 0; h < num_hist_5d; h++) {
   				window[h] = dpixels_3d3[n5d + num_hist_5d - 1 - h];
   			}
   			dpixels_5d_pyramid[0][n5d] = cuasRTUtils.convolve3d(window, null);
   			ts_5d_lev0[n5d] = ts_pyramid[0][n5d + num_hist_5d - 1];
   		}
   		if (save_5d_pixels && num_5d_lev0 > 0) {
   			ImagePlus imp_5d = cuasRTUtils.showConvKernel5d(
   					dpixels_5d_pyramid[0],
   					ts_5d_lev0,
   					title_conv5d);
   			QuadCLTCPU.saveImagePlusInDirectory(imp_5d, getModelDirectory());
   		}
   		if (curt_save_c5rect && num_5d_lev0 > 0) {
   			ImagePlus imp_5d_rect = cuasRTUtils.showConvKernel5d(
   					dpixels_5d_pyramid[0],
   					curt_save_select,
   					ts_5d_lev0,
   					title_conv5d+"-RECT");
   			QuadCLTCPU.saveImagePlusInDirectory(imp_5d_rect, getModelDirectory());
   		}
   		for (int nlev = 0; nlev < pyramid_levels; nlev++) {
   	       	if (save_LoG_pixels) {
   	       		String title_conv2d_lev = getBaseName()+SUFFIX_CONV2D+"-PSF"+d2s(cuasRTUtils.getPsfRadius())+
   	       				"-KR"+d2s(cuasRTUtils.getKernel2dRadius())+"-ALPHA0_"+d2s(alpha0)+"-ALPHAPYR_"+d2s(alpha_pyr)+"-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_pyramid[nlev+1] = new double[num_lev_scenes][width*height];
   				
   				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.temporalAverageLReLU(
   							dpixels_pyramid[nlev][2*nseq+2],          // final double [] data_now,
   							dpixels_pyramid[nlev][2*nseq],        // final double [] data_prev,
   							dpixels_pyramid[nlev+1][nseq], // double []       result_in,
   							alpha_pyr);                 // final double    alpha)
   					
   					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,
   			   				curt_3d3_mode,                     // final int       mode_3d3,
   			   				curt_3d3_frac,                     // final double   curt_3d3_frac, //  =        0.5;   // for stronger signals give this fraction of the total (of 9) weight to the 1-of-9 winner
   			   				curt_3d3_thrsh,                    //final double   curt_3d3_thrsh, //  =       1.0;   // scale down curt_3d3_frac for weaker signals (in ratio of min(data[now]/curt_3d3_thrsh,1.0)
   			   				curt_3d3_power,                    //final double    curt_3d3_power, //  = 2.0; // in mode_3d3=2, redistribute coarse velocities weights as the specified power of their positive values (ignore negative ones)
   							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());
   		   		}
   		   		// 5D convolution for pyramid level nlev+1
   		   		int num_5d_lev = Math.max(0, dpixels_3d3_pyramid[nlev+1].length - num_hist_5d + 1);
   		   		dpixels_5d_pyramid[nlev+1] = new double[num_5d_lev][][][];
   		   		String[] ts_5d_lev = new String[num_5d_lev];
   		   		for (int n5d = 0; n5d < num_5d_lev; n5d++) {
   		   			double[][][] window = new double[num_hist_5d][][];
   		   			for (int h = 0; h < num_hist_5d; h++) {
   		   				window[h] = dpixels_3d3_pyramid[nlev+1][n5d + num_hist_5d - 1 - h];
   		   			}
   		   			dpixels_5d_pyramid[nlev+1][n5d] = cuasRTUtils.convolve3d(window, null);
   		   			ts_5d_lev[n5d] = ts_pyramid[nlev+1][n5d + num_hist_5d - 1];
   		   		}
   		   		if (save_5d_pixels && num_5d_lev > 0) {
   		   			ImagePlus imp_5d = cuasRTUtils.showConvKernel5d(
   		   					dpixels_5d_pyramid[nlev+1],
   		   					ts_5d_lev,
   		   					title_conv5d+"-LEV"+(nlev+1));
   		   			QuadCLTCPU.saveImagePlusInDirectory(imp_5d, getModelDirectory());
   		   		}
   		   		if (curt_save_c5rect && num_5d_lev > 0) {
   		   			ImagePlus imp_5d_rect = cuasRTUtils.showConvKernel5d(
   		   					dpixels_5d_pyramid[nlev+1],
   		   					curt_save_select,
   		   					ts_5d_lev,
   		   					title_conv5d+"-LEV"+(nlev+1)+"-RECT");
   		   			QuadCLTCPU.saveImagePlusInDirectory(imp_5d_rect, 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)

*/