/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * Version : 09/04/2018 : SCA : V2.2.4
 *  Redmine DMPT4
 * Version : 29/06/2022 : LBE : V2.5
 * DM-ID : Redmine 9229: PFSS mode
 * Version : 05/08/2024 : LBE : V2.11
 * DM-ID : Redmine#l0763 : 05/08/2024 : Field lines menu removed
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.carrington;

import java.util.ArrayList;
import java.util.List;

import fr.tlse.irap.pt.client.controller.common.Formatter;
import fr.tlse.irap.pt.client.controller.common.Position2D;
import fr.tlse.irap.fieldLine.Footpoint;
import fr.tlse.irap.fieldLine.Footpoints;
import fr.tlse.irap.utils.CoordConverter;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class CarringtonCMEModel {
	private static double DEFAULT_EXTENT = 45;
	
	private boolean isValid;
	private BooleanProperty hasChanged = new SimpleBooleanProperty();

	// data in degrees
	private StringProperty longSource = new SimpleStringProperty();
	private StringProperty latSource = new SimpleStringProperty();
	private StringProperty longExtent = new SimpleStringProperty();
	private DoubleProperty fpRadius = new SimpleDoubleProperty();
	private double longExt;
	private double latExt;

	private static MTreeNode treeFieldLines;
	
	// data in radian	
	private double phiSrc = 0.0;
	private double thetaSrc = Math.toRadians(90.0); // co-latitude of src
	private double phiExt = 0.0;
	private double thetaExt = Math.toRadians(90.0 - DEFAULT_EXTENT); // co-latitude of ext

	private double extent = Math.toRadians(DEFAULT_EXTENT);
	private double radius = 2.5;
	private List<Footpoints> fieldLines = new ArrayList<Footpoints>();
	private int indexFieldLine = 0;
	
	public CarringtonCMEModel() {
	}

//	public static MTreeNode getTreeFieldLines() {
//		
//		if (treeFieldLines == null) {
//			treeFieldLines = new MTreeNode("Field lines");
//		
//			// create model nodes
//			MTreeNode modelNode = new MTreeNode();
//			modelNode.setName("PFSS");
//			modelNode.setDesc("PFSS");
//			modelNode.setParent(treeFieldLines);
//			treeFieldLines.getChildren().add(modelNode);
//			// create source nodes
//			MTreeNode sourceNode = new MTreeNode();
//			sourceNode.setName("WSO");
//			sourceNode.setDesc("WSO");
//			sourceNode.setParent(modelNode);
//			//modelNode.getChildren().add(sourceNode); //not used anymore
//                        /** not CME related but put here for convenience */
//			//PFFS cubes not ready, disable for this delivery
//                        sourceNode = new MTreeNode();
//			sourceNode.setName("BEST_OF");
//			sourceNode.setDesc("Best cube");
//			sourceNode.setParent(modelNode);
//			modelNode.getChildren().add(sourceNode);
//                        
//		}
//		return treeFieldLines;
//	}

	/**
	 * Update view and compute positions of CME
	 * @param date 
	 */
	public void updateView() {
		try {
			// Update extremity point from view 
			longExt = Double.parseDouble(longSource.getValue()) + Double.parseDouble(longExtent.getValue())/2;
			latExt = 0.0;

			// Update data from view (source, extremity)
			phiSrc = Math.toRadians(Double.parseDouble(longSource.getValue()));
			thetaSrc = Math.toRadians(90.0 - Double.parseDouble(latSource.getValue()));
			phiExt = Math.toRadians(longExt);
			thetaExt = Math.toRadians(90.0 - latExt);
			extent = Math.toRadians(Double.parseDouble(longExtent.getValue()) / 2);
			radius = fpRadius.getValue();

			// Compute footpoints positions at 2.5 RS 
			computeFootpoints();
			
			// notify change
			isValid = true;
			hasChanged.set(false);
			hasChanged.set(true);
			
		} catch (NumberFormatException e) {
			isValid = false;
		}
	}

	/**
	 * Update model from click event and compute extent
	 */
	public void updateExtent() {
		try {
			// Update data from view (source, extremity)
			phiSrc = Math.toRadians(Double.parseDouble(longSource.getValue()));
			thetaSrc = Math.toRadians(90.0 - Double.parseDouble(latSource.getValue()));
			phiExt = Math.toRadians(longExt);
			thetaExt = Math.toRadians(90.0 - latExt);

			// Compute extent CME
			extent = computeLengthCME(phiSrc, thetaSrc, phiExt, thetaExt);
			setLongExtent(CoordConverter.normalizeLongitude(2.0 * Math.toDegrees(extent)));

			isValid = true;

		} catch (NumberFormatException e) {
			isValid = false;
		}
	}

	/**
	 * update view with a new radius 
	 */
	public void updateViewWith(double radius) {
		setFpRadius(radius);
		indexFieldLine = 0;
		if (radius < 2.5) {
			indexFieldLine = 1;
		}
		// notify change
		isValid = true;
		hasChanged.set(false);
		hasChanged.set(true);
	}

	/**
	 * Update view and compute data from another model
	 * @param longSource : Carrington longitude source
	 * @param longExtent : Delta longitude extent
	 */
	public void updateViewWith(double longSource, double longExtent) {
		// update data from another model
		setLongSource(longSource);
		setLatSource(0.0);
		setLongExtent(longExtent);
		setFpRadius(2.5);
		// update view and compute positions of CME
		updateView();
	}
	
	/**
	 * Compute CME positions at 2.5 RS 
	 */
	public void computeFootpoints() {
		double phi, theta;
		long length;
		long extentRef = Math.round(Math.toDegrees(extent));

		// compute positions of start FieldLine (at 2.5 RS)
		clearFieldLines();
		if (fieldLines.size() == 0) {
			fieldLines.add(new Footpoints());
		}
		indexFieldLine = 0;
		List<Footpoint> footpoints = fieldLines.get(indexFieldLine).getFootpoints();
		footpoints.clear();
		
		for (int lon = 0; lon < 360; lon++) {
			phi = Math.toRadians(lon);
			for (int lat = 0; lat < 360; lat++) {
				theta = Math.toRadians(lat / 2.0);
				double coLat = 90.0 - (double) lat / 2.0;
				length = Math.round(Math.toDegrees(computeLengthCME(phiSrc, thetaSrc, phi, theta)));
				if (length == extentRef || length <= 2) {
					// add log/co-lat coordinates in degrees
					footpoints.add(new Footpoint((float)lon, (float)coLat));
				}
			}
		}
	}

	/**
	 * Computation of CME length longitude (cf. algo IRAP)
	 * 
	 * @param phiLaunch (radian)
	 * @param thetaLaunch (radian)
	 * @param phiExt (radian)
	 * @param thetaExt (radian)
	 * @return : CME length longitude in radian
	 */
	private static double computeLengthCME(double phiLaunch, double thetaLaunch, double phiExt, double thetaExt) {
		double lengthCME = Math.acos(Math.cos(thetaLaunch) * Math.cos(thetaExt) 
				+ (Math.sin(thetaLaunch) * Math.sin(thetaExt) * Math.cos(phiExt - phiLaunch)));
		return lengthCME;
	}

	public StringProperty getLongExtent() {
		return longExtent;
	}

	public void setLongExtent(double longExtent) {
		this.longExtent.set(Formatter.toDecimal1D(longExtent));
	}

	public StringProperty getLongSource() {
		return longSource;
	}

	public void setLongSource(double longSource) {
		this.longSource.set(Formatter.toDecimal1D(longSource));
	}

	public StringProperty getLatSource() {
		return latSource;
	}

	public void setLatSource(double latSource) {
		this.latSource.set(Formatter.toDecimal1D(latSource));
	}

	public DoubleProperty getFpRadius() {
		return fpRadius;
	}

	public void setFpRadius(double fpRadius) {
		this.fpRadius.set(fpRadius);
	}

	public boolean isValid() {
		return isValid;
	}

	public void setValid(boolean isValid) {
		this.isValid = isValid;
	}

	public Double getSrcLong() {
		try {
			return Double.parseDouble(longSource.get());
		}
		catch (Exception e) {
			return null;
		}
	}

	public Double getSrcLat() {
		try {
			return Double.parseDouble(latSource.get());
		}
		catch (Exception e) {
			return null;
		}
	}

	public Double getLongExt() {
		return longExt;
	}

	public void setLongExt(double longExt) {
		this.longExt = longExt;
	}

	public Double getLatExt() {
		return latExt;
	}

	public void setLatExt(double latExt) {
		this.latExt = latExt;
	}

	public BooleanProperty getHasChanged() {
		return hasChanged;
	}

	// get default positions
	public List<Position2D> getPositions() {
		return getPositions(indexFieldLine);
	}

	public List<Position2D> getPositions(int index) {
		List<Position2D> positions = new ArrayList<Position2D>();
		double x, y;
		if (fieldLines.size() > 0) {
			Footpoints fpStart = fieldLines.get(index);
			for (Footpoint fp : fpStart.getFootpoints()) {
				x = CarringtonHelper.longToX((double)fp.getLon());
				y = CarringtonHelper.latToY((double)fp.getLat());
				positions.add(new Position2D(x, y));
			}
		}
		return positions;
	}

	public int getIndexFieldLine() {
		return indexFieldLine;
	}

	public void setIndexFieldLine(int indexFieldLine) {
		this.indexFieldLine = indexFieldLine;
	}

	/**
	 * Clear field lines = remove all field lines except first one 
	 */
	public void clearFieldLines() {
		while (fieldLines.size() > 1) {
			fieldLines.remove(1);
		}
	}

	public boolean hasFieldLines() {
		return fieldLines.size() > 1;
	}

	public List<Footpoints> getFieldLines () {
		return fieldLines;
	}
 
}
