/* ----------------------------------------
 * 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 : 06/04/2023 : LBE : V2.7
 * DM-ID : redmine#8642 : 06/04/2023 : Added 3D shockwave
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.carrington;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fr.tlse.irap.pt.client.controller.common.Position2D;
import fr.tlse.irap.fieldLine.Footpoint;
import fr.tlse.irap.pos.Position;
import fr.tlse.irap.shock.Shock;
import fr.tlse.irap.utils.CoordConverter;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;

public class CarringtonShockModel {

	protected static final Logger log = LoggerFactory.getLogger(CarringtonShockModel.class);

	private static final float CARRINGTON_DAYS = 27.2753f;

	private boolean isValid;
	private BooleanProperty hasChanged = new SimpleBooleanProperty();

	// data in degrees
	private List<Shock> shocks;
	private List<List<Footpoint>> shockPts = new ArrayList<List<Footpoint>>();
	private int indexShock = 0;
	//private Date sunTime;
	private double[] attSun;
	
	public CarringtonShockModel() {
	}

	/**
	 * Update view and compute positions of Shocks
	 * @param date 
	 */
	public void updateView() {
		try {
			// Compute Carrington coordinates of projected ellipse shock.  
			computeEllipse(indexShock);
			
			// notify change
			isValid = true;
			hasChanged.set(false);
			hasChanged.set(true);
			
		} catch (NumberFormatException e) {
			isValid = false;
		}
	}

	/**
	 * init view  
	 */
	public void initView(Date startTime) {
		//log.debug("Init from : " + startTime.toString());
		for (Shock shock : shocks) {
			//log.debug("Loop");
			if (shock.getShockTime().getTime()/1000 - startTime.getTime()/1000 < 86400 * CARRINGTON_DAYS) { 
				//log.debug("Add shockPoints");
				shockPts.add(new ArrayList<Footpoint>());
			}
		}
		attSun = CarringtonHelper.getAttitudeSun(startTime);
		// init view at step 0
		indexShock = 0;
		updateView();
	}

	/**
	 * update view with a new step 
	 */
	public void updateViewWith(int step) {
		//log.debug("step = " + step);
		indexShock = step;
		updateView();
	}

	public int getSteps() {
		return shockPts.size();
	}
	
	public Date getShockDate(int step) {
		return shocks.get(step).getShockTime();
	}
	
	/**
	 * Compute ellipse of current index
	 */
	private void computeEllipse(int iShock) {

		if (shockPts.size() > 0) {
			List<Footpoint> pts = shockPts.get(iShock);
			if (pts.isEmpty()) {
				//log.debug("Compute ellipse points at index " + iShock);
				Shock shock = shocks.get(iShock);
				double rdSun = shock.getRdSun();
				double b = shock.getRadiusY();
				double c = shock.getRadiusZ();
				
				// Get intersected points (x, y, z) between ellipse shock and plane defined by normal (phi, theta)
				List<Position> positions = new ArrayList<Position>();
				// add center
				positions.add(new Position(rdSun, 0, 0));  
				//log.debug("Add point : " + CoordConverter.toAU(rdSun) + ", " + 0 + "," + 0);
				int iMax = 30;
				for (int i=0; i<=iMax; i++) {
					double y = b*i/iMax;
					double z = c * Math.sqrt(1 - (y/b)*(y/b));
					//log.debug("Add point : " + CoordConverter.toAU(rdSun) + ", " + CoordConverter.toAU(y) + "," + CoordConverter.toAU(z));
					positions.add(new Position(rdSun, y, z));
					positions.add(new Position(rdSun, y, -z));
					positions.add(new Position(rdSun, -y, -z));
					positions.add(new Position(rdSun, -y, z));
				}
				// Transform these points in HAE then in Carrington coordinate system 
				//log.debug("Transform in HAE using theta = " + shock.getTheta() + ", and phi = " + shock.getPhi());
				double cosT = Math.cos(Math.toRadians(shock.getTheta()));
				double cosP = Math.cos(Math.toRadians(shock.getPhi()));
				double sinT = Math.sin(Math.toRadians(shock.getTheta()));
				double sinP = Math.sin(Math.toRadians(shock.getPhi()));
				for (Position pos : positions) {
					// Transform these cartesian points in Ecliptic coordinate system (HAE) => (x', y', z')
					// using inverse rotation matrix around Z and Y axis
					double x = pos.getX()*cosT*cosP + pos.getY()*sinP - pos.getZ()*cosP*sinT;
					double y = -pos.getX()*cosT*sinP + pos.getY()*cosP + pos.getZ()*sinP*sinT;
					double z = pos.getX()*sinT + pos.getZ()*cosT;
					//log.debug("Points in HAE : " + x + ", " + y + "," + z);
					// Transform in Carrington coordinate system using attitude sun at Carrington time
					double[] lonLat = CoordConverter.rectangularToCarrington(x, y, z, attSun);
					// add lon/co-lat spheric coordinates in degrees
					//log.debug("Add long/lat points " + lonLat[0] + ", " + lonLat[1]);
					pts.add(new Footpoint((float)lonLat[0], (float)lonLat[1]));
				}
			}
		}
	}

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

	/**
	* Get view positions (x, y) from current shock points
	*/
	private List<Position2D> getPositions(int index) {
		List<Position2D> positions = new ArrayList<Position2D>();
		double x, y;
		if (shockPts != null && shockPts.size() > 0) {
			List<Footpoint> footpoints = shockPts.get(index);
			for (Footpoint fp : footpoints) {
				x = CarringtonHelper.longToX((double)fp.getLon());
				y = CarringtonHelper.latToY((double)fp.getLat());
				positions.add(new Position2D(x, y));
			}
		}
		return positions;
	}

	public boolean isValid() {
		return isValid;
	}

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

	public BooleanProperty getHasChanged() {
		return hasChanged;
	}

	public int getIndexShock() {
		return indexShock;
	}

	public void setIndexShock(int indexShock) {
		this.indexShock = indexShock;
	}

	/**
	 * @return the shocks
	 */
	public List<Shock> getShocks() {
		return shocks;
	}

	/**
	 * @param shocks the shocks to set
	 */
	public void setShocks(List<Shock> shocks) {
		this.shocks = shocks;
	}

	public void clearView() {
		if (shocks != null) {
			shocks.clear();
		}
		//setShocks(null);
		while (shockPts.size() > 0) {
			shockPts.get(0).clear();
			shockPts.remove(0);
		}
		isValid = false;
		// notify change
		hasChanged.set(false);
		hasChanged.set(true);

	}

}
