/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * Version : 15/04/2016 : SCA : V2.2 (H2020)
 *  => Add Z axis in propagation computation 
 * Version : 17/10/2018 : SCA : V2.2.5
 *  Redmine #6119 
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.radial;

import java.util.Date;

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

import fr.tlse.irap.pt.client.controller.common.Formatter;
import fr.tlse.irap.pt.client.services.ServiceFactory;
import fr.tlse.irap.spaceElement.SpaceElement;
import fr.tlse.irap.spaceElement.Sun;
import fr.tlse.irap.tool.radial.Radial;
import fr.tlse.irap.utils.CoordConverter;
import fr.tlse.irap.utils.ToolHelper;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class RadialStartModel {

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

	ObjectProperty<SpaceElement> startSource = new SimpleObjectProperty<SpaceElement>();
	StringProperty startTime = new SimpleStringProperty();
	StringProperty speed = new SimpleStringProperty();
	StringProperty deltaSpeed = new SimpleStringProperty();
	StringProperty deltaTime = new SimpleStringProperty();
	StringProperty longStart = new SimpleStringProperty();
	BooleanProperty dragModel = new SimpleBooleanProperty();
	StringProperty innerBoundary = new SimpleStringProperty();
	StringProperty asymptoticSpeed = new SimpleStringProperty();
	StringProperty dragParam = new SimpleStringProperty();
	
	SpaceElement src;
	Sun sun = new Sun();

	/**
	 * Set up start fields from model
	 * @param radialModel
	 */
	public void setUpViewWith(Radial radialModel) {
		if (radialModel != null) {
			setSpeed(Formatter.toDecimal(radialModel.getSpeedSolarWind()));
			setDeltaSpeed(Formatter.toDecimal(radialModel.getSpeedError()));
			setLongStart(Formatter.toDecimal1D(radialModel.getLongStartT()));
			setDragModel(radialModel.isDragModel());
			setInnerBoundary(Formatter.toDecimal(radialModel.getInnerBoundary()));
			setAsymptoticSpeed(Formatter.toDecimal(radialModel.getAsymptoticSpeed()));
			setDragParam(Formatter.toDecimal1D(radialModel.getDragParam()));
		}
	}

	/**
	 * Update Radial model from Start fields
	 * @param radialModel
	 */
	public void update(Radial radialModel) {
		radialModel.setSpeedSolarWind(Formatter.toDouble(speed.getValue()));
		radialModel.setSpeedError(Formatter.toDouble(deltaSpeed.getValue()));
		radialModel.setLongStartT(Formatter.toDouble(longStart.getValue()));
		radialModel.setDragModel(dragModel.getValue());
		radialModel.setInnerBoundary(Formatter.toDouble(innerBoundary.getValue()));
		radialModel.setAsymptoticSpeed(Formatter.toDouble(asymptoticSpeed.getValue()));
		radialModel.setDragParam(Formatter.toDouble(dragParam.getValue()));
	}

	public void compute(Radial r, Date date) {
		src = ServiceFactory.getPositionService().getPositionElement(startSource.get(), date);
		if (src != null) {
			r.setStartTime(date);
			//log.debug("===> Start time is " + r.getStartTime().getTime());
			if (startIsSun()) {
				// LongStartT = HAE longitude attitude sun (T)
				r.setLongStartT(src.getAttitude()[0]);
				r.setLatStartT(src.getAttitude()[1]);
			} else {
				// LongStartT = HAE longitude start (T)
				double[] longLat = CoordConverter.toSpheric(src.getX(), src.getY(), src.getZ());
				r.setLongStartT(longLat[0]);
				r.setLatStartT(longLat[1]);
			}
			//log.debug("Computed LongStartT = " + r.getLongStartT());
	
			// Radial distance away SUN of START point.
			double radialDistanceStart = ToolHelper.computeRadialDistance(src);
			r.setRadialDistanceStart(radialDistanceStart);
			//log.debug("radialDistanceStart=" + CoordConverter.toAU(radialDistanceStart) + " AU");
			// compute source
			computeSource(r);
		}
	}

	/**
	 * 
	 * Compute Start fields from Carrington source 
	 */
	public void computeFromSource(Radial radial) {
		if (radial.startIsSun()) {
			radial.setLongStartT(CoordConverter.normalizeLongitude(radial.getSunAttitudeTSun()[0] + radial.getLongSource()));
			radial.setLatStartT(CoordConverter.normalizeLongitude(radial.getSunAttitudeTSun()[1] + radial.getLatSource()));
		}
	}

	/**
	 * Compute carrington source from start fields and a new date  
	 * @param radial
	 */
	public void computeSource(Radial radial, Date date) {
		src = ServiceFactory.getPositionService().getPositionElement(startSource.get(), date);
		if (src != null) {
			computeSource(radial);
		}
	}

	/**
	 * Compute carrington source from start fields  
	 * @param radial
	 */
	public void computeSource(Radial radial) {
		// compute Sun time
		computeSunTime(radial);
		
		if (radial.startIsSun()) {
			radial.setLongSource(CoordConverter.normalizeLongitude(radial.getLongStartT() - radial.getSunAttitudeTSun()[0]));
			radial.setLatSource(CoordConverter.normalizeLatitude(radial.getLatStartT() - radial.getSunAttitudeTSun()[1]));
		}
		else {
			// compute long source
			log.debug("Get position of " + radial.getStartElement().getName() + " at sun time " + radial.getSunTime().getTime());
			SpaceElement e = ServiceFactory.getPositionService().getPositionElement(radial.getStartElement(), radial.getSunTime().getTime());
			if (e != null) {
				double[] longLat = CoordConverter.rectangularToCarrington(e.getX(), e.getY(), e.getZ(), radial.getSunAttitudeTSun());
				radial.setLongSource(longLat[0]);
				radial.setLatSource(longLat[1]);
			}
		}
	}

	/**
	 * Compute sun time and attitude sun (used by Carrington and JMap components)
	 * @param r
	 */
	private void computeSunTime(Radial r) {
		if (r.startIsSun()) {
			// Sun time = start time => attitude at sun time = attitude at start time
			r.setSunTime(r.getStartTime().getTime());
			// Get attitude sun at tSun
			r.setSunAttitudeTSun(src.getAttitude());
		}
		else {
			// Compute Sun time
			double initialSeconds = r.getStartTime().getTimeInMillis() / 1000.0;
			double sunSeconds = initialSeconds - (r.getRadialDistanceStart() / r.getSpeedSolarWind());
			Date sunTime = new Date();
			sunTime.setTime((long)sunSeconds * 1000);
			r.setSunTime(sunTime);
			sun = (Sun) ServiceFactory.getPositionService().getPositionElement(sun, sunTime);
			if (sun != null) {
				r.setSunAttitudeTSun(sun.getAttitude());
			}
		}
		log.info("===> Sun time is " + r.getSunTime().getTime()+" at "+r.getStartTime().getTime()+" speed="+r.getSpeedSolarWind());
	}

	public StringProperty getStartTime() {
		return startTime;
	}
	public void setStartTime(String startTime) {
		this.startTime.set(startTime);
	}
	public StringProperty getSpeed() {
		return speed;
	}
	public void setSpeed(String speed) {
		this.speed.set(speed);
	}
	public StringProperty getDeltaSpeed() {
		return deltaSpeed;
	}
	public void setDeltaSpeed(String deltaSpeed) {
		this.deltaSpeed.set(deltaSpeed);
	}
	public StringProperty getDeltaTime() {
		return deltaTime;
	}
	public void setDeltaTime(String deltaTime) {
		this.deltaTime.set(deltaTime);
	}
	public StringProperty getLongStart() {
		return longStart;
	}
	public void setLongStart(String longStart) {
		this.longStart.set(longStart);
	}

	public StringProperty getDragParam() {
		return dragParam;
	}

	public void setDragParam(String dragParam) {
		this.dragParam.set(dragParam);
	}

	public StringProperty getAsymptoticSpeed() {
		return asymptoticSpeed;
	}

	public void setAsymptoticSpeed(String asymptoticSpeed) {
		this.asymptoticSpeed.set(asymptoticSpeed);
	}

	public ObjectProperty<SpaceElement> getStartSource() {
		return startSource;
	}

	public void setStartSource(SpaceElement startSource) {
		this.startSource.set(startSource);
	}

	public Boolean startIsSun() {
		try {
			return (this.startSource.get() instanceof Sun);
		}
		catch (Exception e){
			return false;
		}
	}

	public StringProperty getInnerBoundary() {
		return innerBoundary;
	}

	public void setInnerBoundary(String innerBoundary) {
		this.innerBoundary.set(innerBoundary);
	}

	public BooleanProperty getDragModel() {
		return dragModel;
	}

	public void setDragModel(Boolean dragModel) {
		this.dragModel.set(dragModel);
	}
}
