/* Copyright 2013-2016 CS Systèmes d'Information
 * Licensed to CS Systèmes d'Information (CS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * CS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package AffinagePleiades;



import org.hipparchus.linear.ArrayRealVector;
import org.hipparchus.linear.RealVector;

import org.orekit.rugged.api.SensorToGroundMapping;
import org.orekit.rugged.api.Rugged;
import org.orekit.rugged.linesensor.LineSensor;
import org.orekit.rugged.linesensor.SensorPixel;
import org.orekit.rugged.errors.RuggedException;
import org.orekit.time.AbsoluteDate;
import org.s2geolib.utils.S2GeolibConstants;

import java.util.Collections;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;
import java.util.Locale;

import org.hipparchus.util.FastMath;
import org.orekit.bodies.GeodeticPoint;

/** class for measure generation
 * @author Jonathan Guinet
 */
public class LocalisationMetrics {


    /** mapping */
    private Set <Map.Entry<SensorPixel, GeodeticPoint>> groundTruthMappings;

    private Set <Map.Entry<SensorPixel, GeodeticPoint>> estimationMappings; 
    
    private Rugged rugged;
    
    private LineSensor sensor;

    private PleiadesViewingModel viewingModel;
    
    private int measureCount;
     
    private final double EARTH_RADIUS = 637100000d;
    
    private boolean computeInDeg; 
    
    /* max residual distance */
    private double resMax;
    
    
    /* mean residual distance */
    private double resMean;
    
    
    /** Simple constructor.
     * <p>
     *
     * </p>
     */
    public LocalisationMetrics(SensorToGroundMapping groundTruthMapping, Rugged rugged, boolean computeInDeg) throws RuggedException
    {
	

    	groundTruthMappings =  groundTruthMapping.getMappings();
    	this.rugged = rugged;
    	this.sensor = rugged.getLineSensor(groundTruthMapping.getSensorName());
    	this.computeInDeg = computeInDeg;
    	this.computeMetrics();
    }
    
    
    /** Get the maximum residual;
     * @return max residual
     */
    public double getMaxResidual() {
        return resMax;
    }
    

    
    /** Get the mean residual;
     * @return mean residual
     */
    public double getMeanResidual() {
        return resMean;
    }
    
    
    /**
     * Compute distance between point (lonDeg1, latDeg1) and point (lonDeg2, latDeg2) in meters
     * @param lonDeg1
     * @param latDeg1
     * @param lonDeg2
     * @param latDeg2
     * @return distance between point (lonDeg1, latDeg1) and point (lonDeg2, latDeg2)
     */
    private double computeDistance(double lonDeg1, double latDeg1, double lonDeg2, double latDeg2) {
        double returned;
        double xRad1 = FastMath.toRadians(lonDeg1);
        double xRad2 = FastMath.toRadians(lonDeg2);
        double yRad1 = FastMath.toRadians(latDeg1);
        double yRad2 = FastMath.toRadians(latDeg2);
        returned = computeDistanceRad(xRad1, yRad1, xRad2, yRad2);
        return returned;
    }

    /**
     * distance in meters between point (xRad1, yRad1) and point (xRad2, yRad2)
     * @param xRad1
     * @param xRad2
     * @param yRad1
     * @param yRad2
     * @return
     */
    private double computeDistanceRad(double xRad1, double yRad1, double xRad2, double yRad2) {
        double returned;
        double sinValue = FastMath.sin(xRad1) * FastMath.sin(xRad2);
        double deltaLambda = FastMath.abs(yRad1 - yRad2);
        double cosValue = FastMath.cos(xRad1) * FastMath.cos(xRad2) * FastMath.cos(deltaLambda);
        double deltaPhy = FastMath.acos(sinValue + cosValue);
        if (Double.isNaN(deltaPhy)) {
            deltaPhy = 0d;
        }
        returned = EARTH_RADIUS / 100 * deltaPhy;
        return returned;
    }

    
    
    
    public void computeMetrics()  throws RuggedException {
    	    	    
            //final RealVector longDiffVector;
            //final RealVector latDiffVector;
            //final RealVector altDiffVector;
            RealVector distanceVector = new ArrayRealVector();
            double count=0;
            resMax = 0; 
    	  	int k = groundTruthMappings.size();
    	  	Iterator<Map.Entry<SensorPixel, GeodeticPoint>> gtIt = groundTruthMappings.iterator();
    	  	
    	  	
    	  	while (gtIt.hasNext()) {
    	  		Map.Entry<SensorPixel, GeodeticPoint> gtMapping =gtIt.next();
    	  		
    	  		
                final SensorPixel gtSP = gtMapping.getKey();
                final GeodeticPoint gtGP = gtMapping.getValue();
                
                AbsoluteDate date = sensor.getDate(gtSP.getLineNumber());
                
                GeodeticPoint esGP = rugged.directLocation(date, sensor.getPosition(),
                        sensor.getLOS(date, (int) gtSP.getPixelNumber()));
                   
                double distance = 0;
                
                if(this.computeInDeg == true)
                {
                	double lonDiff = esGP.getLongitude() - gtGP.getLongitude();
                	double latDiff = esGP.getLatitude() - gtGP.getLatitude();		
                	double altDiff = esGP.getAltitude() - gtGP.getAltitude();
                	distance = Math.sqrt( lonDiff * lonDiff + latDiff * latDiff + altDiff * altDiff );
                }
                else
                	distance = computeDistance(esGP.getLongitude(), esGP.getLatitude(),gtGP.getLongitude() , gtGP.getLatitude());
    	  	    count += distance;
    	  	    if (distance > resMax)
    	  	    {
    	  	    		resMax = distance;
    	  	    }
                //distanceVector.append(distance);
    	  		
    	  	}
    	  	
            //resMax =  distanceVector.getMaxValue();
            //System.out.format(Locale.US, "max: %3.6e %n ",distanceVector.getMaxValue() )
    	  	
            resMean = count / k ;
            //System.out.format("number of points %d  %n", k);
        }
    	
  	

    
}