/**
 ** CuasScenePreload.java - all-scene raw LWIR preload for CUAS RT measurements
 **
 ** Copyright (C) 2026 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  CuasScenePreload.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.
 **
 **  CuasScenePreload.java 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 CuasScenePreload.java.  If not, see <http://www.gnu.org/licenses/>.
 ** -----------------------------------------------------------------------------**
 **
 */
package com.elphel.imagej.cuas.rt;

import com.elphel.imagej.cuas.CuasMotion;
import com.elphel.imagej.tileprocessor.QuadCLT;

/**
 * Immutable {@code float[scene][sensor][pixel]} raw-scene store. File I/O and,
 * for captures made with FLIR Row/Col disabled, application of the already
 * calculated borrowed Row/Col profiles happen here before the measured loop.
 * This class never estimates Row/Col; future recalculation belongs to the slow
 * background calibration process, not to setup or the timed RT path.
 *
 * The loader must run before CUAS photometric refinement: each saved profile is
 * in the calibrated-count domain established by the prior corr-xml scales. The
 * exact prior scale is therefore consumed while it is still attached to the
 * scene, rather than approximated with the slightly refined current scale.
 *
 * By Codex on 07/15/2026 from Andrey's RT preload design.
 */
public final class CuasScenePreload {
	private final float [][][] scenes;
	private final int width;
	private final int height;
	private final int numSensors;
	private final boolean rowColApplied;
	private final long bytes;

	private CuasScenePreload(
			final float [][][] scenes,
			final int width,
			final int height,
			final int numSensors,
			final boolean rowColApplied,
			final long bytes) {
		this.scenes = scenes;
		this.width = width;
		this.height = height;
		this.numSensors = numSensors;
		this.rowColApplied = rowColApplied;
		this.bytes = bytes;
	}

	/**
	 * Read every non-null physical scene. Any incomplete scene or incompatible
	 * Row/Col provenance aborts the preload as a unit; the RT measurement then does
	 * not run, rather than silently estimating profiles or timing a partial store.
	 */
	public static CuasScenePreload load(
			final QuadCLT [] quadCLTs,
			final boolean rowcol_enable,
			final int threadsMax,
			final int debugLevel) {
		if ((quadCLTs == null) || (quadCLTs.length == 0)) return null;
		final long t0 = System.nanoTime();
		final float [][][] scenes = new float [quadCLTs.length][][];
		int width = 0;
		int height = 0;
		int numSensors = 0;
		int loaded = 0;
		long bytes = 0;
		System.out.println("CuasScenePreload: reading all raw scenes before the measured loop"+
				(rowcol_enable ? " and applying borrowed Row/Col profiles with their prior corr-xml scales" :
					" (Row/Col already handled by FLIR or explicitly disabled)"));
		for (int nscene = 0; nscene < quadCLTs.length; nscene++) {
			final QuadCLT scene = quadCLTs[nscene];
			if (scene == null) continue;
			final double [][][] raw = CuasMotion.readRawImageData(scene, threadsMax, debugLevel);
			if ((raw == null) || (raw.length == 0) || (raw[0] == null) || (raw[0][0] == null)) {
				System.out.println("CuasScenePreload: FAILED at scene "+nscene+" ("+
						scene.getImageName()+") - preload discarded");
				return null;
			}
			final int sceneWidth = scene.getGeometryCorrection().getSensorWH()[0];
			if ((sceneWidth <= 0) || ((raw[0][0].length % sceneWidth) != 0)) {
				System.out.println("CuasScenePreload: invalid geometry at scene "+nscene+
						" - preload discarded");
				return null;
			}
			final int sceneHeight = raw[0][0].length / sceneWidth;
			if (loaded == 0) {
				width = sceneWidth;
				height = sceneHeight;
				numSensors = raw.length;
			} else if ((sceneWidth != width) || (sceneHeight != height) || (raw.length != numSensors)) {
				System.out.println("CuasScenePreload: incompatible dimensions at scene "+nscene+
						" - preload discarded");
				return null;
			}
			// Validate the complete mono scene before changing any pixel. This keeps a
			// failed preload atomic and prevents silently ignoring unexpected channels.
			for (int s = 0; s < numSensors; s++) {
				if ((raw[s] == null) || (raw[s].length != 1) || (raw[s][0] == null) ||
						(raw[s][0].length != width*height)) {
					System.out.println("CuasScenePreload: incomplete or non-mono sensor "+s+
							" at scene "+nscene+" - preload discarded");
					return null;
				}
			}
			if (rowcol_enable) {
				final double [] scales2 = scene.getLwirScales2();
				if (scales2 != null) for (int s = 0; s < scales2.length; s++) {
					if (scales2[s] != 0.0) {
						System.out.println("CuasScenePreload: nonzero prior scales2["+s+"]="+scales2[s]+
								" cannot map calibrated Row/Col profiles back to raw - preload discarded");
						return null;
					}
				}
				try {
					CuasConditioning.applyCalibratedRowColToRaw(
							raw, sceneWidth, scene.getRowCorr(), scene.getColCorr(),
							scene.getLwirScales().clone());
				} catch (IllegalArgumentException e) {
					System.out.println("CuasScenePreload: "+e.getMessage()+" at scene "+nscene+" ("+
							scene.getImageName()+") - preload discarded");
					return null;
				}
			}
			final float [][] fscene = new float [numSensors][];
			for (int s = 0; s < numSensors; s++) {
				fscene[s] = new float [raw[s][0].length];
				for (int i = 0; i < fscene[s].length; i++) fscene[s][i] = (float) raw[s][0][i];
				bytes += (long) Float.BYTES * fscene[s].length;
			}
			scenes[nscene] = fscene;
			loaded++;
			if (((loaded % 25) == 0) || (nscene == quadCLTs.length - 1)) {
				System.out.println(String.format(
						"CuasScenePreload: %d scenes loaded, %.3f GiB resident",
						loaded, bytes/(1024.0*1024.0*1024.0)));
			}
		}
		if (loaded == 0) return null;
		final double seconds = (System.nanoTime() - t0)*1.0e-9;
		System.out.println(String.format(
				"CuasScenePreload: READY %d scenes, %d sensors, %dx%d, %.3f GiB in %.3f s; file I/O is outside the measured loop",
				loaded, numSensors, width, height, bytes/(1024.0*1024.0*1024.0), seconds));
		return new CuasScenePreload(scenes, width, height, numSensors, rowcol_enable, bytes);
	}

	public float [][] getScene(final int index) {
		return ((index >= 0) && (index < scenes.length)) ? scenes[index] : null;
	}

	public float [][] createScratch() {
		final float [][] scratch = new float [numSensors][];
		for (int s = 0; s < numSensors; s++) scratch[s] = new float [width*height];
		return scratch;
	}

	public boolean isRowColApplied() { return rowColApplied; }
	public int getWidth()             { return width; }
	public int getHeight()            { return height; }
	public int getNumSensors()        { return numSensors; }
	public long getBytes()            { return bytes; }
}
