/**
 **
 ** TpTask.java - GPU tile processing task descriptor
 **
 ** Copyright (C) 2021-2025 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  TpTask.java is free software: you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation, either version 3 of the License, or
 **  (at your option) any later version.
 **
 **  This program is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ** -----------------------------------------------------------------------------**
 **
 */
package com.elphel.imagej.gpu;

import java.util.Arrays;

import com.elphel.imagej.common.ShowDoubleFloatArrays;

/**
 * Host-side image of the GPU per-tile task descriptor — the bridge between Java and the
 * CUDA Tile Processor. Mirrors {@code struct tp_task} (tile_processor_gpu/src/geometry_correction.h)
 * and is marshalled to/from the GPU as a flat float array (see {@link #asFloatArray} and the
 * float-array constructor below; ints are bit-cast into floats, layout must match get_task_size()).
 * <p>
 * A TpTask array IS the GPU work list: Java decides WHICH 16x16 (stride 8) tiles to process and
 * with WHAT geometry; kernels (convert_direct, correlate2D, textures) consume it on the GPU.
 * <p>
 * The central payload is the per-sensor pixel offset {@code xy[num_sensors][2]} implementing
 * "differential rectification": no image warping — each sensor is corrected only by its
 * DIFFERENCE from a common ideal average camera (pure radial distortion, average focal length).
 * That difference is a per-tile space-variant deconvolution split into a center-symmetrical PSF
 * kernel plus a pure 2D shift. The full real-valued shift is the SUM of: (1) factory-calibration
 * kernel center offset, (2) sensor misalignment developed after calibration, (3) target
 * (expected) disparity, (4) camera pose relative to the reference pose being compensated to.
 * convert_direct splits that sum into the nearest-integer part (16x16 tile selection from the
 * source image) and a +/-0.5 pix fractional part applied losslessly in the transform domain by
 * the phase rotators (shiftTileHor/shiftTileVert) — resampling loss occurs only on the final
 * return to pixel domain.
 * <p>
 * Who fills what: historically Java computed xy/disp_dist per tile per sensor on the host
 * (GeometryCorrection.getPortsCoordinates()) and the GPU just used them. Currently Java provides
 * only txy + target_disparity (+ task bits, centerXY, scale) and the GPU computes the per-sensor
 * offsets itself (geometry_correction.cu: calc_rot_deriv() from the relative pose, then
 * calculate_tiles_offsets() filling xy and disp_dist in the GPU-resident task array);
 * GpuQuad.updateTasks() is the D2H readback pulling those GPU-computed values back into these
 * objects. Planned next: per-tile disparity also becomes GPU-resident (slowly-updated map).
 * <p>
 * {@code disp_dist[sensor][4]} is the per-sensor 2x2 Jacobian d(pixel x,y)/d(disparity,
 * non-disparity) from the same geometry chain; consumed by Corr2dLMA.setMatrices()/
 * getPairsOffsets() (expected correlation-peak offsets in the disparity LMA) and by the
 * lazy-eye/ERS accumulation (MultisceneLY, e.g. disp_dist[cam][2] = dy/d(disp)).
 * <p>
 * (Documentation by Claude on 07/03/2026, from Andrey's explanation.)
 */
public class TpTask {
	public int        task;
	// task bits 0..7 - texture neighbors (0 - N, 1 - NE, ..., 7 - NW)
	// bit  8 (GPUTileProcessor.TASK_TEXT_EN) - enable texture generation
	// bit  9 (GPUTileProcessor.TASK_CORR_EN) - enable intrascene correlations
	// bit 10 (GPUTileProcessor.TASK_INTER_EN) - enable interscene correlations
	// Old (still not updated for CPU): [0](+1) - generate 4 images, [4..9]+16..+512 - correlation pairs, 2 - generate texture tiles
	public float      target_disparity;
    public int        num_sensors = 4;
	public int        ty;
	public int        tx;
	public float []   centerXY = {Float.NaN,Float.NaN};
	public float[][]  xy = null;
	public float[][]  xy_aux = null;
	public float [][] disp_dist = null;
//	public float      weight;
	public float      scale = 0.0f; // for motion blur correction
	// 0.0 - set (as it was). >0 multiply and set. <0 multiply and accumulate
	public static int getSize(int num_sensors) {
//		return 5 + 2* num_sensors + 4 * num_sensors;
		return 6 + 2* num_sensors + 4 * num_sensors; // added scale
	}
	public int getSize() {
//		return 5 + 2* num_sensors + 4 * num_sensors;
		return getSize(num_sensors);
	}
	
	public void setScale(float scale) {
		this.scale = scale;
	}
	
	public void setScale(double scale) {
		this.scale = (float) scale;
	}

	public void setScale(double scale, boolean accumulate) {
		if (accumulate && (scale >=0)) {
			if (scale == 0.0) {
				scale = 1.0;
			}
			scale += GpuQuad.SCALE_ACCUM;
		}
		this.scale = (float) scale;
		return;
	}

	
	public float getScale() { // never used?
		if (scale > GpuQuad.SCALE_ACCUM) {
			return (float) (scale - GpuQuad.SCALE_ACCUM);
		}
		return scale;
	}
	
	public TpTask(
			int num_sensors,
			int tileX,
			int tileY) {
		this.num_sensors = num_sensors;
		this.ty = tileY;
		this.tx = tileX;
	}

	public TpTask(int num_sensors, int tx, int ty, float target_disparity, int task ) {
		this.tx = tx;
		this.ty = ty;
		this.target_disparity = target_disparity;
		this.task = task;
		this.num_sensors = num_sensors; // will not be encoded
		this.disp_dist = new float [num_sensors][4];
	}
	/**
	 * Initialize from the float array (read from the GPU)
	 * @param num_sensors number of sensors in an array
	 * @param flt float array containing tasks data
	 * @param indx task number to use
	 * @param use_aux (always false now)
	 */
	public TpTask(int num_sensors, float [] flt, int task_indx, boolean use_aux)
	{
		this.num_sensors = num_sensors; // will not be encoded
		int indx = task_indx * getSize(num_sensors);
		task =    Float.floatToIntBits(flt[indx++]); // 0
		int txy = Float.floatToIntBits(flt[indx++]); // 1
		ty = txy >> 16;
		tx = txy & 0xffff;
		target_disparity = flt[indx++];              // 2
		centerXY[0] = flt[indx++];                   // 3
		centerXY[1] = flt[indx++];                   // 4
		scale = flt[indx++];                         // 5
		if (use_aux) {
			xy_aux = new float[num_sensors][2];
    		for (int i = 0; i < num_sensors; i++) {
    			xy_aux[i][0] = flt[indx++];
    			xy_aux[i][1] = flt[indx++];
    		}
		} else {
			xy = new float[num_sensors][2];
    		for (int i = 0; i < num_sensors; i++) {
    			xy[i][0] = flt[indx++];
    			xy[i][1] = flt[indx++];
    		}
		}
		disp_dist = new float [num_sensors][4];
		for (int i = 0; i < num_sensors; i++) {
			for (int j = 0; j < 4; j++) {
				disp_dist[i][j] = flt[indx++];
			}
		}
	}
	
	public void setTextureEnable(boolean en) {
		if (en) task |=  (1 << GPUTileProcessor.TASK_TEXT_EN);
		else 	task &= ~(1 << GPUTileProcessor.TASK_TEXT_EN);
	}
	
	public boolean getTextureEnable() {
		return (task & (1 << GPUTileProcessor.TASK_TEXT_EN)) != 0;
	}

	public void setIntraCorrelationEnable(boolean en) {
		if (en) task |=  (1 << GPUTileProcessor.TASK_CORR_EN);
		else 	task &= ~(1 << GPUTileProcessor.TASK_CORR_EN);
	}
	
	public boolean getIntraCorrelationEnable() {
		return (task & (1 << GPUTileProcessor.TASK_CORR_EN)) != 0;
	}
	
	public void setInterCorrelationEnable(boolean en) {
		if (en) task |=  (1 << GPUTileProcessor.TASK_INTER_EN);
		else 	task &= ~(1 << GPUTileProcessor.TASK_INTER_EN);
	}
	
	public boolean getInterCorrelationEnable() {
		return (task & (1 << GPUTileProcessor.TASK_INTER_EN)) != 0;
	}
	
	
	
	public float [][] getDispDist(){
		return disp_dist;
	}
	public void setDispDist(float [][] disp_dist){
		this.disp_dist = disp_dist;
	}
	public void setDispDist(double [][] disp_dist){
		this.disp_dist = new float [disp_dist.length][disp_dist[0].length];
		for (int i = 0; i < disp_dist.length; i++) {
			for (int j = 0; j < disp_dist[0].length; j++) {
				this.disp_dist[i][j] = (float) disp_dist[i][j];		
			}
		}
	}

	public double [][] getDoubleDispDist(){
		if (disp_dist == null) { // can it happen?
			return null;
		}
		double [][] ddisp_dist = new double [disp_dist.length][disp_dist[0].length];
		for (int nsens = 0; nsens < disp_dist.length; nsens++) {
			for (int i = 0; i < disp_dist[nsens].length; i++) {
				ddisp_dist[nsens][i] = disp_dist[nsens][i]; 
			}
		}
		return ddisp_dist;
	}

	public float [][] getXY(){
		return  xy;
	}
	public double [][] getDoubleXY(){
		float [][] fXY = getXY();
		if (fXY == null) {
			return null;
		}
		double [][] dXY = new double [fXY.length][fXY[0].length];
		for (int nsens = 0; nsens < fXY.length; nsens++) {
			for (int i = 0; i < fXY[nsens].length; i++) {
				dXY[nsens][i] = fXY[nsens][i]; 
			}
		}
		return dXY;
	}
	
	public int getTileY(){
		return ty;
	}
	public int getTileX(){
		return tx;
	}
	public int [] getTileXY(){
		return new int [] {tx,ty};
	}
	
	public int getTask() {
		return task;
	}
	public double getTargetDisparity() {
		return target_disparity;
	}

	public void updateTargetDisparity(float td) {
		target_disparity = td;
	}
	
	
	public float [] getCenterXY() {
		return centerXY;
	}

	public double [] getDoubleCenterXY() {
		return new double [] {centerXY[0],centerXY[1]};
	}
	
	public void setCenterXY(double [] centerXY) {
		this.centerXY = new float [] {(float) centerXY[0],(float) centerXY[1]};
	}


	// convert this class instance to float array to match layout of the C struct
	public float [] asFloatArray(boolean use_aux) {
		
		float [] flt = new float [getSize()];
		return asFloatArray(flt, 0, use_aux);
	}
	// convert this class instance to float array to match layout of the C struct,
	// fill existing float array from the specified index
	public float [] asFloatArray(float [] flt, int 	task_indx, boolean use_aux) {
		int indx = task_indx * getSize(num_sensors);
		flt[indx++] = Float.intBitsToFloat(task);            // 0
		flt[indx++] = Float.intBitsToFloat(tx + (ty << 16)); // 1
		flt[indx++] = this.target_disparity;                 // 2
		flt[indx++] = centerXY[0];                           // 3
		flt[indx++] = centerXY[1];                           // 4
		flt[indx++] = scale;                                 // 5
		float [][] offsets = use_aux? this.xy_aux: this.xy;
		for (int i = 0; i < num_sensors; i++) {
			if (offsets != null) {
				flt[indx++] = offsets[i][0];
				flt[indx++] = offsets[i][1];
			} else {
				indx+= 2;
			}
		}
		return flt;
	}
	
	public static void showTpTask(
			TpTask[] tp_tasks,
			int      tilesX,
			int      tilesY,
			String   title) {
		int numSensors = tp_tasks[0].num_sensors;
		String [] titles0 = {"X-cent", "Y-cent", "Disp","Scale"};
		String [] titles = new String[titles0.length+2*numSensors];
		for (int i = 0; i < titles0.length; i++) {
			titles[i] = titles0[i];
		}
		for (int i = 0; i < numSensors; i++) {
			titles[titles0.length + 2*i + 0] = "X-"+i;
			titles[titles0.length + 2*i + 1] = "Y-"+i;
		}
		double [][] data = new double [titles.length][tilesX*tilesY];
		for (int i = 0; i < data.length; i++) {
			Arrays.fill(data[i], Double.NaN);
		}
		for (int nTask = 0; nTask < tp_tasks.length; nTask++) {
			TpTask task = tp_tasks[nTask];
			int nTile = task.ty*tilesX+task.tx;
			data [0][nTile] = task.centerXY[0];
			data [1][nTile] = task.centerXY[1];
			data [2][nTile] = task.target_disparity;
			data [3][nTile] = task.scale;
			for (int i = 0; i < numSensors; i++) {
				data [titles0.length + 2*i + 0][nTile] = task.xy[i][0];
				data [titles0.length + 2*i + 1][nTile] = task.xy[i][1];
			}
		}
		ShowDoubleFloatArrays.showArrays(
				data,
				tilesX,
				tilesY,
				true,
				title,
				titles);
	}
	
	
	
}