/* ----------------------------------------
 * 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.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import org.apache.log4j.Logger;

import fr.tlse.irap.server.serviceDAO.ISpiceDAO;
import fr.tlse.irap.server.serviceDAO.utils.SpiceHelper;
import fr.tlse.irap.server.serviceTool.ICorotationTool;
import fr.tlse.irap.spaceElement.Comet;
import fr.tlse.irap.spaceElement.Planet;
import fr.tlse.irap.spaceElement.Probe;
import fr.tlse.irap.spaceElement.SpaceElement;
import fr.tlse.irap.spaceElement.SpaceElementWrapper;
import fr.tlse.irap.tool.corotation.Corotation;
import fr.tlse.irap.tool.corotation.CorotationEnd;
import fr.tlse.irap.utils.CoordConverter;
import fr.tlse.irap.utils.ToolHelper;

public class CorotationToolImpl implements ICorotationTool {

	private static final Logger logger = Logger.getLogger(CorotationToolImpl.class);
	private static final long INTERVAL_DAYS = 30;
	private static final double DISTANCE_MAX_AU = 1.52;

	private ISpiceDAO spiceDAO;
	
	public CorotationToolImpl() {
	}

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

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

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

	}

	/**
	 * Compute corotation propagation from a start to list of end elements
	 * @param CIR fit
	 * @param Observer
	 * @return list of corotationEnd containing result of propagation (time, ...) for each end element
	 */
	@Override
	public List<CorotationEnd> computeCorotation(Corotation corotation)	throws Exception {
		
		List<CorotationEnd> cEnds = new ArrayList<CorotationEnd>();
        
        // for each elements compute Corotation propagation
        SpaceElementWrapper elements = SpiceHelper.buildSpaceElements();

        for (Planet planet : elements.getPlanets()) {
    		CorotationEnd cEnd = null;
    		try {
    			cEnd = computeCorotation(corotation, planet);
    			cEnds.add(cEnd);
    			
    		} catch (Exception ex) {
    		}
        }

        for (Probe probe : elements.getProbes()) {
    		CorotationEnd cEnd = null;
    		try {
    			cEnd = computeCorotation(corotation, probe);
    			cEnds.add(cEnd);
    			
    		} catch (Exception ex) {
    		}
        }

		Date t1 = corotation.getStartTime().getTime();
        Date t2 = new Date();
        t2.setTime(t1.getTime() + (INTERVAL_DAYS * 86400 * 1000));
        long distanceMax = ((Double)CoordConverter.toKm(DISTANCE_MAX_AU)).longValue();
        
        // for each comets within the inner heliosphere compute Radial propagation
        elements = spiceDAO.getAvailableComets(t1, t2, corotation.getStartElement(), distanceMax);
        for (Comet comet : elements.getComets()) {
    		CorotationEnd cEnd = null;
    		try {
    			cEnd = computeCorotation(corotation, comet);
    			cEnds.add(cEnd);
    			
    		} catch (Exception ex) {
    		}
        }
        
		return cEnds;

	}

	/**
	 * Compute the propagated time from time t, speed V at time t and the radial distance between END and START
	 * @param corotation
	 * @param cEnd
	 */
	private void computePropagation(Corotation c, CorotationEnd cEnd) {
		// Get radial distance between START and END at time t 
		double dR = cEnd.getDistEnd() - c.getRadialDistanceStart();
		// Get rotation frequency of the spiral (in degrees) 
		double omega = Math.toDegrees(ToolHelper.getRotationFrequency(c.getRotationPeriod()));
		// Get sun rotation period in seconds
		double rotPeriod = c.getRotationPeriod() * 3600 * 24;
		// Compute the intermediate arrival time that ignores orbital motion of the planet : see equation (7)
		double dt, ti, tf;
		if (cEnd.isSun()) {
			dt = (cEnd.getLongSep() / omega) - (dR / c.getSpeedSolarWind());
		} else {
			dt = (cEnd.getLongSep() / omega) + (dR / c.getSpeedSolarWind());
		}
		// Compute intermediate time modulo sun rotation period
		dt = computeDeltaTimeModulo(dt, rotPeriod, cEnd.isSun());
		ti = c.getStartTime().getTimeInMillis() / 1000.0 + dt;
		// No orbital motion for END=SUN
		tf = ti;
		if (!cEnd.isSun()) {
			// Get position of END at intermediary time  
			cEnd.getEndTime().setTimeInMillis((long)ti * 1000);
			SpaceElement e = spiceDAO.getPositionElement(cEnd.getEndSource(), cEnd.getEndTime().getTime());
			if (e != null) {
				// Longitudinal angle swept by the orbital motion of the END spacecraft between time t and time ti : see equation (9)
				double longEndInter = CoordConverter.toSpheric(e.getX(), e.getY(), e.getZ())[0];
				double orbitalMotion = CoordConverter.normalizeLongitude(longEndInter - cEnd.getLongEnd());
				// Compute the propagated time from time ti corrected for orbital motion of the Observer see equation (8).
				dt = dt + orbitalMotion / omega;
				// Compute final time modulo sun rotation period
				dt = computeDeltaTimeModulo(dt, rotPeriod, cEnd.isSun());
				tf = c.getStartTime().getTimeInMillis() / 1000.0 + dt;
			}
		}

		// set final time
		Date d = new Date();
		d.setTime((long)tf * 1000);
		cEnd.getEndTime().setTimeInMillis((long)tf * 1000);
	}

	/**
	 * Compute delta time modulo sun rotation period
	 * @param dt in seconds
	 * @param period in seconds
	 * @return
	 */
	private double computeDeltaTimeModulo(double dt, double period, boolean isSun) {
		// Compute modulo rate in [-1, 1]
		double mr = (dt / period) - (int)(dt / period);
		if (isSun) {
			// Modulo rate in [-1, 0]
			if (mr > 0) {
				mr = mr - 1;
			}
		} else {
			// Modulo rate in [0, 1] 
			if (mr < 0) {
				mr = mr + 1;
			}
		}
		return mr * period;
	}

	/**
	 * Compute delta times associated with different errors (in hours)
	 * see equations (6) and (7) from annex A on 29/10/2012
	 * @param corotation
	 * @param cEnd
	 */
	private void computeDeltaTimes(Corotation c, CorotationEnd cEnd) {
		double v = c.getSpeedSolarWind();
		double dR = cEnd.getDistEnd() - c.getRadialDistanceStart();
		double omega = Math.toDegrees(ToolHelper.getRotationFrequency(c.getRotationPeriod()));

//		// Earliest arrival time associated with error in departure time
//		cEnd.setDtMin((c.getTimeError() == 0.0 ? 0.0 : -c.getTimeError()));
//		// Latest arrival time associated with error in departure time
//		cEnd.setDtMax(c.getTimeError());
		// Earliest arrival time associated with error in speed
		cEnd.setDvMin((c.getSpeedError() == 0.0 ? 0.0 : dR * (1 / (v + Math.abs(c.getSpeedError())) - 1 / v) / 3600.0));
		// Latest arrival time associated with error in speed
		cEnd.setDvMax((c.getSpeedError() == 0.0 ? 0.0 : dR * (1 / (v - Math.abs(c.getSpeedError())) - 1 / v) / 3600.0));
		// Earliest arrival time associated with error in spread of CIR, expressed in degrees
		cEnd.setDphiMin((c.getSpreadCIR() == 0.0 ? 0.0 : -c.getSpreadCIR() / omega) / 3600.0);
		// Latest arrival time associated with error in spread of CIR, expressed in degrees
		cEnd.setDphiMax((c.getSpreadCIR() == 0.0 ? 0.0 : c.getSpreadCIR() / omega) / 3600.0);
	}

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

	/**
	 * Compute end source of the CIR spiral footpoint at impact time 
	 * @param c
	 * @param cEnd
	 */
	private void computeEndSource(Corotation c, CorotationEnd cEnd) {
		if (cEnd.isSun()) {
			cEnd.setLongEndSource(cEnd.getLongEnd());
		}
		else {
			double omega = Math.toDegrees(ToolHelper.getRotationFrequency(c.getRotationPeriod()));
			double distEndFinalTXY = ToolHelper.computeRadialDistanceXY(cEnd.getEndSource());
			// compute delta longitude between END and spiral footpoint
			double dPhi = (distEndFinalTXY * omega) / c.getSpeedSolarWind();
			// set the HAE end source coordinates 
			cEnd.setLongEndSource(CoordConverter.normalizeLongitude(cEnd.getLongEnd() + dPhi));
		}
		cEnd.setLatEndSource(cEnd.getLatEnd());
	}

	/**
	 * Compute impact with Spiral
	 * @param c
	 */
	private boolean isInsideSpiral(Corotation c) {
		return true;
	}

}
