/**
 **
 ** ItemMatch.java - Match result record linking a ground item to its image location
 **
 ** Copyright (C) 2024 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  ItemMatch.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.orthomosaic;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;

import com.elphel.imagej.common.ShowDoubleFloatArrays;

import ij.ImagePlus;
import ij.Prefs;
import ij.gui.PointRoi;
import ij.io.FileSaver;

public class ItemMatch {
	public double [] combo_pxy; // combo image {pixel_x, pixel_y}
	public double [] lla; // add later
	public HashMap<String, ItemPatternMatch> pattern_matches = new HashMap<String, ItemPatternMatch>();
	public double abs_contrast = 0;
	public CorrelationPeakStats[][] filter_data; // [scene][half/full] 
	public double [][]   extracted_objects;        // [scene][pix]
	public double [][]   extracted_nodc;           // [scene][pix]
	public double [][]   extracted_masked;         // [scene][pix]
	public double [][][] extr_corr;                // [scene][pix][patt]
	public double [][]   extr_corr_half;           // [scene][pix][h/f]
	public boolean removed = false;
	
	public ItemMatch (
			int num_scenes,
			double [] combo_pxy) {
		setComboPXY(combo_pxy);
		this.combo_pxy = combo_pxy;
		filter_data =       new CorrelationPeakStats[num_scenes][2];
		extracted_objects = new double [num_scenes][];
		extracted_nodc =    new double [num_scenes][];
		extracted_masked =  new double [num_scenes][];
		extr_corr =         new double [num_scenes][][];
		extr_corr_half =    new double [num_scenes][];
		removed =           false;
	}
	
	public void setRemoved (boolean removed) {
		this.removed = removed;
	}

	public void remove () {
		this.removed = true;
	}
	
	public boolean isRemoved() {
		return removed;
	}
	
	public void setCorrFull(
			int scene_num,
			int num_patt,
			double [] corr_data) {
		extr_corr[scene_num] = new double [num_patt][];
		extr_corr[scene_num][0] = corr_data;
	}

	public void setCorr(
			int scene_num,
			int patt_index,
			double [] corr_data) {
		extr_corr[scene_num][patt_index] = corr_data;
	}
	
	public double [] getCorr(
			int scene_num,
			int patt_index) {
		return extr_corr[scene_num][patt_index];
	}
	public double [][] getCorrs(
			int scene_num) {
		return extr_corr[scene_num];
	}

	public void setCorrHalf(
			int     scene_num,
			int     best_patt, // 0-full
			boolean combine_full) {
		
		extr_corr_half[scene_num] = extr_corr[scene_num][best_patt].clone();
		if (combine_full) {
			double full_max_value = filter_data[scene_num][0].best_d; // [0];
			if (!Double.isNaN(full_max_value)) {
				// multiply by full correlation, delete by full correlation maximum
				for (int i = 0; i < extr_corr_half[scene_num].length; i++){
					extr_corr_half[scene_num][i] *=
							Math.max(0, extr_corr[scene_num][0][i]/full_max_value);
				}
			}

		}
	}
	
	public void setCorrHalf(
			int scene_num,
			double [] corr_data) {
		extr_corr_half[scene_num] = corr_data;
	}
	
	public double [] getCorrHalf(
			int scene_num) {
		return extr_corr_half[scene_num];
	}
	
	
	
	
	public void setComboPXY(double [] combo_pxy) {
		this.combo_pxy = combo_pxy;
	}
	
	public void setAbsoluteContrast(double contrast) {
		abs_contrast = contrast; // only for main scene
	}
	public double getAbsoluteContrast() {
		return abs_contrast;
	}
	
	public double [] getXY() {
		return combo_pxy;
	}
	public int [] getIntXY() {
		return new int [] {(int) Math.round(combo_pxy[0]), (int) Math.round(combo_pxy[1])};
	}
	
	public void addPatternMatches( // updates existing too
			GroundObjectPattern groundObjectPattern,
			double [] matches,
			int      best_sub) {
		String pattern_path = groundObjectPattern.getPatternPath();
		ItemPatternMatch match = pattern_matches.get(pattern_path);
		if (match == null) {
			match = new ItemPatternMatch(groundObjectPattern);
			pattern_matches.put(pattern_path, match);
		}
		match.setMatches(matches);
		match.setBestSub(best_sub);
	}
	
	public ItemPatternMatch getPatternMatch(GroundObjectPattern groundObjectPattern) {
		return getPatternMatch(groundObjectPattern.getPatternPath());
	}
	
	public ItemPatternMatch getPatternMatch(String pattern_path) {
		return pattern_matches.get(pattern_path);
	}
	
	public double [] getMatchValues(String pattern_path) {
		ItemPatternMatch match = pattern_matches.get(pattern_path);
		if (match == null) {
			System.out.println("No matches found for pattern "+pattern_path);
			return null;
		}
		return match.getMatches();
	}
	
	public double [] getMatchValues(GroundObjectPattern groundObjectPattern) {
		return getMatchValues(groundObjectPattern.getPatternPath());
	}
	
	/**
	 * Get ratio of the worst and best partial pattern correlation values
	 * @param groundObjectPattern
	 * @return
	 */
	public double getRoundness (GroundObjectPattern groundObjectPattern) {
		return getRoundness(groundObjectPattern.getPatternPath());
	}	
	
	public double getRoundness (String pattern_path) {
		ItemPatternMatch match = pattern_matches.get(pattern_path);
		if (match == null) {
			System.out.println("No matches found for pattern "+pattern_path);
			return Double.NaN;
		}
		double [] matches = match.getMatches();
		double best =  matches[1]; 
		double worst = matches[1];
		for (int i = 1; i < matches.length; i++) {
			if (matches[i] < worst) {
				worst = matches[i];
			}
			if (matches[i] > best) {
				best = matches[i];
			}
		}
		return worst/best;
	}
	
	
	public double getMatchValue(String pattern_path, int indx) {
		ItemPatternMatch match = pattern_matches.get(pattern_path);
		if (match == null) {
			System.out.println("No matches found for pattern "+pattern_path);
			return Double.NaN;
		}
		return match.getMatch(indx);
	}

	public double getMatchBestValue(String pattern_path) {
		ItemPatternMatch match = pattern_matches.get(pattern_path);
		if (match == null) {
			System.out.println("No matches found for pattern "+pattern_path);
			return Double.NaN;
		}
		return match.getBestMatchValue();
	}

	
	
	
	/**
	 * Return correlation value as the one of the pattern correlations pointed
	 * by the best subpattern index.
	 * @param groundObjectPattern
	 * @return selected correlation value
	 */
	public double getMatchValue(GroundObjectPattern groundObjectPattern) {
		return getMatchValue(groundObjectPattern.getPatternPath());
	}
	
	/**
	 * Return actual best correlation value regardless of where best subpattern index
	 * points to - it may be forced to point to the full pattern by absolute contrast
	 * @param groundObjectPattern
	 * @return maximal correlation value amoong the full pattern and all half-patterns.
	 */
	public double getMatchBestValue(GroundObjectPattern groundObjectPattern) {
		return getMatchBestValue(groundObjectPattern.getPatternPath());
	}
	
	
	
	public double getMatchValue(String pattern_path) {
		int indx = getPatternMatch(pattern_path).getBestSub(); // -1;
		return getMatchValue(pattern_path,indx);
	}
	
	
	
	public double getMatchValue(GroundObjectPattern groundObjectPattern, int indx) {
		return getMatchValue(groundObjectPattern.getPatternPath(), indx);
	}
	
	public static ArrayList<Integer> sortByMatch(
			ArrayList<ItemMatch> match_list,
			GroundObjectPattern  groundObjectPattern,
			boolean              keep_removed,
			int                  indx){
		String pattern_path = groundObjectPattern.getPatternPath();
		return sortByMatch(match_list, pattern_path, keep_removed, indx);
		
	}
	
	public static ArrayList<Integer> sortByMatch(
			ArrayList<ItemMatch> match_list,
			String               pattern_path,
			boolean              keep_removed,
			int                  indx){
		if (indx < 0) {
			return sortByBestMatch (match_list, pattern_path, keep_removed);
		}
		double large_enough = 1.0;
		ArrayList<Integer> result_list= new ArrayList<Integer>(match_list.size());
		for (int i = 0; i < match_list.size(); i++) {
			if (keep_removed || !match_list.get(i).isRemoved()) {
				result_list.add(i);
			}
		}
		Collections.sort(result_list, new Comparator<Integer>() {
			@Override
			public int compare(Integer lhs, Integer rhs) {
				ItemMatch rhsm = match_list.get(rhs);
				ItemMatch lhsm = match_list.get(lhs);
				double rhsd = rhsm.getMatchValue(pattern_path, indx) - (rhsm.isRemoved() ? large_enough:0 );
				double lhsd = lhsm.getMatchValue(pattern_path, indx) - (lhsm.isRemoved() ? large_enough:0 );
				return (rhsd > lhsd) ? 1 : (rhsd < lhsd) ? -1 : 0; // decreasing
			}
		});
		return result_list;
	}
	
	public static ArrayList<Integer> sortByBestMatch(
			ArrayList<ItemMatch> match_list,
			String               pattern_path,
			boolean              keep_removed){
		double large_enough = 1.0;
		ArrayList<Integer> result_list= new ArrayList<Integer>(match_list.size());
		for (int i = 0; i < match_list.size(); i++) {
			if (keep_removed || !match_list.get(i).isRemoved()) {
				result_list.add(i);
			}
		}
		Collections.sort(result_list, new Comparator<Integer>() {
			@Override
			public int compare(Integer lhs, Integer rhs) {
				ItemMatch rhsm = match_list.get(rhs);
				ItemMatch lhsm = match_list.get(lhs);
				double rhsd = rhsm.getMatchBestValue(pattern_path) - (rhsm.isRemoved() ? large_enough:0 );
				double lhsd = lhsm.getMatchBestValue(pattern_path) - (lhsm.isRemoved() ? large_enough:0 );
				return (rhsd > lhsd) ? 1 : (rhsd < lhsd) ? -1 : 0; // decreasing
			}
		});
		return result_list;
	}
	
	
	
	public static void setExtractedObjects(
			int                   scene_num,
			int                   extr_size,
			GroundObjectPattern   gop,
			ArrayList <ItemMatch> matches_list,
			ArrayList<Integer>    match_sort,
			double[]              data,
			int                   width,
			int                   debugLevel) {
		if (match_sort.isEmpty()) {
			System.out.println("List of detected objects is empty, nothing to process");
			return;
		}
		for (int mn=0; mn < match_sort.size(); mn++) {
			int indx =match_sort.get(mn);
			ItemMatch match = matches_list.get(indx);
			match.extractObject(
					data,       // double [] data,
					width,      // int       width,
					scene_num,  // int       scene_num,
					extr_size); // int       extr_size);
		}
	}
	
	public void extractObject(
			double [] data,
			int       width,
			int       scene_num,
			int       extr_size) {
		double [] center_xy = getXY();
		double [] extr_data = ObjectLocation.extractObjectImage(
				center_xy,  // double [] center_xy,
				extr_size,  // int       size,
				data,       // double [] src_img,
				width);     // int width)
		setExtractedObject(extr_data, scene_num);
	}
	

	public void extractCorrs(
			double [][] corr_data,
			int         width,
			int         scene_num,
			int         extr_size) {
		double [] center_xy = getXY();
		extr_corr[scene_num] = new double [corr_data.length][];
		for (int i = 0; i < corr_data.length; i++) {
			extr_corr[scene_num][i] = ObjectLocation.extractObjectImage(
					center_xy,    // double [] center_xy,
					extr_size,    // int       size,
					corr_data[i], // double [] src_img,
					width);       // int width)
		}
	}
	
	
	
	
	public void setExtractedObject(
			double [] data,
			int       scene_num) {
		extracted_objects[scene_num] = data;
		extracted_nodc[scene_num] = data.clone();
		OrthoMap.removeDC(extracted_nodc[scene_num]); 
	}
	
	public void setMaskedObject(
			double [] data,
			int       scene_num) {
		extracted_masked[scene_num] = data;
	}
	public void setCorrData(
			double [] data,
			int       scene_num,
			int       half_full) { // 1 - half, 0 - full
		extr_corr[scene_num][half_full] = data;
	}
	
	public static ImagePlus getImageExtracts(
			String                prefix, // include PC, filter here
			String                suffix, // w/o .tiff
			boolean               show,
			boolean               save,
			boolean               show_removed,
			String                save_dir,
			int                   scene_num,
			boolean               nodc,
			int                   mode, // 0 - extract, 1 - masked, 2 - corr full, 3 - corr half
			boolean               show_centers,
			int                   extr_size,
			GroundObjectPattern   gop,
			ArrayList <ItemMatch> matches_list,
			ArrayList<Integer>    match_sort,
			int                   debugLevel) {
		if (match_sort.isEmpty()) {
			System.out.println("List of detected objects is empty, nothing to display");
			return null;
		}
		PointRoi roi = new PointRoi();
		roi.setOptions("nolabel"); // label");
		for (int mn=0; mn < match_sort.size(); mn++) {
			roi.addPoint(extr_size/2,extr_size/2,mn+1); // ,1);
		}
		int num_slices = match_sort.size();
		if (!show_removed) {
			num_slices=0;
			for (int mn=0; mn < match_sort.size(); mn++) {
				if (!matches_list.get(match_sort.get(mn)).isRemoved()) {
					num_slices++;
				}
			}			
		}
		String []   extr_titles = new String [num_slices];
		double [][] extr_data = new double [num_slices][];
		String title = prefix; 
		switch (mode) {
		case 0: title += "_objects_" + scene_num + (nodc ? "_no-DC":"_with-DC"); break;
		case 1: title += "_contrast-masks";  break;
		case 2: title += "_objects_"+scene_num+"_corr_full";  break;
		case 3: title += "_objects_"+scene_num+"_corr_halves";  break;
		}
		if (suffix != null) {
			title += suffix;
		}
		title += ".tiff";
		int indx = 0;
		for (int mn=0; mn < match_sort.size(); mn++) {
			ItemMatch match = matches_list.get(match_sort.get(mn));
			if (show_removed || !matches_list.get(match_sort.get(mn)).isRemoved()) {
				int [] ixy = match.getIntXY();
				extr_titles[indx] = ixy[0]+"/"+ ixy[1]+":"+String.format("%7.5f",	match.getMatchValue(gop));
				switch (mode) {
				case 0: extr_data[indx] = nodc ? match.extracted_nodc[scene_num] : match.extracted_objects[scene_num]; break;
				case 1: extr_data[indx] = match.extracted_masked[scene_num]; break;
				case 2: extr_data[indx] = match.extr_corr[scene_num][0]; break;
				case 3: extr_data[indx] = match.extr_corr_half[scene_num]; break;
				}
				indx++;
			}
		}
		ImagePlus imp = ShowDoubleFloatArrays.makeArrays(
				extr_data,
				extr_size,
				extr_size,
				title, // +"-objects_"+scene_num,				
				extr_titles); // test_titles,
		if (imp == null) {
			return null;
		}
		if (show_centers) {
			imp.setRoi(roi); // null
		}
		if (show) {
			imp.show();
		}
		if (save && (save_dir != null)) {
			if (!save_dir.endsWith(Prefs.getFileSeparator())) {
				save_dir+=Prefs.getFileSeparator();
			}
			String save_path = save_dir+title;
			FileSaver imp_fs = new FileSaver(imp);
			imp_fs.saveAsTiff(save_path);
			if (debugLevel > -4) {
				System.out.println("Saved "+save_path);	
			}
		}
		return imp;
	}
	
	
	
}
