package config;

import java.util.ArrayList;

import lejos.geom.Line;
import lejos.robotics.mapping.LineMap;
import lejos.robotics.navigation.Pose;
import robots.DataRead;

public class Models {
	
	private static boolean USE_TRIANGULAR = false;
	private static int SONAR_CONE = 15;
	private static double SONAR_SIGMA = 9; // varian
	
	private static double ALPHA1 = 0.01;		// desvio padrao do erro no gito
	private static double ALPHA2 = 0.001;	// desvio padrao do erro qunado gira proporconal ao deslocalmento 0.0001
	private static double ALPHA3 = 0.1;		// desvio padrao no erro do deslocamento
	private static double ALPHA4 = 0.01;    // desvio padrao no erro do deslocamento baseado em quanto rodou (nao importa para o nosso robo)

	LineMap map;
	public Models(LineMap map) {
		this.map = map;
	}
	
    // return pdf(x, mu, signma) = Gaussian pdf with mean mu and stddev sigma
    private static double pdf(double x, double sigma) {
    		if (USE_TRIANGULAR) {
    			double r = Math.sqrt(sigma*6);
    			double abs = Math.abs(x);
    			if (abs > r) return 0;
    			return (r-abs)/(6*sigma);
    		}
        return Math.exp(-x*x / 2 / sigma) / Math.sqrt(2 * Math.PI * sigma);
    }

	private double probTriangularDistribution (double a, double b) {
		double r = Math.sqrt(b*6);
		double abs = Math.abs(a);
		if (abs > r) return 0;
		return (r-abs)/(6*b);
	}
	
	
	/* retorna a probabilidade do robo estar em (ap) sendo que ele saiu de (bp) 
	 * com um movimento de rotacao de ang graus.*/
	public double rotateProbability (Pose pa, Pose pb, double ang) {
		return motionModelOdometry(pa, pb, 0, ang);
	}
	
	/* retorna a probabilidade do robo estar em (pa) sendo que ele saiu de (pb) 
	 * com sendo que ele foi move centimetros para frente.*/
	public double moveProbability (Pose pa, Pose pb, double move) {
		return motionModelOdometry(pa, pb, move, 0);
	}
	
	/* retorna a probabilidade do robo estar em (pa) sendo que ele saiu de (pb) 
	 * sendo que ele rodou ang graus e foi move centimetros para frente.*/
	public double motionModelOdometry (Pose pa, Pose pb, double move, double ang) {
		double drot1 = n(Math.toRadians(ang));
		double pah = Math.toRadians(pa.getHeading());
		double pbh = Math.toRadians(pb.getHeading());
		
		double dtrans = move;
		
		double dx = pa.getX()-pb.getX();
		double dy = pa.getY()-pb.getY();
		
		double dcrot1 = n(Math.atan2(dy, dx) - Math.toRadians(90));
		double dctrans = Math.sqrt(dx*dx+dy*dy);
		double dcrot2 = n(pah - dcrot1);
		
		double p1 = probTriangularDistribution(Math.abs(n(pah-pbh-drot1)), ALPHA2*dtrans+Math.abs(ALPHA1*drot1)); //SOME
		double p2 = probTriangularDistribution(Math.abs(dtrans)-Math.abs(dctrans), Math.abs(ALPHA3*dtrans)+Math.abs(ALPHA4*drot1)+Math.abs(ALPHA4*dcrot2));
		double p3 = probTriangularDistribution(dcrot2, ALPHA2*dtrans+Math.abs(ALPHA1*dcrot2));
		if (dtrans == 0) p3 = 1;
		return p1*p2*p3;
	}
	
	private double n (double x) {
		return Math.atan2(Math.sin(x), Math.cos(x));
	}
	
	/* retorna a probabilidade dele estar em (p) sendo que realizou a leitura do
	 *  conjunto data de medidas - pdf */
	public double readProbability (Pose p, ArrayList<DataRead> data) {
		double prob = 1.0;
		for (DataRead dp: data) {
			// os 90 é para a correcao do mapa
			float newang = (float) (p.getHeading() + 270 + dp.getSensorAngle());
			Pose pose = new Pose(p.getX(), p.getY(), newang);
			
			float mindist = Float.POSITIVE_INFINITY;
			int cone = SONAR_CONE; // CONE CONE
			for (int angulo = -cone / 2; angulo <= cone / 2; angulo++) {
				pose.setHeading((float) (newang - angulo));
				float dist = map.range(pose);
				if (dist > 0 && dist < mindist)
					mindist = dist;
			}
			double teoricaldistance = mindist;
			
			// mapa nao tem parede - ignora medidas...
			if (teoricaldistance == -1)
				continue;

			double error = dp.getDistance() >= 255 || dp.getDistance()  <= 0 ? 1.0: 0.0;
			prob *= pdf(dp.getDistance()-teoricaldistance, SONAR_SIGMA)*0.7+0.2/255+.1*error; // desvio padrao
		}
		return prob;
	}
}
