/**
 **
 ** CuasRender.java - dedicated CUAS RT renderer: conditioned raw-jp4 scenes
 ** rendered on the virtual-camera uniform grid at borrowed poses.
 **
 ** Copyright (C) 2026 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  CuasRender.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.cuas.rt;

import java.util.ArrayList;

import com.elphel.imagej.cameras.CLTParameters;
import com.elphel.imagej.cuas.CuasMotion;
import com.elphel.imagej.gpu.GpuQuad;
import com.elphel.imagej.gpu.TpTask;
import com.elphel.imagej.tileprocessor.ErsCorrection;
import com.elphel.imagej.tileprocessor.ImageDtt;
import com.elphel.imagej.tileprocessor.OpticalFlow;
import com.elphel.imagej.tileprocessor.QuadCLT;
import com.elphel.imagej.tileprocessor.TwoQuadCLT;

import ij.ImagePlus;
import ij.ImageStack;

/**
 * The dedicated CUAS RT renderer (design ruling 2026-07-05, spec in
 * imagej-elphel-internal handoffs/2026-07-05_cuas_rt_dataflow_and_grids.md par.6):
 * replaces the legacy multi-purpose renderSceneSequence() for CUAS RT.
 *  - Grid: ALWAYS the virtual-camera uniform grid (no transformFromVirtual /
 *    transformCameraVew / conditionInitialDS in this path).
 *  - Disparity: the raw center DSI (the -INTER-INTRA-LMA vintage, same source as
 *    reliable_tiles) - ONE disparity source for all of RT.
 *  - Pixels: raw /jp4/*.tiff + CuasConditioning (conditionSceneToGpu) - never
 *    QuadCLT image products.
 *  - QuadCLT instances: BORROW-only (poses + ERS rates from scenes_poses, geometry,
 *    tile dims). Any new implied QuadCLT dependency must be raised with Andrey.
 *  - All methods STATIC.
 *
 * First delivered piece: the MINIMAL ingest+render sequence test (testRenderSequence)
 * that certifies conditionSceneToGpu + the virtual-grid render chain by generating
 * per-sensor + merged per-scene stacks directly comparable to the oracle
 * -CUAS-INDIVIDUAL-CUAS-DBG / -CUAS-MERGED-CUAS-DBG products. Today's lesson: this
 * step was jumped over (conditionSceneToGpu had a single caller inside the pose test
 * and was never image-certified standalone) - test and debug HERE first, only then
 * return to the pose test.
 *
 * By Claude on 07/05/2026, from Andrey's design.
 */
public class CuasRender {
	public static final double [] ZERO3 = {0.0, 0.0, 0.0};

	/**
	 * Render ONE scene (already ingested to the GPU by CuasConditioning.conditionSceneToGpu)
	 * on the virtual-camera uniform grid at the given (borrowed or fitted) pose.
	 * No correlation - tasks + convert (erased CLT: NaN outside task tiles) + imclt.
	 * @param clt_parameters processing parameters (LPF sigmas, margin)
	 * @param image_dtt      ImageDtt bound to the center GPU (created once per sequence)
	 * @param center_CLT     virtual center (grid dims, ErsCorrection reference side)
	 * @param scene          scene instance (BORROW: geometry + its ErsCorrection with
	 *                       rates already set; image data must already be on the GPU)
	 * @param center_disparity raw center DSI disparity (virtual grid)
	 * @param scene_xyz      scene position (ZERO3 for CUAS rotation-only)
	 * @param scene_atr      scene orientation (borrowed/fitted)
	 * @param margin         tile margin
	 * @param debugLevel     debug level
	 * @return [17][width*height]: renders of the 16 sensors (grid-transformed to the
	 *         virtual grid) + [16] = the consolidated (NaN-aware weighted average)
	 *         merged render; null if no valid tasks
	 */
	public static float [][] renderSceneVirtual(
			final CLTParameters clt_parameters,
			final ImageDtt      image_dtt,
			final QuadCLT       center_CLT,
			final QuadCLT       scene,
			final double []     center_disparity,
			final double []     scene_xyz,
			final double []     scene_atr,
			final int           margin,
			final int           debugLevel) {
		final GpuQuad gpuQuad = center_CLT.getGPUQuad();
		// 1. virtual uniform grid -> scene grid at the pose (ERS per the instances' rates)
		final double [][] scene_pXpYD = OpticalFlow.transformToScenePxPyD(
				null, center_disparity, scene_xyz, scene_atr, scene, center_CLT);
		final double disparity_corr = clt_parameters.imp.disparity_corr + center_CLT.getDispInfinityRef();
		final TpTask [] tp_tasks = GpuQuad.setInterTasks(
				scene.getNumSensors(),
				scene.getGeometryCorrection().getSensorWH()[0],
				false,                          // GPU calculates port coordinates
				scene_pXpYD,                    // per-tile pX,pY,disparity
				null,                           // selection: ALL tiles with valid disparity
				scene.getGeometryCorrection(),
				disparity_corr,
				margin,
				null,                           // valid_tiles
				ImageDtt.THREADS_MAX);
		if ((tp_tasks == null) || (tp_tasks.length == 0)) {
			System.out.println("renderSceneVirtual(): no tasks for scene "+scene.getImageName());
			return null;
		}
		// 2. LPFs + tasks + offsets + convert (mask=0: no correlation)
		image_dtt.interCorrTD(
				clt_parameters.img_dtt,
				tp_tasks,
				null,
				clt_parameters.gpu_sigma_r,
				clt_parameters.gpu_sigma_b,
				clt_parameters.gpu_sigma_g,
				clt_parameters.gpu_sigma_m,
				scene.isMonochrome() ? 1.0 : clt_parameters.gpu_sigma_rb_corr,
				clt_parameters.getGpuCorrSigma(scene.isMonochrome()),
				clt_parameters.getGpuCorrLoGSigma(scene.isMonochrome()),
				clt_parameters.corr_red,
				clt_parameters.corr_blue,
				0,                              // sensor_mask_inter = 0: convert only
				ImageDtt.THREADS_MAX,
				debugLevel);
		// re-convert with erase_clt=1: NaN outside task tiles (interCorrTD converts with
		// erase=-1, leaving the previous scene's tiles as ghosts in a render)
		gpuQuad.execConvertDirect(false, null, 1);
		// 3. per-sensor renders (virtual grid)
		final float [][] per_sensor = CuasMotion.perSensorImagesFromTD(gpuQuad, false);
		// 4. merged = the NaN-aware weighted average consolidated in TD, rendered via slot 0
		final float [][] fclt16 = gpuQuad.getCltData(false);
		final float [] avg_td = CuasTD.consolidateSensorsTD(fclt16, null);
		gpuQuad.setCltData(0, avg_td, false);
		final float [] merged = CuasMotion.perSensorImagesFromTD(gpuQuad, false)[0];
		final float [][] rslt = new float [per_sensor.length + 1][];
		System.arraycopy(per_sensor, 0, rslt, 0, per_sensor.length);
		rslt[per_sensor.length] = merged;
		return rslt;
	}

	/**
	 * MINIMAL standalone test of the RT ingest+render chain (runs INSTEAD of detection
	 * and INSTEAD of the pose test): for every scene, ingest raw /jp4/ via
	 * CuasConditioning.conditionSceneToGpu, borrow the STORED pose + STORED ERS rates
	 * (exactly the sources the oracle DBG renders use), render on the virtual uniform
	 * grid, and save one hyperstack:
	 *   <center>-CUAS-RT-RENDER.tiff  [t: s00..s15, merged][z: scenes]
	 * directly comparable (slice-by-slice, same borrowed poses) to the oracle
	 * -CUAS-INDIVIDUAL-CUAS-DBG / -CUAS-MERGED-CUAS-DBG debugging products.
	 * By Claude on 07/05/2026, from Andrey's plan: test and debug THIS first,
	 * only then return to the pose test.
	 */
	public static void testRenderSequence(
			final CLTParameters clt_parameters,
			final QuadCLT       center_CLT,
			final QuadCLT []    quadCLTs,
			final int           debugLevel) {
		final int margin = clt_parameters.imp.margin;
		final boolean use_lma_dsi = clt_parameters.imp.use_lma_dsi;
		final boolean is_aux = center_CLT.isAux();
		final double [][] center_dsi = center_CLT.dsi;
		final double [] disparity =     center_dsi[is_aux ? TwoQuadCLT.DSI_DISPARITY_AUX     : TwoQuadCLT.DSI_DISPARITY_MAIN];
		final double [] disparity_lma = center_dsi[is_aux ? TwoQuadCLT.DSI_DISPARITY_AUX_LMA : TwoQuadCLT.DSI_DISPARITY_MAIN_LMA];
		final double [] center_disparity = (use_lma_dsi && (disparity_lma != null)) ? disparity_lma : disparity;
		final ErsCorrection ers_center = center_CLT.getErsCorrection();
		final ImageDtt image_dtt = new ImageDtt(
				center_CLT.getNumSensors(),
				clt_parameters.transform_size,
				clt_parameters.img_dtt,
				center_CLT.isAux(),
				center_CLT.isMonochrome(),
				center_CLT.isLwir(),
				clt_parameters.getScaleStrength(center_CLT.isAux()),
				center_CLT.getGPU());
		final int img_width =  center_CLT.getGPUQuad().getImageWidth();
		final int img_height = center_CLT.getGPUQuad().getImageHeight();
		final int num_sens =   center_CLT.getNumSensors();
		final int num_comps =  num_sens + 1; // 16 sensors + merged
		final ArrayList<float [][]> rendered = new ArrayList<float [][]>();
		final ArrayList<String>     ts_names = new ArrayList<String>();
		final float [] nan_slice = new float [img_width * img_height];
		java.util.Arrays.fill(nan_slice, Float.NaN);
		int num_ok = 0, num_fail = 0;
		for (int nscene = 0; nscene < quadCLTs.length; nscene++) {
			if (quadCLTs[nscene] == null) continue;
			final String ts_name = quadCLTs[nscene].getImageName();
			// BORROWED pose + BORROWED stored ERS rates - the same sources the oracle
			// DBG renders use (renderSceneSequence: getSceneXYZ/ATR + getSceneErsXYZ_dt/ATR_dt)
			final double [][] pose = ers_center.getSceneXYZATR(ts_name);
			final double [] ers_xyz_dt = ers_center.getSceneErsXYZ_dt(ts_name);
			final double [] ers_atr_dt = ers_center.getSceneErsATR_dt(ts_name);
			float [][] rslt = null;
			if (pose == null) {
				System.out.println("testRenderSequence(): scene "+nscene+" ("+ts_name+") has no stored pose - skipping");
			} else {
				if ((ers_xyz_dt != null) && (ers_atr_dt != null)) {
					quadCLTs[nscene].getErsCorrection().setErsDt(ers_xyz_dt, ers_atr_dt);
				} else {
					quadCLTs[nscene].getErsCorrection().setErsDt(ZERO3.clone(), ZERO3.clone());
					System.out.println("testRenderSequence(): scene "+nscene+" ("+ts_name+") has no stored ERS rates - zeros");
				}
				// THE STEP UNDER TEST: raw /jp4/ -> conditioning -> GPU
				final boolean raw_ok = CuasConditioning.conditionSceneToGpu(
						quadCLTs[nscene],
						null,                 // Config (defaults: rowcol + photometric + FPN)
						ImageDtt.THREADS_MAX,
						debugLevel - 2);
				if (raw_ok) {
					rslt = renderSceneVirtual(
							clt_parameters, image_dtt, center_CLT, quadCLTs[nscene],
							center_disparity, pose[0], pose[1], margin, debugLevel - 2);
				} else {
					System.out.println("testRenderSequence(): scene "+nscene+" ("+ts_name+") raw-jp4 ingest FAILED");
				}
			}
			if (rslt != null) num_ok++; else num_fail++;
			rendered.add(rslt); // null OK - NaN slices keep z aligned
			ts_names.add(ts_name);
			if ((debugLevel > -4) && (((num_ok + num_fail) % 50) == 0)) {
				System.out.println("testRenderSequence(): rendered "+(num_ok+num_fail)+" scenes ("+num_fail+" failed)");
			}
		}
		// ---- Save: [t: s00..s15, merged][z: scenes] hyperstack ----
		final ImageStack stack = new ImageStack(img_width, img_height);
		for (int nc = 0; nc < num_comps; nc++) {
			final String comp = (nc < num_sens) ? String.format("s%02d", nc) : "merged";
			for (int ns = 0; ns < rendered.size(); ns++) {
				final float [][] r = rendered.get(ns);
				stack.addSlice(comp+":"+ts_names.get(ns), ((r != null) && (r[nc] != null)) ? r[nc] : nan_slice);
			}
		}
		final ImagePlus imp = new ImagePlus(center_CLT.getImageName()+"-CUAS-RT-RENDER", stack);
		imp.setDimensions(1, rendered.size(), num_comps);
		imp.setOpenAsHyperStack(true);
		imp.getProcessor().resetMinAndMax();
		center_CLT.saveImagePlusInModelDirectory(null, imp); // title as filename
		System.out.println("testRenderSequence(): scenes OK="+num_ok+", failed="+num_fail+
				", saved -CUAS-RT-RENDER ("+num_comps+" components x "+rendered.size()+" scenes)");
	}
}
