/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * Version : 25/04/2016 : SCA : V2.2 (PSWS-H2020)
 *  Swap STEREO-A / STEREO-B FOV after 14/07/2015
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.jmap;

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

import fr.tlse.irap.fit.FitPoint;
import fr.tlse.irap.tool.radial.DragModel;

public class JMapComputeHelper {

    public static final int NB_DAYS = 10;
    public static final int DELTA_SECONDS = 600;

    /**
     * Compute Elongation of a space point from a J-Map observer
     * @param rObserver : radial distance of the J-Map observer (SECCHI-A/SECCHI-B/SOHO)
     * @param rPoint : radial distance of a space point (Spacecraft, CME, ...)
     * @param deltaLong : longitude in degrees between the space point and the observer
     */
    public static double computeElongation(double rObserver, double rPoint, double deltaLong) {
        double rx, ry;
        double alpha;

        double beta = Math.toRadians(deltaLong);
        rx = rObserver - rPoint * Math.cos(beta);
        ry = rPoint * Math.sin(beta);
        // Compute elongation of spacecraft using atan2 for a result belonging to [-pi, pi]
        alpha = Math.atan2(ry, rx);

        // Return elongation in degrees
        return (Math.toDegrees(alpha));
    }

    /**
     * Compute InSitu fit points
     * @param radius : radial distance of the J-Map observer (SECCHI-A/SECCHI-B)
     * @param speed : speed solar wind measured by start at time t
     * @param beta : longitude between the CME/CIR and the observer (deg)
     * @param gamma : observer orbital motion factor (in deg/s) 
     * @param startTime : start time of the CME/CIR will impact start at time t
     */
    public static List<FitPoint> computeFitPoints(double radius, double speed, double beta, double gamma, Date startTime) {
    	return computeFitPoints(radius, speed, beta, gamma, startTime, DELTA_SECONDS, NB_DAYS * 3600 * 24);
    }

    /**
     * Compute InSitu fit points taking into account the orbital motion of observer 
     * @param radius : radial distance of the J-Map observer (SECCHI-A/SECCHI-B/SOHO)
     * @param speed : speed solar wind measured by start at time t
     * @param beta : longitude in degrees between the CME/CIR and the observer at start time
     * @param gamma : observer orbital motion factor (in deg/s) 
     * @param t0 : start time of the CME/CIR will impact start at time t
     * @param timeStep : fit time step in seconds
     * @param maxTime : fit max time in seconds
     */
    public static List<FitPoint> computeFitPoints(double radius, double speed, double beta, double gamma, Date t0, long timeStep, long maxTime) {
        double ri;
        double alpha = 0.;
        long ti = 0;
        long time;
        double dBeta;

        List<FitPoint> fitPoints = new ArrayList<FitPoint>();
        FitPoint fitPoint;

        // do not compute if beta start is out of observer angle view
        if (beta > 180) return fitPoints;
       
        while (alpha < 90. && ti < maxTime) {
            /* Compute CME radial distance (km) */
            ri = speed * ti; // this is the Fit function
            dBeta = gamma * ti; // orbital motion angle 
            alpha = computeElongation(radius, ri, beta + dBeta);
            time = t0.getTime() + ti * 1000;
            /* create a new fitPoint */
            fitPoint = new FitPoint(time, alpha);
            fitPoints.add(fitPoint);
            /* increment time */
            ti += timeStep;
        }

        return fitPoints;
    }
    
    /**
     * Compute InSitu fit points with drag model
     * @param rObserver : radial distance of the J-Map observer (SECCHI-A/SECCHI-B/SOHO)
     * @param dragModel : drag model computed from a constant speed phase and a variable speed phase 
     * @param beta : longitude between the CME/CIR and the observer (deg)
     * @param gamma : observer orbital motion factor (deg/s) 
     * @param t0 : start time of the CME/CIR will impact start at time t
     */
    public static List<FitPoint> computeFitPoints(double rObserver, DragModel dragModel, double beta, double gamma, Date t0) {
    	double ri;
    	long time;
        long ti = 0;
        double alpha = 0.;
        double dBeta;
        
        FitPoint fitPoint;
        List<FitPoint> fitPoints = new ArrayList<FitPoint>();

        // do not compute if beta start is out of observer angle view
        if (beta > 180) return fitPoints;

        while (alpha < 90. && ti < dragModel.getDuration()) {
            /* get drag radius from ti */
        	ri = dragModel.getRadius(ti); // this is the Fit function
            dBeta = gamma * ti; // orbital motion angle from start time 
            alpha = computeElongation(rObserver, ri, beta + dBeta);
            time = t0.getTime() + ti * 1000;
            /* create a new fitPoint */
            fitPoint = new FitPoint(time, alpha);
            fitPoints.add(fitPoint);
            /* increment time */
            ti += dragModel.getResolution();
        }

        return fitPoints;
    }

    /**
     * Compute Radial Distance (km) of a space point according to a J-Map observer
     * @param rObserver : radial distance of the J-Map observer (SECCHI-A/SECCHI-B/SOHO)
     * @param elongation : elongation of the space point from the observer
     * @param deltaLong : longitude in degrees between the space point and the observer
     */
    public static double computeRadialDistance(double rObserver, double elongation, double deltaLong) {
        double beta = Math.toRadians(deltaLong);
        double alpha = Math.toRadians(elongation);
        double radialDistance = rObserver * Math.sin(alpha) / Math.sin(Math.PI - alpha - beta);
        return (radialDistance);
    }

    public static Long minTime(List<FitPoint> fitPoints) {
        Long time = fitPoints.get(0).getTime();
        for (FitPoint fitPoint : fitPoints) {
            if (fitPoint.getTime() < time) {
                time = fitPoint.getTime();
            }
        }
        return time;
    }

    public static double minElongation(List<FitPoint> fitPoints) {
        double alpha = 90.0;
        for (FitPoint fitPoint : fitPoints) {
            if (fitPoint.getElongation() < alpha) {
                alpha = fitPoint.getElongation();
            }
        }
        return alpha;
    }

    public static double maxElongation(List<FitPoint> fitPoints) {
        double alpha = 0.0;
        for (FitPoint fitPoint : fitPoints) {
            if (fitPoint.getElongation() > alpha) {
                alpha = fitPoint.getElongation();
            }
        }
        return alpha;
    }

    public static int[] minIndex(double[][] value, int ySize, int xSize) {
        double minValue = 9999999.;
        int iMin = 0;
        int jMin = 0;
        for (int i = 0; i < ySize; i++) {
            for (int j = 0; j < xSize; j++) {
                if (value[i][j] < minValue) {
                    minValue = value[i][j];
                    iMin = i;
                    jMin = j;
                }
            }
        }
        int minIndex[] = new int[2];
        minIndex[0] = iMin;
        minIndex[1] = jMin;
        return minIndex;
    }

    public static double computeError(List<FitPoint> fitPoints, Double[] alphaTh) {
        double err = 0;

        for (int i = 0; i < fitPoints.size(); i++) {
            err += Math.pow(fitPoints.get(i).getElongation() - alphaTh[i], 2);
        }
        err = Math.sqrt(err / fitPoints.size());

        return err;
    }

}
