/* ----------------------------------------
 * 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.IRadialTool;
import fr.tlse.irap.utils.ToolHelper;
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.radial.CMEType;
import fr.tlse.irap.tool.radial.Radial;
import fr.tlse.irap.tool.radial.RadialEnd;
import fr.tlse.irap.utils.CoordConverter;

public class RadialToolImpl implements IRadialTool {

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

	private ISpiceDAO spiceDAO;
	
	public RadialToolImpl() {
	}

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

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

	/**
	 * Compute radial propagation from a start to end element
	 * @param radial
	 * @param endElement
	 * @return radialEnd containing result of propagation (time, ...)
	 */
	@Override
	public RadialEnd computeRadial(Radial radial, SpaceElement endElement) throws Exception {
		// set default timezone UTC
        TimeZone.setDefault(TimeZone.getTimeZone("UTC"));        
		
		RadialEnd rEnd = new RadialEnd();
		rEnd.setEndSource(endElement);
		logger.debug("Get position of " + endElement.getName() + " at Start time " + radial.getStartTime().getTime());
		//SpaceElement e = positionService.getPositionElement(endElement, radial.getStartTime().getTime());
		SpaceElement e = spiceDAO.getPositionElement(endElement, radial.getStartTime().getTime());
		if (e != null) {
			// compute radial distance of End at start time
			rEnd.setDistEnd(ToolHelper.computeRadialDistance(e));
			//log.debug("Computed Radial Distance End = " + radial.getRadialDistanceEnd());
			computePropagation(radial, rEnd);
			//log.debug("Computed Propagation date = " + endTime.getTime());
			computeDeltaTimes(radial, rEnd);
			//log.debug("Delta times is computed");
			computeEndFinalT(radial, rEnd);
			rEnd.setImpacted(computeIsInsideCME(radial, rEnd));
			
			if (radial.getCmeType().equals(CMEType.SSE)) {
				// compute with SSE model 
				if (computePropagationSSE(radial, rEnd)) {
					// re-compute longitudes and impact with the real end time 
					computeEndFinalT(radial, rEnd);
					rEnd.setImpacted(computeIsInsideCME(radial, rEnd));
				}
			}
		}
		return rEnd;
	}

	/**
	 * Compute radial propagation from a start to list of end elements
	 * @param CME fit
	 * @param Observer
	 * @param Type CME
	 * @return list of radialEnd containing result of propagation (time, ...) for each end element
	 */
	@Override
	public List<RadialEnd> computeRadial(Radial radial) throws Exception {

		List<RadialEnd> rEnds = new ArrayList<RadialEnd>();
        
        // for each elements compute Radial propagation
        SpaceElementWrapper elements = SpiceHelper.buildSpaceElements ();

        for (Planet planet : elements.getPlanets()) {
    		RadialEnd rEnd = null;
    		try {
    			rEnd = computeRadial(radial, planet);
    			rEnds.add(rEnd);
    			
    		} catch (Exception ex) {
    		}
        }

        for (Probe probe : elements.getProbes()) {
    		RadialEnd rEnd = null;
    		try {
    			rEnd = computeRadial(radial, probe);
    			rEnds.add(rEnd);
    			
    		} catch (Exception ex) {
    		}
        }

		Date t1 = radial.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, radial.getStartElement(), distanceMax);
        for (Comet comet : elements.getComets()) {
    		RadialEnd rEnd = null;
    		try {
    			rEnd = computeRadial(radial, comet);
    			rEnds.add(rEnd);
    			
    		} catch (Exception ex) {
    		}
        }
        
		return rEnds;
	}

	/**
	 *  Compute the propagated time in case of fixed point geometry (CME) 
	 *  <li>compute from time t, speed V at time t and the radial distance between END and START
	 */
	private void computePropagation(Radial r, RadialEnd rEnd) {
		double tf; 

		if (rEnd.isSun()) {
			tf = r.getSunTime().getTimeInMillis() / 1000.0;
		} 
		else {
			// compute delta distance between Start and End at start time
			double ti = r.getStartTime().getTimeInMillis() / 1000.0;
			double dR = rEnd.getDistEnd() - r.getRadialDistanceStart();
			if (r.isDragModel()) {
   				tf = ti + (r.getDragTime(dR) / 1000);
			}
			else {
				tf = ti + (dR / r.getSpeedSolarWind());
			}
		}
		// set final time
		Date df = new Date();
		df.setTime((long)tf * 1000);
		rEnd.setEndTime(df);
	}

	/**
	 *  Compute the propagated time in case of SSE geometry (CME).
	 *  <li>Compute from time t, speed V at time t, delta longitude between START at tSun and END at time t', extent of CME.
	 *  (cf publication Mostl and Davis, the 8 march 2012)
	 *  <li>Set endTime to null if delta > lambda
	 * @param r
	 * @return true if SSE propagation has been computed
	 */
	private boolean computePropagationSSE(Radial r, RadialEnd rEnd) {
		boolean computed=false;
		double tf=0;

		if (rEnd.isSun()) {
			tf = r.getSunTime().getTimeInMillis() / 1000.0;
		} 
		else {
			double lambda = Math.toRadians(r.getExtentCME() / 2);
			double delta = Math.toRadians(rEnd.getLongSep());
			if (delta > Math.PI) {
				delta = Math.toRadians(360.0 - rEnd.getLongSep());
			}
			if (delta < lambda) {
				double riSSE = rEnd.getDistEnd() * (1 + Math.sin(lambda) / (Math.cos(delta) + Math.sqrt(Math.pow(Math.sin(lambda), 2) - Math.pow(Math.sin(delta), 2))));
				double ti = r.getStartTime().getTimeInMillis() / 1000.0;
				tf = ti + riSSE / r.getSpeedSolarWind();
				computed = true;
			}
		}
		if (tf > 0) {
			// set final time
			Date df = new Date();
			df.setTime((long)tf * 1000);
			rEnd.setEndTime(df);
		}
		return computed;
	}

	/**
	 * Compute delta times associated with different errors (in hours)
	 * see equations (6) and (7) from annex A on 29/10/2012
	 */
	private void computeDeltaTimes(Radial r, RadialEnd rEnd) {
		double v = r.getSpeedSolarWind();
		double dR = Math.abs(rEnd.getDistEnd() - r.getRadialDistanceStart());

//		// Earliest arrival time associated with error in departure time
//		rEnd.setDtMin((r.getTimeError() == 0.0 ? 0.0 : -r.getTimeError()));
//		// Latest arrival time associated with error in departure time
//		rEnd.setDtMax(r.getTimeError());
		// Earliest arrival time associated with error in speed
		rEnd.setDvMin(r.getSpeedError() == 0.0 ? 0.0 : dR * (1 / (v + Math.abs(r.getSpeedError())) - 1 / v) / 3600.0);
		// Latest arrival time associated with error in speed
		rEnd.setDvMax(r.getSpeedError() == 0.0 ? 0.0 : dR * (1 / (v - Math.abs(r.getSpeedError())) - 1 / v) / 3600.0);
	}

	/**
	 * Compute End fields from End date
	 * @param r
	 */
	private void computeEndFinalT(Radial r, RadialEnd rEnd) {
		rEnd.setLongEnd(null);
		rEnd.setLongSep(null);
		rEnd.setLatEnd(null);
		rEnd.setLatSep(null);
		SpaceElement e = spiceDAO.getPositionElement(rEnd.getEndSource(), rEnd.getEndTime().getTime());
		if (e != null) {
			if (rEnd.isSun()) {
				// Get HAE Sun coordinates (at time t')
				rEnd.setLongEnd(e.getAttitude()[0]);
				rEnd.setLatEnd(e.getAttitude()[1]);
			} else {
				// Get HAE target coordinates (at time t')
				double[] longLat = CoordConverter.toSpheric(e.getX(), e.getY(), e.getZ());
				rEnd.setLongEnd(longLat[0]);
				rEnd.setLatEnd(longLat[0]);
			}
		}
		if (rEnd.getLongEnd() != null) {
			// Compute long sep = long end (t') - long start (t)
			rEnd.setLongSep(CoordConverter.normalizeLongitude(rEnd.getLongEnd() - r.getLongStartT()));
			rEnd.setLatSep(CoordConverter.normalizeLongitude(rEnd.getLatEnd() - r.getLatStartT()));
		}
	}

	/**
	 * Compute impact with CME
	 * @param r
	 */
	private boolean computeIsInsideCME(Radial r, RadialEnd rEnd) {
		boolean isInsideCME = false;
		// Compute impact
		if (rEnd.isSun()) {
			isInsideCME = true;
		} else {
			if (rEnd.getLongSep() != null) {
				isInsideCME = ToolHelper.isInsideCME((double)rEnd.getLongSep(), r.getExtentCME() / 2.0);
			}
		}
		return isInsideCME;
	}

}
