/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 15/09/2016 : SCA : V2.2 (PSWS-H2020)
 *  creation
 * Version : 17/10/2018 : SCA : V2.2.5
 *  Redmine #6119 
 * ----------------------------------------
 */
package fr.tlse.irap.server.serviceTool.impl;

import java.util.TimeZone;

import org.apache.log4j.Logger;

import fr.tlse.irap.server.serviceDAO.ISpiceDAO;
import fr.tlse.irap.server.serviceTool.ISepTool;
import fr.tlse.irap.spaceElement.SpaceElement;
import fr.tlse.irap.tool.sep.Sep;
import fr.tlse.irap.tool.sep.Sep.Particle;
import fr.tlse.irap.tool.sep.SepEnd;
import fr.tlse.irap.utils.CoordConverter;
import fr.tlse.irap.utils.ToolHelper;

public class SepToolImpl implements ISepTool {

	private static final Logger logger = Logger.getLogger(SepToolImpl.class);
	private ISpiceDAO spiceDAO;
	
	public SepToolImpl() {
	}

	public SepToolImpl(ISpiceDAO iSpiceDAO) {
		setSpiceDAO(iSpiceDAO);
	}

	public ISpiceDAO getSpiceDAO() {
		return spiceDAO;
	}
	public void setSpiceDAO(ISpiceDAO spiceDAO) {
		this.spiceDAO = spiceDAO;
	}

	/**
	 * Compute sep propagation from a start to end element
	 * @param sep
	 * @param endElement
	 * @return sepEnd containing result of propagation (time, ...)
	 */
	@Override
	public SepEnd computeSep(Sep sep, SpaceElement endElement) throws Exception {
		// set default timezone UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));        
		
        SepEnd sepEnd = new SepEnd();
		logger.debug("Get position of " + endElement.getName() + " at Start time " + sep.getStartTime().getTime());
		SpaceElement e = spiceDAO.getPositionElement(endElement, sep.getStartTime().getTime());
		if (e != null) {
			sepEnd.setEndSource(e);
			sepEnd.setDistEnd(ToolHelper.computeRadialDistance(e));
			// compute end time
			computePropagation(sep, sepEnd);
			computeDeltaTimes(sep, sepEnd);
			e = spiceDAO.getPositionElement(endElement, sepEnd.getEndTime().getTime());
			if (e != null) {
				// compute end at final time
				sepEnd.setEndSource(e);
				computeEnd(sep, sepEnd);
				sepEnd.setImpacted(isInsideSpiral(sep, sepEnd));
			}
		}
		return sepEnd;

	}

	/**
	 * Compute SEPs propagation times and errors: we need to first calculate the length of the spiral between the Sun
	 * and the spacecraft and then the speed along the magnetic field line.
	 * @param sep
	 * @param sepEnd
	 */
	private void computePropagation(Sep sep, SepEnd sepEnd) {
		// Compute the length of the Parker spiral between START and END, see equation (4)
		double spiralLengthEnd=0.0;
		if (!sepEnd.isSun()) {
			spiralLengthEnd = ToolHelper.computeSpiralLength(sep.getSpeedSolarWind(), CoordConverter.toAU(sepEnd.getDistEnd()), sep.getRotationPeriod());
		}
		double deltaLength = spiralLengthEnd - sep.getSpiralLengthStart();
		// Compute the propagated time from start time t and the length of the spiral between START and END. See equation (8)
		sepEnd.setDeltaTime(ToolHelper.computePropagationTime(sep.getParticleEnergy(), sep.getParticle(), deltaLength));
		double omega = Math.toDegrees(ToolHelper.getRotationFrequency(sep.getRotationPeriod()));
		sepEnd.setDeltaLong(CoordConverter.normalizeLongitude(omega * sepEnd.getDeltaTime() * 60));
		sepEnd.setLength(Math.abs(deltaLength));
		// set final time
		double ti = sep.getStartTime().getTimeInMillis() / 1000.0;
		double tf = ti + (sepEnd.getDeltaTime() * 60);
		sepEnd.getEndTime().setTimeInMillis((long)tf * 1000);
	}

	/**
	 * Compute delta times associated with different errors (in hours)
	 * see equations (6) and (7) from annex A on 29/10/2012
	 * @param sep
	 * @param sepEnd
	 */
	private void computeDeltaTimes(Sep sep, SepEnd sepEnd) {
		double v = sep.getSpeedSolarWind();
		double vError = sep.getSpeedError();
		double rp = sep.getRotationPeriod();
		double rs = CoordConverter.toAU(sep.getRadialDistanceStart());
		double re = CoordConverter.toAU(sepEnd.getDistEnd());
		double pe = sep.getParticleEnergy();
		Particle p = sep.getParticle();

//		// Earliest arrival time associated with error in departure time, expressed in minutes.
//		sepEnd.setDtMin((sep.getTimeError() == 0.0 ? 0.0 : -sep.getTimeError()));
//		// Latest arrival time associated with error in departure time
//		sepEnd.setDtMax(sep.getTimeError());
		// Min error on spiral length between start and end
		sepEnd.setDlMin(Math.abs(ToolHelper.computeSpiralLength(v + vError, rs, rp) - ToolHelper.computeSpiralLength(v + vError, re, rp)));
		// Max error on spiral length between start and end
		sepEnd.setDlMax(Math.abs(ToolHelper.computeSpiralLength(v - vError, rs, rp) - ToolHelper.computeSpiralLength(v - vError, re, rp)));
		// Earliest arrival time associated with error in spiral length between START and END itself related to error in speed
		double tmpDelta = Math.abs(Math.abs(sepEnd.getDeltaTime()) - ToolHelper.computePropagationTime(pe, p, sepEnd.getDlMin()));
		sepEnd.setDtMinL(tmpDelta == 0.0 ? 0.0 : -tmpDelta);
		// latest arrival time associated with error in spiral length between START and END itself related to error in speed
		sepEnd.setDtMaxL(Math.abs(ToolHelper.computePropagationTime(pe, p, sepEnd.getDlMax()) - Math.abs(sepEnd.getDeltaTime())));
	}

	/**
	 * Compute End fields at final time 
	 * @param sep
	 * @param sepEnd
	 */
	private void computeEnd(Sep sep, SepEnd sepEnd) {
		SpaceElement src = sepEnd.getEndSource();
		if (sepEnd.isSun()) {
			// Get HAE Sun coordinates (at time t or t')
			sepEnd.setLongEnd(src.getAttitude()[0]);
			sepEnd.setLatEnd(src.getAttitude()[1]);
		} else {
			// Get HAE target coordinates (at time t or t')
			double[] longLat = CoordConverter.toSpheric(src.getX(), src.getY(), src.getZ());
			sepEnd.setLongEnd(longLat[0]);
			sepEnd.setLatEnd(longLat[1]);
		}
		// Compute long sep = long end (t) - long start (t) or long end (t') - long start (t) 
		sepEnd.setLongSep(CoordConverter.normalizeLongitude(sepEnd.getLongEnd() - sep.getLongStartT()));
		sepEnd.setLatSep(CoordConverter.normalizeLongitude(sepEnd.getLatEnd() - sep.getLatStartT()));

	}

	/**
	 * Compute impact with Spiral
	 * @param sep
	 */
	private boolean isInsideSpiral(Sep sep, SepEnd sepEnd) {
		if (!sepEnd.isSun()) {
			// Get spiral HAE longitude at t' between [-180, 180]
			double longEndSourceHAE = CoordConverter.normalizeLatitude(sep.getLongSourceT() + sepEnd.getDeltaLong());
			// Get spiral footpoints HAE longitudes at t' between [-360, 360]
			double fpt1 = longEndSourceHAE - sep.getSpreadCIR();
			double fpt2 = longEndSourceHAE + sep.getSpreadCIR();
			// Get target HAE longitude at t' between [-180, 180]
			double longTarget = CoordConverter.normalizeLatitude(sepEnd.getLongEnd());
			//double longTarget = longEnd > fpt2 ? longEnd - 360.0 : longEnd;
			return ToolHelper.isInsideSpiral(sep.getSpeedSolarWind(), sep.getRotationPeriod(), fpt1, fpt2, longTarget, CoordConverter.toAU(sepEnd.getDistEnd()));
		}
		else {
			return true;
		}
	}

}
