/**
 ** -----------------------------------------------------------------------------**
 ** LwirReaderParameters.java
 **
 ** Parameters handling for LwirReader class
 **
 **
 ** Copyright (C) 2019 Elphel, Inc.
 **
 ** -----------------------------------------------------------------------------**
 **
 **  LwirReaderParameters.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.lwir;

import java.io.File;
import java.io.FilenameFilter;
import java.util.Properties;

import com.elphel.imagej.cameras.CLTParameters;
import com.elphel.imagej.common.GenericJTabbedDialog;
import com.elphel.imagej.readers.EyesisTiff;

import ij.ImagePlus;
import ij.Prefs;

public class LwirReaderParameters {
	public final static String    NAME_TALON=        "Talon";
	public final static String    NAME_LWIR16=       "LWIR16";
	public final static String [] CAMERA_NAMES= {NAME_TALON,NAME_LWIR16};
	public final static int []    BOSON_FFC_FRAMES= {2,4,8,16};
	public final static int []    FFC_GROUPS=          {1,2,4};
	public static final String [] SENSOR_TYPES = {"EO","LWIR"};
	public static final String    SENSOR_TYPE =  "SENSOR_TYPE";
	public final static int       TYPE_EO= 0;
	public final static int       TYPE_LWIR= 1;
	
	protected static int MAX_LWIR_WIDTH =     1024; //
	
	private boolean parameters_updated =     false;
	protected String  camera_name =          "Talon"; // "LWIR16";
	protected int     avg_number =           4; // number of LWIR measurements to average
	protected int     avg_number_eo =        1; // number of EO measurements to average
	protected int     num_frames =           7; // misses images if high quality large size?
	protected boolean lwir_ffc =             true;
	protected int     ffc_frames =           8;
	protected int     ffc_wait_frames =      10; // extra wait after FFC finished (2xffc_frames)
	protected int     ffc_groups =           2; // 1/2/4 - do not run FFC on all channels simultaneously (43 failed)
	protected double  acquire_delay =        3.0; // seconds. Schedule later from the master channel current time to start all cameras
	protected boolean avg_all =              true;
//	protected String  lwir_ip =              "192.168.0.36";
	protected String[] lwir_ips =             {"192.168.0.41","192.168.0.42","192.168.0.43","192.168.0.44"}; // first - master
	protected String  eo_ip =                "192.168.0.38";
	protected int []  lwir_channels =        {0, 1, 2 ,3};
	protected int []  eo_channels =          {0, 1, 2 ,3};
	protected boolean lwir_telemetry =       true;
	protected boolean eo_full_window =       true;
	protected double  eo_quality =           98.0;
	protected boolean eo_scale =             false; // restore sensor pixel values, undo camera white balancing
	protected boolean eo_autoexp =           false;
	protected double  eo_max_autoexp_ms =    20.0;
	protected double  eo_exposure_ms =       5.0;
	protected boolean eo_whitebal =          false;
	protected double  eo_gain_g =            2.0;
	protected double  eo_gain_rg =           0.7705; // 1.116; halogen/fluorescent
	protected double  eo_gain_bg =           2.401;  // 1.476;
	protected boolean [] selected_channels = {true, true, true, true, true, true, true, true};
/*
	protected double [] eo_exp_corr = {1.0, 1.0, 1.0, 1.0};
	protected double [] eo_gcorr_rbgb = {
			1.0,    1.0,    1.0,
			1.0,    1.0,    1.0,
			1.0,    1.0,    1.0,
			1.0,    1.0,    1.0};
 */
	protected double [] eo_exp_corr =	{
			1.0, 1.0, 1.0, 1.0};
	protected double [] eo_gcorr_rbgb = { // example after autobalance for 192.168.0.38 with halogen lamps
			1.0,    1.0,    0.9743,
			1.0668, 1.1055, 1.0006,
			1.1533, 1.0780, 1.0015,
			0.9966, 1.0445, 1.0023};
	protected int     lwir_trig_dly   =     9000000; //in 100MHz clock cycle, current FPGA requires >0 (used 1000)
	protected int     eo_lag   =          2;//1; // frames
	protected double  max_mismatch_ms =     0.05;
	protected int     max_frame_diff =      2;// 1; // 2;
	protected int     debug_level =           0;//-3: OFF, -2:Fatal, -1:ERROR, 0:WARN, 1:INFO,2:DEBUG
	protected boolean show_images = false;

	protected int     lwir_chn0 =             0; // not configurable
//	protected int     eo_chn0 =               4; // not configurable

	public LwirReaderParameters() {
		
	}
	
	public int [] getLwirChannels(boolean absolute) {
		int [] absolute_chn = new int [lwir_channels.length];
		for (int i = 0; i < absolute_chn.length; i++) {
			absolute_chn[i] = lwir_channels[i] + (absolute? getLwirChn0() : 0);
		}
		return absolute_chn;
	}

	public int [] getEoChannels(boolean absolute) {
		int [] absolute_chn = new int [eo_channels.length];
		for (int i = 0; i < absolute_chn.length; i++) {
			absolute_chn[i] = eo_channels[i] +  (absolute? getEoChn0():0);
		}
		return absolute_chn;
	}
//	protected int []  lwir_channels =        {0, 1, 2 ,3};
//	protected int []  eo_channels =          {0, 1, 2 ,3};
	
	
	public LwirReaderParameters(String name) {
		if (NAME_TALON.equals(name)) camera_name = NAME_TALON;
		else if (NAME_LWIR16.equals(name)) camera_name = NAME_LWIR16;
	}

	public static boolean is_LWIR(String type) {
		return ((type != null) && type.equals(SENSOR_TYPES[1]));
	}
	public static boolean is_EO(String type) {
		return ((type != null) && type.equals(SENSOR_TYPES[1]));
	}
	public static String sensorName(boolean lwir) {
		return SENSOR_TYPES[lwir?1:0];
	}
	
	// --- interface methods
	public int getAvgNumberLwir() {
		return avg_number;
	}
	public int getAvgNumberEO() {
		return avg_number_eo;
	}

	
	public String getCameraName() {
		return this.camera_name;
	}
	
	public int getNumFrames() {
		return num_frames;
	}
	public int getLwirChn0() {
		return lwir_chn0;
	}

	public int getEoChn0() {
		return isLwir16() ? 16:4;// eo_chn0;
	}
	
	public int getNumChannels() {
		return isLwir16()?20:8;
	}
	
	public int [] getTypeMap() { // eo - 0, lwir - 1
		int [] types = new int [getNumChannels()];
		for (int i = 0; i < types.length; i++) {
			types[i] = (i >= getEoChn0())? TYPE_EO : TYPE_LWIR;
		}
		return types;
	}
	
	public static boolean is_LWIR(int width) {
		return width <= MAX_LWIR_WIDTH;
	}

	public static boolean is_LWIR(ImagePlus imp){
		// See if image has LwirReaderParameters.SENSOR_TYPE property, then use is_LWIR(String property_value),
		// if not - use old width property
		if (imp.getProperty("WOI_WIDTH")==null) {
			EyesisTiff.decodeProperiesFromInfo(imp);
		}
		if (imp.getProperty(SENSOR_TYPE)!=null) {
			return is_LWIR((String) imp.getProperty(SENSOR_TYPE));
		}
		return is_LWIR(imp.getWidth());
	}
	public static int sensorType(ImagePlus imp) {
		if (imp.getProperty("WOI_WIDTH")==null) {
			EyesisTiff.decodeProperiesFromInfo(imp);
		}
		if (imp.getProperty(SENSOR_TYPE)!=null) {
			return (is_LWIR((String) imp.getProperty(SENSOR_TYPE)))? 1:0;
		}
		return is_LWIR(imp.getWidth())? TYPE_LWIR : TYPE_EO;
		
	}

	public int getDebugLevel() {
		return this.debug_level;
	}

	public void setDebugLevel(int level) {
		this.debug_level = level;
	}

	public boolean isShowImages() {
		return show_images;
	}
	public String getLwirIP() {
		return lwir_ips[0];
	}
	public String getLwirIP(int n) {
		return lwir_ips[n];
	}
	public String [] getLwirIPs() {
		return lwir_ips;
	}

	public String getEOIP() {
		return eo_ip;
	}
	public String [] getAllIPs() {
		String [] all_ips = new String [lwir_ips.length+1];
		int i = 0;
		for (i = 0; i < lwir_ips.length; i++) {
			all_ips[i] = lwir_ips[i];
		}
		all_ips[i] = eo_ip;
		return all_ips;
	}
	boolean [] getSelectedChannels() {
		return selected_channels;
	}
	
	// call after switch from Lwir16 to Talon
	public boolean isLwir16() {
		return camera_name.equals(NAME_LWIR16) || ((lwir_channels != null) && (lwir_channels.length == 16));
	}
	
	public int getFFCFrames() {
		return ffc_frames;
	}

	public int getFFCGroups() {
		return ffc_groups;
	}

	public int getFFCWaitFrames() {
		return ffc_wait_frames;
	}
	
	public double getAcquireDelay() {
		return acquire_delay;
	}
	
	public boolean runFFCBeforeMeasurments() {
		return lwir_ffc;
	}

	public void setDefaultChannels() {
		boolean[] old_selected = selected_channels;
		if (isLwir16()) {
			lwir_ips =      new String[]{"192.168.0.41","192.168.0.42","192.168.0.43","192.168.0.43"}; 
			lwir_channels = new int[] {0,1,2,3,4,5,6,7,8,9,10,11,12,12,14,15};
			eo_ip =         "192.168.0.45";
		} else {
			lwir_ips =      new String[]{"192.168.0.36"}; 
			lwir_channels = new int[] {0,1,2,3};
			eo_ip =         "192.168.0.38";
		}
		selected_channels = new boolean[lwir_channels.length + eo_channels.length];
		for (int i = 0; i < selected_channels.length; i++) {
			if (i < old_selected.length) {
				selected_channels[i] = old_selected[i];
			} else {
				selected_channels[i] = true;
			}
		}
	}
	
	
	public void setProperties(String prefix,Properties properties){
		properties.setProperty(prefix+"camera_name",         this.camera_name+"");
		properties.setProperty(prefix+"avg_number",          this.avg_number+"");
		properties.setProperty(prefix+"avg_number_eo",       this.avg_number_eo+"");
		properties.setProperty(prefix+"num_frames",          this.num_frames+"");
		properties.setProperty(prefix+"lwir_ffc",            this.lwir_ffc+"");
		properties.setProperty(prefix+"ffc_frames",          this.ffc_frames+"");
		properties.setProperty(prefix+"ffc_wait_frames",     this.ffc_wait_frames+"");
		properties.setProperty(prefix+"ffc_groups",          this.ffc_groups+"");
		properties.setProperty(prefix+"acquire_delay",       this.acquire_delay+"");
		properties.setProperty(prefix+"avg_all",             this.avg_all+"");
		properties.setProperty(prefix+"lwir_ips",            CLTParameters.arr_to_str(this.lwir_ips)); // this.lwir_ip+"");; this.lwir_ip+"");
		properties.setProperty(prefix+"eo_ip",               this.eo_ip+"");
		properties.setProperty(prefix+"lwir_channels",       CLTParameters.arr_to_str(this.lwir_channels));
		properties.setProperty(prefix+"eo_channels",         CLTParameters.arr_to_str(this.eo_channels));
		properties.setProperty(prefix+"lwir_telemetry",      this.lwir_telemetry+"");
		properties.setProperty(prefix+"eo_full_window",      this.eo_full_window+"");
		properties.setProperty(prefix+"eo_quality",          this.eo_quality+"");
		properties.setProperty(prefix+"eo_scale",            this.eo_scale+"");
		properties.setProperty(prefix+"eo_autoexp",          this.eo_autoexp+"");
		properties.setProperty(prefix+"eo_max_autoexp_ms",   this.eo_max_autoexp_ms+"");
		properties.setProperty(prefix+"eo_exposure_ms",      this.eo_exposure_ms+"");
		properties.setProperty(prefix+"eo_whitebal",         this.eo_whitebal+"");
		properties.setProperty(prefix+"eo_gain_g",           this.eo_gain_g+"");
		properties.setProperty(prefix+"eo_gain_rg",          this.eo_gain_rg+"");
		properties.setProperty(prefix+"eo_gain_bg",          this.eo_gain_bg+"");
		properties.setProperty(prefix+"eo_exp_corr",         CLTParameters.arr_to_str(this.eo_exp_corr));
		properties.setProperty(prefix+"eo_gcorr_rbgb",       CLTParameters.arr_to_str(this.eo_gcorr_rbgb));
		properties.setProperty(prefix+"lwir_trig_dly",       this.lwir_trig_dly+"");
		properties.setProperty(prefix+"eo_lag",              this.eo_lag+"");
		properties.setProperty(prefix+"max_mismatch_ms",     this.max_mismatch_ms+"");
		properties.setProperty(prefix+"max_frame_diff",      this.max_frame_diff+"");
		properties.setProperty(prefix+"debug_level",         this.debug_level+"");
		properties.setProperty(prefix+"selected_channels",   CLTParameters.arr_to_str(this.selected_channels));
		properties.setProperty(prefix+"show_images",         this.show_images+"");
	}

	public void getProperties(String prefix,Properties properties){
		String old_camera_name =       this.camera_name; 
		if (properties.getProperty(prefix+"camera_name")!=null) {
			this.camera_name = properties.getProperty(prefix+"camera_name");
		} else {
			this.camera_name = NAME_TALON; // old configs
		}
		if (!this.camera_name.equals(old_camera_name)) {
			setDefaultChannels(); // modifies lwir_IPs, eo_ip, channels,
		}
		if (properties.getProperty(prefix+"vnir_ip")!=null)             this.eo_ip=      properties.getProperty(prefix+"vnir_ip");
		if (properties.getProperty(prefix+"vnir_channels")!=null)       this.eo_channels=CLTParameters.str_to_iarr(properties.getProperty(prefix+"vnir_channels"));
		if (properties.getProperty(prefix+"vnir_quality")!=null)        this.eo_quality=Double.parseDouble(properties.getProperty(prefix+"vnir_quality"));
		if (properties.getProperty(prefix+"vnir_scale")!=null)          this.eo_scale= Boolean.parseBoolean(properties.getProperty(prefix+"vnir_scale"));
		if (properties.getProperty(prefix+"vnir_autoexp")!=null)        this.eo_autoexp= Boolean.parseBoolean(properties.getProperty(prefix+"vnir_autoexp"));
		if (properties.getProperty(prefix+"vnir_max_autoexp_ms")!=null) this.eo_max_autoexp_ms=Double.parseDouble(properties.getProperty(prefix+"vnir_max_autoexp_ms"));
		if (properties.getProperty(prefix+"vnir_exposure_ms")!=null)    this.eo_exposure_ms=Double.parseDouble(properties.getProperty(prefix+"vnir_exposure_ms"));
		if (properties.getProperty(prefix+"vnir_whitebal")!=null)       this.eo_whitebal= Boolean.parseBoolean(properties.getProperty(prefix+"vnir_whitebal"));
		if (properties.getProperty(prefix+"vnir_gain_g")!=null)         this.eo_gain_g=Double.parseDouble(properties.getProperty(prefix+"vnir_gain_g"));
		if (properties.getProperty(prefix+"vnir_gain_rg")!=null)        this.eo_gain_rg=Double.parseDouble(properties.getProperty(prefix+"vnir_gain_rg"));
		if (properties.getProperty(prefix+"vnir_gain_bg")!=null)        this.eo_gain_bg=Double.parseDouble(properties.getProperty(prefix+"vnir_gain_bg"));
		if (properties.getProperty(prefix+"vnir_exp_corr")!=null)       this.eo_exp_corr=CLTParameters.str_to_darr(properties.getProperty(prefix+"vnir_exp_corr"));
		if (properties.getProperty(prefix+"vnir_gcorr_rbgb")!=null)     this.eo_gcorr_rbgb=CLTParameters.str_to_darr(properties.getProperty(prefix+"vnir_gcorr_rbgb"));
		if (properties.getProperty(prefix+"vnir_lag")!=null)            this.eo_lag=Integer.parseInt(properties.getProperty(prefix+"vnir_lag")); // old version
		if (properties.getProperty(prefix+"avg_number")!=null)          this.avg_number=Integer.parseInt(properties.getProperty(prefix+"avg_number"));
		if (properties.getProperty(prefix+"avg_number_eo")!=null)       this.avg_number_eo=Integer.parseInt(properties.getProperty(prefix+"avg_number_eo"));
		if (properties.getProperty(prefix+"num_frames")!=null)          this.num_frames=Integer.parseInt(properties.getProperty(prefix+"num_frames"));
		if (properties.getProperty(prefix+"lwir_ffc")!=null)            this.lwir_ffc= Boolean.parseBoolean(properties.getProperty(prefix+"lwir_ffc"));
		if (properties.getProperty(prefix+"ffc_frames")!=null)          this.ffc_frames=Integer.parseInt(properties.getProperty(prefix+"ffc_frames"));
		if (properties.getProperty(prefix+"ffc_wait_frames")!=null)     this.ffc_wait_frames=Integer.parseInt(properties.getProperty(prefix+"ffc_wait_frames"));
		if (properties.getProperty(prefix+"ffc_groups")!=null)          this.ffc_groups=Integer.parseInt(properties.getProperty(prefix+"ffc_groups"));
		if (properties.getProperty(prefix+"acquire_delay")!=null)       this.acquire_delay=Double.parseDouble(properties.getProperty(prefix+"acquire_delay"));
		if (properties.getProperty(prefix+"avg_all")!=null)             this.avg_all= Boolean.parseBoolean(properties.getProperty(prefix+"avg_all"));
		if (properties.getProperty(prefix+"lwir_ip")!=null)             this.lwir_ips[0]=    properties.getProperty(prefix+"lwir_ip");
		if (properties.getProperty(prefix+"lwir_ips")!=null)            this.lwir_ips=  CLTParameters.str_to_sarr(properties.getProperty(prefix+"lwir_ips"));
		if (properties.getProperty(prefix+"eo_ip")!=null)               this.eo_ip=      properties.getProperty(prefix+"eo_ip");
		if (properties.getProperty(prefix+"lwir_channels")!=null)       this.lwir_channels=CLTParameters.str_to_iarr(properties.getProperty(prefix+"lwir_channels"));
		if (properties.getProperty(prefix+"eo_channels")!=null)         this.eo_channels=CLTParameters.str_to_iarr(properties.getProperty(prefix+"eo_channels"));
		if (properties.getProperty(prefix+"lwir_telemetry")!=null)      this.lwir_telemetry= Boolean.parseBoolean(properties.getProperty(prefix+"lwir_telemetry"));
		if (properties.getProperty(prefix+"eo_full_window")!=null)      this.eo_full_window= Boolean.parseBoolean(properties.getProperty(prefix+"eo_full_window"));
		if (properties.getProperty(prefix+"eo_quality")!=null)          this.eo_quality=Double.parseDouble(properties.getProperty(prefix+"eo_quality"));
		if (properties.getProperty(prefix+"eo_scale")!=null)            this.eo_scale= Boolean.parseBoolean(properties.getProperty(prefix+"eo_scale"));
		if (properties.getProperty(prefix+"eo_autoexp")!=null)          this.eo_autoexp= Boolean.parseBoolean(properties.getProperty(prefix+"eo_autoexp"));
		if (properties.getProperty(prefix+"eo_max_autoexp_ms")!=null)   this.eo_max_autoexp_ms=Double.parseDouble(properties.getProperty(prefix+"eo_max_autoexp_ms"));
		if (properties.getProperty(prefix+"eo_exposure_ms")!=null)      this.eo_exposure_ms=Double.parseDouble(properties.getProperty(prefix+"eo_exposure_ms"));
		if (properties.getProperty(prefix+"eo_whitebal")!=null)         this.eo_whitebal= Boolean.parseBoolean(properties.getProperty(prefix+"eo_whitebal"));
		if (properties.getProperty(prefix+"eo_gain_g")!=null)           this.eo_gain_g=Double.parseDouble(properties.getProperty(prefix+"eo_gain_g"));
		if (properties.getProperty(prefix+"eo_gain_rg")!=null)          this.eo_gain_rg=Double.parseDouble(properties.getProperty(prefix+"eo_gain_rg"));
		if (properties.getProperty(prefix+"eo_gain_bg")!=null)          this.eo_gain_bg=Double.parseDouble(properties.getProperty(prefix+"eo_gain_bg"));
		if (properties.getProperty(prefix+"eo_exp_corr")!=null)         this.eo_exp_corr=CLTParameters.str_to_darr(properties.getProperty(prefix+"eo_exp_corr"));
		if (properties.getProperty(prefix+"eo_gcorr_rbgb")!=null)       this.eo_gcorr_rbgb=CLTParameters.str_to_darr(properties.getProperty(prefix+"eo_gcorr_rbgb"));
		if (properties.getProperty(prefix+"lwir_trig_dly")!=null)       this.lwir_trig_dly=Integer.parseInt(properties.getProperty(prefix+"lwir_trig_dly"));
		if (properties.getProperty(prefix+"eo_lag")!=null)              this.eo_lag=Integer.parseInt(properties.getProperty(prefix+"eo_lag"));
		if (properties.getProperty(prefix+"max_mismatch_ms")!=null)     this.max_mismatch_ms=Double.parseDouble(properties.getProperty(prefix+"max_mismatch_ms"));
		if (properties.getProperty(prefix+"max_frame_diff")!=null)      this.max_frame_diff=Integer.parseInt(properties.getProperty(prefix+"max_frame_diff"));
		if (properties.getProperty(prefix+"debug_level")!=null)         this.debug_level=Integer.parseInt(properties.getProperty(prefix+"debug_level"));
		if (properties.getProperty(prefix+"selected_channels")!=null)   this.selected_channels=CLTParameters.str_to_barr(properties.getProperty(prefix+"selected_channels"));
		if (properties.getProperty(prefix+"show_images")!=null)         this.show_images= Boolean.parseBoolean(properties.getProperty(prefix+"show_images"));
		parameters_updated = true;
	}
	@Override
	public LwirReaderParameters clone() { //  throws CloneNotSupportedException {
		LwirReaderParameters lrp =  new LwirReaderParameters();
		lrp.camera_name =           this.camera_name;
		lrp.avg_number=             this.avg_number;
		lrp.avg_number_eo=          this.avg_number_eo;
		lrp.num_frames=             this.num_frames;
		lrp.lwir_ffc=               this.lwir_ffc;
		lrp.ffc_frames=             this.ffc_frames;
		lrp.ffc_wait_frames=        this.ffc_wait_frames;
		lrp.ffc_groups=             this.ffc_groups;
		lrp.acquire_delay=          this.acquire_delay;
		lrp.avg_all=                this.avg_all;
		lrp.lwir_ips=               this.lwir_ips.clone();
		lrp.eo_ip=                  this.eo_ip;
		lrp.lwir_channels=          this.lwir_channels.clone();
		lrp.eo_channels=            this.eo_channels.clone();
		lrp.lwir_telemetry=         this.lwir_telemetry;
		lrp.eo_full_window=         this.eo_full_window;
		lrp.eo_quality=             this.eo_quality;
		lrp.eo_scale=               this.eo_scale;
		lrp.eo_autoexp=             this.eo_autoexp;
		lrp.eo_max_autoexp_ms=      this.eo_max_autoexp_ms;
		lrp.eo_exposure_ms=         this.eo_exposure_ms;
		lrp.eo_whitebal=            this.eo_whitebal;
		lrp.eo_gain_g=              this.eo_gain_g;
		lrp.eo_gain_rg=             this.eo_gain_rg;
		lrp.eo_gain_bg=             this.eo_gain_bg;
		lrp.eo_exp_corr=            this.eo_exp_corr.clone();
		lrp.eo_gcorr_rbgb=          this.eo_gcorr_rbgb.clone();
		lrp.lwir_trig_dly=          this.lwir_trig_dly;
		lrp.eo_lag=                 this.eo_lag;
		lrp.max_mismatch_ms=        this.max_mismatch_ms;
		lrp.max_frame_diff=         this.max_frame_diff;
		lrp.debug_level=            this.debug_level;
		lrp.selected_channels =     this.selected_channels.clone();
		lrp.show_images =           this.show_images;
		return lrp;
	}

	@Override
	public boolean equals(Object o) {
		if (o == this) {
			return true;
		}
		if (!(o instanceof LwirReaderParameters)) {
			return false;
		}
		
		LwirReaderParameters lrp = (LwirReaderParameters) o;
		boolean all_ips_eq = lrp.lwir_ips.length == this.lwir_ips.length;
		if (all_ips_eq) {
			for (int i = 0; (i < lrp.lwir_ips.length) && all_ips_eq; i++) {
				all_ips_eq &= (lrp.lwir_ips[i].equals(this.lwir_ips[i]));
			}
			
		}
		return  (lrp.camera_name.equals(this.camera_name)) &&
				(lrp.avg_number == this.avg_number) &&
				(lrp.avg_number_eo == this.avg_number_eo) &&
				(lrp.lwir_ffc == this.lwir_ffc) &&
				(lrp.ffc_frames == this.ffc_frames) &&
				(lrp.ffc_wait_frames == this.ffc_wait_frames) &&
				(lrp.ffc_groups == this.ffc_groups) &&
				(lrp.acquire_delay == this.acquire_delay) &&
				(lrp.avg_all == this.avg_all) &&
				all_ips_eq && //(lrp.lwir_ip.equals(this.lwir_ip)) &&
				(lrp.eo_ip.equals(this.eo_ip)) &&
				(java.util.Arrays.equals(lrp.lwir_channels, this.lwir_channels)) &&
				(java.util.Arrays.equals(lrp.eo_channels, this.eo_channels)) &&
				(lrp.lwir_telemetry == this.lwir_telemetry) &&
				(lrp.eo_full_window == this.eo_full_window) &&
				(lrp.eo_quality == this.eo_quality) &&
				(lrp.eo_scale == this.eo_scale) &&
				(lrp.eo_autoexp == this.eo_autoexp) &&
				(lrp.eo_max_autoexp_ms == this.eo_max_autoexp_ms) &&
				(lrp.eo_exposure_ms == this.eo_exposure_ms) &&
				(lrp.eo_whitebal == this.eo_whitebal) &&
				(lrp.eo_gain_g == this.eo_gain_g) &&
				(lrp.eo_gain_rg == this.eo_gain_rg) &&
				(lrp.eo_gain_bg == this.eo_gain_bg) &&
				(java.util.Arrays.equals(lrp.eo_exp_corr, this.eo_exp_corr)) &&
				(java.util.Arrays.equals(lrp.eo_gcorr_rbgb, this.eo_gcorr_rbgb)) &&
				(lrp.lwir_trig_dly == this.lwir_trig_dly) &&
				(lrp.eo_lag == this.eo_lag) &&
				(lrp.max_mismatch_ms == this.max_mismatch_ms) &&
				(lrp.max_frame_diff == this.max_frame_diff) &&
//				(lrp.debug_level ==    this.debug_level) &&
				(java.util.Arrays.equals(lrp.selected_channels, this.selected_channels));
//				&& (lrp.show_images ==    this.show_images);
	}

	@Override
	public int hashCode() {
		int prime = 31;
		int result = 1;
		result = prime * result + camera_name.hashCode();
		result = prime * result + ((Integer) avg_number).hashCode();
		result = prime * result + ((Integer) avg_number_eo).hashCode();
		result = prime * result + (lwir_ffc?1:0);
		result = prime * result + ((Integer) ffc_frames).hashCode();
		result = prime * result + ((Integer) ffc_wait_frames).hashCode();
		result = prime * result + ((Integer) ffc_groups).hashCode();
		result = prime * result + ((Double) acquire_delay).hashCode();
		result = prime * result + (avg_all?1:0);
		for (String ip:lwir_ips) {
			result = prime * result + ip.hashCode();
		}
		result = prime * result + eo_ip.hashCode();
		result = prime * result + CLTParameters.arr_to_str(lwir_channels).hashCode();
		result = prime * result + CLTParameters.arr_to_str(eo_channels).hashCode();
		result = prime * result + (lwir_telemetry?1:0);
		result = prime * result + (eo_full_window?1:0);
		result = prime * result + ((Double) eo_quality).hashCode();
		result = prime * result + (eo_scale?1:0);
		result = prime * result + (eo_autoexp?1:0);
		result = prime * result + ((Double) eo_max_autoexp_ms).hashCode();
		result = prime * result + ((Double) eo_exposure_ms).hashCode();
		result = prime * result + (eo_whitebal?1:0);
		result = prime * result + ((Double) eo_gain_g).hashCode();
		result = prime * result + ((Double) eo_gain_rg).hashCode();
		result = prime * result + ((Double) eo_gain_bg).hashCode();
		result = prime * result + CLTParameters.arr_to_str(eo_exp_corr).hashCode();
		result = prime * result + CLTParameters.arr_to_str(eo_gcorr_rbgb).hashCode();
		result = prime * result + ((Integer) lwir_trig_dly).hashCode();
		result = prime * result + CLTParameters.arr_to_str(selected_channels).hashCode();
		// next are not needed to be programmed to the cameras
//		result = prime * result + (new Integer(eo_lag)).hashCode();
//		result = prime * result + (new Double(max_mismatch_ms)).hashCode();
//		result = prime * result + (new Integer(max_frame_diff)).hashCode();
		return 0;
	}

	public void dialogQuestions(GenericJTabbedDialog gd) {
		// TODO: separate parameters into common and type -specific
		gd. addChoice("Camera name", CAMERA_NAMES, camera_name, "Camera type - now Talon (4*Lepton+4*MT9P006) and LWIR16 (16xBoson+4*MT9P006");

		gd.addNumericField("Number to average LWIR",this.avg_number,      0,3,"","Number of acquired consecutive LWIR images to average");
		gd.addNumericField("Number to average EO",  this.avg_number_eo,   0,3,"","Number of acquired consecutive EO images to average");
		gd.addNumericField("Number of sets to acquire",this.num_frames,   0,3,"","Total number, may be limited by EO quality/frame size (last will be missed/null)");
		gd.addCheckbox    ("Run FFC",       this.lwir_ffc, "Perform calibration before each measurements to average (Lepton takes ~1.6 sec, 15 frames), Boson - twice ffc_frames");

		String [] sffc_frames = new String [BOSON_FFC_FRAMES.length];
		for (int i = 0; i < sffc_frames.length; i++) sffc_frames[i]=""+BOSON_FFC_FRAMES[i]; 
		gd. addChoice     ("FFC frames", sffc_frames, ""+ffc_frames,"Number of frames to integrate during FFC (Full FFC takes 2x this number of frames)" );
		gd.addNumericField("FFC wait frames",this.ffc_wait_frames,        0,3,"","Wait this number of frames after FFC is supposed to finish");
		
		String [] sffc_groups = new String [FFC_GROUPS.length];
		for (int i = 0; i < sffc_groups.length; i++) sffc_groups[i]=""+FFC_GROUPS[i];
		gd. addChoice     ("FFC groups", sffc_groups, ""+ffc_groups,"Split FFC actions in groups to limit power consumption" );
		gd.addNumericField("Acquire delay", this.acquire_delay,  3,6,"s", "Schedule synchronous acquisition ahead of current master camera time");
		gd.addCheckbox    ("Average all",   this.avg_all, "Average all simultaneously acquired images (unchecked - only requested number to average)");
//		gd.addStringField ("LWIR IP",       this.lwir_ip, 20, "LWIR camera IP address");
		for (int i = 0; i < this.lwir_ips.length; i++) {
			gd.addStringField ("LWIR IP"+i,       this.lwir_ips[i], 20, "LWIR camera "+(i+((i==0)?" (master)":""))+" IP address");
		}
		gd.addStringField ("EO IP",         this.eo_ip, 20, "Visible range high resolution camera IP address");
		gd.addStringField ("LWIR channels", CLTParameters.arr_to_str(this.lwir_channels), 40, "Space-separated list of used LWIR camera channels, such as '0 1 2 3'");
		gd.addStringField ("EO channels", CLTParameters.arr_to_str(this.eo_channels), 20, "Space-separated list of used visible range camera channels, such as '0 1 2 3'");
		gd.addCheckbox    ("LWIR telemetry",this.lwir_telemetry, "Set LWIR sesnors to provide telemetry data in the last 2 lines (may become mandatory later)");
		gd.addCheckbox    ("EO full window",this.eo_full_window, "Set EO to full window, disregarding preset margins (need reset to restore original)");
		gd.addNumericField("EO quality",    this.eo_quality,  3,6,"%", "Visible range camera JPEG compression quality (all channels)");
		gd.addCheckbox    ("EO undo white balance",    this.eo_scale, "Undo in-camera white balancing");
		gd.addCheckbox    ("EO autoexposure",this.eo_autoexp, "Enable autoexposure for the visible range camera");
		gd.addNumericField("EO eo_max_autoexp_ms", this.eo_max_autoexp_ms,  3,6,"ms", "Visible range camera maximal exposure in autoexposure mode");
		gd.addNumericField("EO exposure",   this.eo_exposure_ms,  3,6,"ms", "Visible range camera exposure time (all channels)");
		gd.addCheckbox    ("EO white balance", this.eo_whitebal, "Enable automatic white balancing for the visible range camera");
		gd.addNumericField("EO gain G",     this.eo_gain_g,  3,6,"","Analog gain for green channel for all visible range camera channels (normally 2.0)");
		gd.addNumericField("EO gain R/G",   this.eo_gain_rg, 3,6,"","Red to green gain ratio for all visible range camera channels");
		gd.addNumericField("EO gain B/G",   this.eo_gain_bg, 3,6,"","Blue to green gain ratio for all visible range camera channels");
		gd.addStringField ("EO exposure corrections", CLTParameters.arr_to_str(this.eo_exp_corr), 50, "Fine corrections of channel exposures (4 channel relative exposures)");
		gd.addStringField ("EO gain corrections", CLTParameters.arr_to_str(this.eo_gcorr_rbgb), 100, "Fine corrections to per channel, per color gains:'r0 b0 gb0 r1 b1 gb1 ...'");
		gd.addNumericField("LWIR trig dly", this.lwir_trig_dly,     0,10,"x10ns","Output trigger delay, should eventually match Lepton+FPGA latency to trigger EO exactly 1 frame after LWIR. 0 does not work with current FPGA - usec do not match sec in transmitted timestamp");
		gd.addNumericField("EO lag",        this.eo_lag,     0,3,"","Visible camera lag (in frames) relative to LWIR one");
		gd.addNumericField("Max mismatch",  this.max_mismatch_ms,    3,6,"ms","Maximal mismatch between image timestamps. Larger mismatch requires LWIR sinsor reinitialization");
		gd.addNumericField("Max frame diff",this.max_frame_diff,     0,3,"","Maximal difference in frames between simultaneously acquired channels as calculated from the timestamps");
		gd.addNumericField("Debug level",   this.debug_level,        0,3,"","Image acquisition log level: -3: OFF, -2:FATAL, -1:ERROR, 0:WARN, 1:INFO, 2:DEBUG");
		gd.addStringField ("Selected channels", CLTParameters.arr_to_str(this.selected_channels), 40, "Space-separated channel selection (1 - selected, 0 - unselected)");
		gd.addCheckbox    ("Show images",   this.show_images, "Show acquired images after averaging)");
	}

	public void dialogAnswers(GenericJTabbedDialog gd) {
		String new_camera_name =       CAMERA_NAMES[gd.getNextChoiceIndex()];
		this.avg_number =        (int) gd.getNextNumber();
		this.avg_number_eo =     (int) gd.getNextNumber();
		this.num_frames =        (int) gd.getNextNumber();
		this.lwir_ffc =                gd.getNextBoolean();
		this.ffc_frames = BOSON_FFC_FRAMES[gd.getNextChoiceIndex()];
		
		this.ffc_wait_frames =   (int) gd.getNextNumber();
		this.ffc_groups =   FFC_GROUPS[gd.getNextChoiceIndex()];
		this.acquire_delay =           gd.getNextNumber();		
		this.avg_all =                 gd.getNextBoolean();
//		this.lwir_ip =                 gd.getNextString();
		for (int i = 0; i < this.lwir_ips.length; i++) {
			this.lwir_ips[i] =         gd.getNextString();
		}
		
		this.eo_ip =                   gd.getNextString();
		this.lwir_channels =           CLTParameters.str_to_iarr(gd.getNextString());
		this.eo_channels =             CLTParameters.str_to_iarr(gd.getNextString());
		this.lwir_telemetry =          gd.getNextBoolean();
		this.eo_full_window =          gd.getNextBoolean();
		this.eo_quality =              gd.getNextNumber();
		this.eo_scale =                gd.getNextBoolean();
		this.eo_autoexp =              gd.getNextBoolean();
		this.eo_max_autoexp_ms =       gd.getNextNumber();
		this.eo_exposure_ms =          gd.getNextNumber();
		this.eo_whitebal =             gd.getNextBoolean();
		this.eo_gain_g =               gd.getNextNumber();
		this.eo_gain_rg =              gd.getNextNumber();
		this.eo_gain_bg =              gd.getNextNumber();
		this.eo_exp_corr =             CLTParameters.str_to_darr(gd.getNextString());
		this.eo_gcorr_rbgb =           CLTParameters.str_to_darr(gd.getNextString());
		this.lwir_trig_dly =     (int) gd.getNextNumber();
		this.eo_lag =            (int) gd.getNextNumber();
		this.max_mismatch_ms =         gd.getNextNumber();
		this.max_frame_diff =    (int) gd.getNextNumber();
		this.debug_level =       (int) gd.getNextNumber();
		this.selected_channels =       CLTParameters.str_to_barr(gd.getNextString());
		this.show_images =             gd.getNextBoolean();
		if (!this.camera_name.equals(new_camera_name)) {
			this.camera_name = new_camera_name; // in the very end to to disturb the number of items  
			setDefaultChannels(); // modifies lwir_IPs, eo_ip, channels,
		}
		parameters_updated = true;
	}


	public boolean showJDialog() {
		GenericJTabbedDialog gd = new GenericJTabbedDialog("Set LWIR/EO parameters",800,900);
//		gd.addTab         ("General", "General parameters");
		dialogQuestions(gd);
		gd.showDialog();
		if (gd.wasCanceled()) return false;
		dialogAnswers(gd);
		return true;
	}


	public boolean is_updated() {
		return parameters_updated;
	}

	public void reset_updated() {
		parameters_updated = false;
	}

	public boolean [] getSelected(){
		return  selected_channels;
	}

	public boolean [] getSelectedLwir(){
		boolean [] sel = selected_channels.clone();
		for (int i = lwir_channels.length; i < sel.length; i++ ) {
			sel[i] = false;
		}
		return  sel;
	}

	public boolean [] getSelectedEo(){
		boolean [] sel = selected_channels.clone();
		for (int i = 0; i < lwir_channels.length; i++ ) {
			sel[i] = false;
		}
		return  sel;
	}

	public String [] getSourceFilesFlat(String [] sets, boolean[] channels) {
		int num_sel = 0;
		for (boolean s: channels) if (s) num_sel++;
		String [] files = new String [sets.length * num_sel];
		int indx = 0;
		for (String set:sets) {
			for (int i = 0; i < channels.length; i++) if (channels[i]) {
				String file_base = set;
				if (set.indexOf(Prefs.getFileSeparator()) >=0) {
					file_base = set.substring(set.lastIndexOf(Prefs.getFileSeparator())+1);
				}
			files[indx++] =set + Prefs.getFileSeparator()+ file_base+"_"+i+".tiff";
			}
		}
		return files;
	}


	// Image set may have different timestamps, only lwir0 matches
	public String [][] getSourceFiles(String [] sets, boolean[] channels) {
		String [][] files = new String [sets.length][channels.length];
		for (int nset= 0; nset < sets.length; nset++) {
			// read all files in the directory
			File  set_dir = new File(sets[nset]);
			File [] channel_files = set_dir.listFiles(new FilenameFilter() {
				@Override
				public boolean accept(File current, String name) {
					if (!(new File(current, name).isFile()) || !name.endsWith(".tiff")) return false;
					String base = name.substring(0, name.lastIndexOf(".tiff"));
					int undr = base.lastIndexOf("_");
					if (undr < 0) return false;
					int chn = -1;
					try {
						chn = Integer.parseInt(base.substring(undr + 1));
					} catch (Exception e) {

					}
					return chn >= 0;
				}
			});
			for (File f: channel_files) {
				String base = f.getName().substring(0, f.getName().lastIndexOf(".tiff"));
				int undr = base.lastIndexOf("_");
				int chn =  Integer.parseInt(base.substring(undr + 1));
				if ((chn >= 0) && (chn < channels.length) && channels[chn]) {
					files[nset][chn] = f.getPath();
				}
			}
		}
		return files;
	}



	// --- internal methods
	// Moved as static methods to CLTParameters
/*
	private String arr_to_str(int [] arr) {
		String s = "";
		for (int c:arr) s+=c+" ";
		return s.trim();
	}

	private String arr_to_str(double [] arr) {
		String s = "";
		for (double c:arr) s+=c+" ";
		return s.trim();
	}


	private String arr_to_str(boolean [] arr) {
		String s = "";
		for (boolean c:arr) s+= (c?1:0)+" ";
		return s.trim();
	}

	private String arr_to_str(String [] arr) {
		String s = "";
		for (int i = 0; i < arr.length; i++) {
			s += arr[i].trim();
			if (i < (arr.length - 1)){
				s += ",";
			}
		}
		return s;
	}
	
	private boolean [] str_to_barr(String s) {
		int [] iarr = str_to_iarr(s);
		if (iarr == null) return null;
		boolean [] barr = new boolean [iarr.length];
		for (int i = 0; i < barr.length; i++) {
			barr[i] = iarr[i] != 0;
		}
		return barr;
	}

	private int [] str_to_iarr(String s) {
		String [] sa;
		if (s.indexOf(",") >= 0) {
			sa = s.trim().split(",");
		} else {
			sa = s.trim().split(" ");
		}
		int [] iarr = new int [sa.length];
		for (int i = 0; i < sa.length; i++) {
			iarr[i]=Integer.parseInt(sa[i].trim());
		}
		return iarr;
	}

	private double [] str_to_darr(String s) {
		String [] sa;
		if (s.indexOf(",") >= 0) {
			sa = s.trim().split(",");
		} else {
			sa = s.trim().split(" ");
		}
		double [] darr = new double [sa.length];
		for (int i = 0; i < sa.length; i++) {
			darr[i]=Double.parseDouble(sa[i].trim());
		}
		return darr;
	}

	private String [] str_to_sarr(String s) {
		String [] sa;
		if (s.indexOf(",") >= 0) {
			sa = s.trim().split(",");
		} else {
			sa = s.trim().split(" ");
		}
		String [] sarr = new String [sa.length];
		for (int i = 0; i < sa.length; i++) {
			sarr[i]=sa[i].trim();
		}
		return sarr;
	}

*/
	public boolean [] selectSourceChannels(){
		return selectSourceChannels(selected_channels);
	}

	public boolean [] selectSourceChannels(boolean [] sel) {
		GenericJTabbedDialog gd = new GenericJTabbedDialog("Set CLT parameters",300,500);
		for (int i = 0; i < sel.length; i++) {
			gd.addCheckbox    ("Channel "+i,       sel[i], "Enable processing camera channel "+i);
		}
		gd.showDialog();
		if (gd.wasCanceled()) return null;
		for (int i = 0; i < sel.length; i++) {
			sel[i] = gd.getNextBoolean();
		}
    	return sel;
    }

}
