/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.drawingPlane;

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

import fr.tlse.irap.pt.client.controller.common.Position2D;
import fr.tlse.irap.utils.CoordConverter;
import fr.tlse.irap.utils.ToolHelper;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;

public class DrawingSpiral {

	private static int DEFAULT_NBPOS_CYCLE = 30;
	private static double MIN_SPREAD = 1.0;

	private Polygon spiral;
	private double spread;
	private double startAngle;
	private double rotationPeriod;
	private double speed;
	private double footPointLong; // HAE footpoint longitude
	private double positionMax;

	public DrawingSpiral() {
		createSpiral();
		startAngle = 0;
	}

	public void drawWith(double xMax) {
		setPositionMax(xMax);
		setStartAngle(DrawHelper.HAEToHEE(footPointLong));
		drawSpiral();
	}

	/**
	 * compute delta positions between spiral From and spiral To
	 * @param xPosMax
	 */
	private void drawSpiral() {
		double xMax = positionMax * 0.95;
		// max 60 PI cycles for spiral To
		int nbMaxPos = 60 * DEFAULT_NBPOS_CYCLE;
		List<Position2D> posSpiralTo = computeSpiralPositions(nbMaxPos, speed, startAngle + spread, rotationPeriod, xMax);
		// compute number of positions for spiral From
		double delta = spread / 90.0 * DEFAULT_NBPOS_CYCLE;
		nbMaxPos = posSpiralTo.size() - (int)delta;
		List<Position2D> posSpiralFrom = computeSpiralPositions(nbMaxPos, speed, startAngle - spread, rotationPeriod, xMax);

		modifySpiralWith(posSpiralFrom, posSpiralTo);
	}

	/**
	 * Create Parker's spiral
	 */
	private void createSpiral() {
		spiral = new Polygon();
		spiral.setFill(Color.web("48ACFA"));
		spiral.setId("SPIRAL");
		// Spiral not visible by default
		spiral.setVisible(false);
	}

	/**
	 * Update spiral
	 * @param positions
	 */
	private void modifySpiralWith(List<Position2D> posSpiralFrom, List<Position2D> posSpiralTo) {
		spiral.getPoints().clear();
		// spiral from
		for (Position2D pos : posSpiralFrom) {
			spiral.getPoints().add(pos.getX());
			spiral.getPoints().add(pos.getY());
		}
		// spiral to
		Position2D pos;
		for (int i = posSpiralTo.size() - 1; i >= 0; i--) {
			pos = posSpiralTo.get(i);
			spiral.getPoints().add(pos.getX());
			spiral.getPoints().add(pos.getY());
		}
	}

	/**
	 * Calculate the positions of the spiral (see §2.2 PropagationTool_Algo20120926.pdf)
	 * Equation spiral 3D (theta = f(rho)) : rho*rc/re = vp/(omega*re) * (theta-theta0)*sin(phi)
	 * For phi = pi/2, Equation spiral 2D on Ecliptic plane is : rho*rc/re = vp/(omega*re) * (theta-theta0)
	 * 
	 * @param Number of positions to calculate
	 * @param Speed of solar wind, expressed in km/s-1
	 * @param Longitude of footpoint of spiral, expressed in degrees
	 * @param Sun rotation period, expressed in day
	 * @return List of positions (Cartesian coordinates)
	 */
	private static List<Position2D> computeSpiralPositions(int nbPos, Double speed, Double footpointLong, Double rotationPeriod, double xPosMax) {
		// Solar wind speed, expressed in rs/day
		double vp = speed * 0.124138; 
		// Footpoint of spiral, expressed in radians
		double theta0 = Math.toRadians(footpointLong);
		// Rotation frequency, expressed in rs/day
		double omegaRadians = (2 * Math.PI) / rotationPeriod;
		// Coefficient A to turn the spiral, expressed in rs
		double coeffA = ToolHelper.rC / ToolHelper.rE;
		// Coefficient B to determine distance between arms of spiral expressed in 1/radians
		double coeffB = vp / (omegaRadians * ToolHelper.rE);
		// get distance of 1 AU in Ecliptic unity 
		double dist = CoordConverter.toEcliptic(CoordConverter.toKm(1.0)) * DrawHelper.INIT_SCALE * DrawHelper.getScale();

		// Calculate spiral positions
		List<Position2D> positions = new ArrayList<Position2D>();
		double xPos = 0;
		double yPos = 0;
		double theta = 0.0;
		double rho = 0.0;
		double nbPosByCycle = DEFAULT_NBPOS_CYCLE;
		int index = 0;

		// Equation f(rho, ln(rho)) : rho*rc/re - 1 - ln(rho*rc/re) = vp/(omega*re) * (theta-theta0)*sin(phi)
		while (index < nbPos && Math.abs(xPos) < xPosMax) {
			// Polar coordinates (rho, theta)
			// theta from theta0 to theta0 - PI
			theta = theta0 - (index / nbPosByCycle) * Math.PI;
			rho = coeffA + (coeffB * (theta0 - theta));
			rho = rho * dist;
			// Cartesian coordinates (x,y)
			xPos = rho * Math.cos(theta);
			yPos = rho * Math.sin(theta);
			// Y axis inversion
			yPos = -yPos;
			index++;
			// Store cartesian coordinates in returned array
			positions.add(new Position2D(xPos, yPos));
		}

		// Equation with ln : rho*rc/re - 1 - ln(rho*rc/re) = vp/(omega*re) * (theta-theta0)sin(phi)
		//double rho = 0.01;
		//double d;
		//while (Math.abs(xPos) < xPosMax) {
		//	// Polar coordinates (rho, theta)
		//	theta = (coeffA*rho - 1 - Math.log(coeffA*rho))/coeffB - theta0;
		//	d = rho * dist;
		//	// Cartesian coordinates (x,y)
		//	xPos = d * Math.cos(theta);
		//	yPos = d * Math.sin(theta);
		//	// Y axis inversion
		//	yPos = -yPos;
		//	rho+=0.1;
		//	// Store cartesian coordinates in returned array
		//	positions.add(new Position(xPos, yPos));
		//}

		return positions;
	}

	public void show() {
		if (!spiral.isDisable()) {
			spiral.setVisible(true);
		}
	}

	public void hide() {
		spiral.setVisible(false);
	}

	public boolean isVisible() {
		return spiral.isVisible();
	}

	public void setDisable(boolean disable) {
		spiral.setDisable(disable);
	}

	public Polygon getSpiral() {
		return spiral;
	}

	public double getStartAngle() {
		return startAngle;
	}

	public void setStartAngle(double startAngle) {
		this.startAngle = startAngle;
	}

	public double getRotationPeriod() {
		return rotationPeriod;
	}

	public void setRotationPeriod(double rotationPeriod) {
		this.rotationPeriod = rotationPeriod;
	}

	public double getSpeed() {
		return speed;
	}

	public void setSpeed(double speed) {
		this.speed = speed;
	}

	public double getSpread() {
		return spread;
	}

	public void setSpread(double spread) {
		if (spread > 0) {
			this.spread = spread;
		} else {
			this.spread = MIN_SPREAD;
		}
	}

	public double getPositionMax() {
		return positionMax;
	}

	public void setPositionMax(double positionMax) {
		this.positionMax = positionMax;
	}

	public double getFootPointLong() {
		return footPointLong;
	}

	public void setFootPointLong(double footPointLong) {
		this.footPointLong = footPointLong;
	}
}
