import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

/*
 * Satelite.java
 *
 * Created on 21 de diciembre de 2006, 13:05
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

/**
 *
 * @author David
 */
public class Satelite {
    
    // Parametros definidos por el usuario
    private String NombreImagenInicio;                  // Carpeta en la que estan las imagenes
    private int NImagenes;                              // N de imagenes que hay en la carpeta
    private int NImagenesASaltar;                       // N de imagenes a saltarse
    private int NImagenesUsadas;
    private int MaxIters;                               // Maximo de iteraciones del steepedascend
    private double PrecisionInicial;                    // Precision inicial del steepdascend
    private double IncrementoPrecision;                 // Incremento de precision del steepdascend
    private double MaxPrecision;                        // Maxima precision a la que deseamos llegar
    private double MaxError;                            // Error al que deseamos llegar
    private double[] parametros;                        // Array de parametros de la trayectoria
    private boolean inverso;
    private boolean resta=true;
    
    // Datos leidos de la carpeta o calculadios a partir de otros
    private int iniAncho=255;
    private int iniAlto=255;
    private int[][][] imagenes;                         // Array de matrices con las imagenes
    private double[][] centrosDouble;                   // Array de centros de los planetas
    
    private PolRegresion poli;                          // Polinomio por minimos cuadrados
    private double radio;                               // Radio del satelite
    private int xinicial, yinicial, incremento;
    private int [] x;                                   // 
    private double [] y;                               // Salidas reales
    private double [] y1;                               // Salidas con el polinomio1
    private double [] y2;                               // Salidas con el polinomio2
    private double [] e1;                               // Errores del polinomio1 en cada punto
    private double [] e2;                               // Errores del polinomio2 en cada punto
    private double sumE1, sumE2;                        // Error de los polinomios
        
    /**
     * Creates a new instance of Satelite
     */
    public Satelite(String NombreImagenInicio, int NImagenes, int NImagenesASaltar, int MaxIters, double PrecisionInicial, double IncrementoPrecision, double MaxPrecision, double MaxError, double []parametros, boolean inverso) {
            
        // Variables
        this.NombreImagenInicio= NombreImagenInicio;
        this.NImagenes=NImagenes;
        this.NImagenesASaltar=NImagenesASaltar;
        this.MaxIters=MaxIters;
        this.PrecisionInicial=PrecisionInicial;
        this.IncrementoPrecision=IncrementoPrecision;
        this.MaxPrecision=MaxPrecision;
        this.MaxError=MaxError;
        this.parametros=parametros;
        this.NImagenesUsadas =(int)Math.floor(NImagenes/NImagenesASaltar);
        this.inverso=inverso;
        
        // Leemos las imagenes
        if (LeeImagenes()!=0)
            System.out.println("Error al leer las imagenes");
        
        // Calculamos los centros y radios de los satelites
        CalculaCentros(inverso);
        this.radio=CalculaRadios();
    }
    
    
    /** Lee un fichero de imagen y devuelve una matriz con los pixeles */
    private static int[][] LeeImagen(String fichero){
        // Declaramos las variables
        int[][] matriz = new int[255][255];
        String[] vector;
        int i=0, j=0;
            
        try{
            BufferedReader reader = new BufferedReader(new FileReader(fichero));
            while(j<255){
                String linea=reader.readLine();
                vector = linea.split(" ");
                while(i<255){
                    matriz[i][j]=Integer.parseInt(vector[i]);
                    i++;
                }
                i=0;
                j++;
            }
        }catch(Exception e){
                System.out.println("Error la funcion obtener_matriz");
                e.printStackTrace();
        }
        return matriz;
}

    /** Buscamos el eje vertical en el que se encuentra el planeta en una imagen */
    private static int busca_maxim_x(int[][] matriz){
        // Declaramos las variables
        int pos=0, valor_columna=0, maximo_columna=0, i=0, j=0;

        // Buscamos la columna que tiene valor maximo.
        while(i<matriz[0].length){
            while(j<matriz[0].length){
                valor_columna+=matriz[i][j];
                j++;
            }

            if(maximo_columna<valor_columna){
                pos=i;
                maximo_columna=valor_columna;
            }
            valor_columna=0;
            j=0;
            i++;
        }
        return pos;
    }

    /** Buscamos el eje vertical en el que se encuentra el planeta en una imagen */
    private static int busca_maxim_y(int[][] matriz){
        int pos=0, valor_fila=0, maximo_fila=0, i=0, j=0;
		
        // Buscamos la fila que tiene valor maximo.
        while(i<matriz[0].length){
            while(j<matriz[0].length){
                valor_fila+=matriz[j][i];
                j++;
            }
            if(maximo_fila<valor_fila){
                pos=i;
                maximo_fila=valor_fila;
            }
            valor_fila=0;
            j=0;
            i++;
        }
        return pos;
    }

    
    /** Lee las imagenes de los ficheros */
    private int LeeImagenes(){
        // Declaramos las variables
        this.imagenes=new int[NImagenesUsadas][255][255];
        int i=1, j=0;
        
        while (j<NImagenesUsadas) {
            
            // Abrimos el fichero
            String NombreImagenCompleto=NombreImagenInicio+i+".txt";
            File fichero=new File(NombreImagenCompleto);

            // Comprobamos que exista el fichero
            if(!fichero.exists()){
                System.out.print("El fichero '"+NombreImagenCompleto+"' no existe\n");
                return -1;
            }
            
            // Leemos la imagen
            imagenes[j]=LeeImagen(NombreImagenCompleto);
            
            // Avanzamos a la siguiente imagen
            j++;
            i+=NImagenesASaltar;
        }
        return 0;
    }
        
    
    /** Calcula los centros de los asteroides de una secuencia de imagenes */
    private void CalculaCentros(boolean inverso){
        // Declaramos las variables
        x = new int[NImagenesUsadas];
        y = new double[NImagenesUsadas];
        
        // Calculamos el centro de la primera imagen para tomar el punto como origen de coordenadas
        xinicial=busca_maxim_x(imagenes[0]);
        yinicial=busca_maxim_y(imagenes[0]);
        if (inverso) {
            parametros[0]=yinicial;
            incremento=xinicial;
        } else {
            parametros[0]=xinicial;
            incremento=yinicial;
        }
        // Calculamos los centros
        for (int i=0; i<NImagenesUsadas; i++) {
            if (inverso) {
                x[i]=busca_maxim_x(imagenes[i]);
                y[i]=busca_maxim_y(imagenes[i]);
            } else {
                y[i]=busca_maxim_x(imagenes[i]);
                x[i]=busca_maxim_y(imagenes[i]);
            }
        }
    }
        
    /** Calculo del radio del satelite como la media de los radios */
    private double CalculaRadios(){
        // Daclaramos las variables
        double[] radios=new double [imagenes.length];
        double radioMedio=0;
        int inicio=0, fin=0;
        
        // Recorremos todas las imagenes
        for (int i=0; i<imagenes.length; i++) {
            // Contamos el numero de pixels del diametro horizontal
            for (int j=0; j<(254); j++) {
                if(imagenes[i][x[i]][j]==0 && imagenes[i][x[i]][j+1]!=0) inicio=j;
                if(imagenes[i][x[i]][j]!=0 && imagenes[i][x[i]][j+1]==0) fin=j;
            }
            radios[i]=(fin-inicio)/2;
            radioMedio=radioMedio+fin-inicio;
            
        }
        radioMedio=radioMedio/imagenes.length/2;
        System.out.println("RadioMedio: "+radioMedio);
        
        return radioMedio;
}
    
    /** Calcula el valor de una gausiana con los parametros dados */
    private static double calculo_gauss(double x, int mu, double radio){

        // Calculamos el exponente de la gausiana: exp=-(0.5)*((x-mu)/radio)^2
        double exp=-(0.5)*Math.pow(((x-mu)/radio),2);
        // Calculamos el valor de la gausiana: (Ni/sqrt(2*pi)*radio)*e^(exp)
        double valor=(1/(Math.sqrt(2*3.1416159))*radio)*Math.exp(exp);

        // Calculamos para area=1 (Ni=1). Esto indica que el valor maximo de la gaussiana sera 0.4.

//        System.out.println("mu="+mu+"  x="+x+"   valor RADIO="+radio);
//        System.out.println("VALOR X-MU/RADIO="+Math.pow(((x-mu)/radio),2));
//        System.out.println("VALOR CARRO="+carro);
//        System.out.println("VALOR EXPONENTE="+Math.exp(carro));
//        System.out.println("VALOR GAUSIANA="+valor);

        // Devolvemos el valor de la gausiana con esos parametros
        return valor;
    }
    
    private double CalculaRadioGauss(){

        // La intensidad maxima de un pixel es 255
        // El valor maximo de una gausiana de area 1 es 0.4
        
        // Suponemos que la maxima intensidad de un pixel es 255 y para este valor el valor maximo d la gaussiana es 0.4 (en area =1)
        // Tenemos que calcular cual sera el valor de la gaussiana para el valor  promedio de la intensidad que pasemos a esta funcion
        // una vez sepamos el valor de la gaussiana -> podremos empezar con las  comparaciones que mas se parezcan con el radio calculado
        // mediante HILL CLIMBING (pq ya sabemos el estado final -> "el valor dela gaussiana").

        // valor_y es el valor que cobraria la intensidad del pixel central suponiendo que la intensidad maxima de pixel es 255 y que
        // para este valor su valor m‡ximo de gaussiana es 0.4. Es decir transformamos el valor central de los .txt al valor que cobra 
        // en la gaussiana.

        // Calculamos el valor medio de la intensidad de los centros de los satelites
        double media_maximos=0;
        for (int i=0; i<this.NImagenesUsadas; i++) {
            media_maximos+=imagenes[i][(int)x[i]][(int)y[i]];
        }
        media_maximos=media_maximos/this.NImagenesUsadas;
        
        // Obtenemos las coordenadas del centro del primer satelite
        int maxim_x=x[0];
        int maxim_y=(int)y[0];
        
        // Definimos las variables
        double radio=0;
        double valor_y=(media_maximos*0.4)/255;     // Estado objetivo
        int mu = maxim_y;
        double valor_x=maxim_x+mu;
        double error_minimo = 0.001;                // Error maximo admisible (Condicion de parada)
        double error=999999;                        // Error inicial
        double[] opc = new double[6];               // Posibles cambios en el radio (Hijos)
        int opci=0;
        
        // Mientras el error no sea admisible, vamos ajustando la gausiana
        while(error_minimo<error){

            // Calculamos el nuevo error para los 6 hijos (Valor de la gausiana - valor objetivo)
            opc[0]=Math.abs(calculo_gauss(valor_x, mu, radio+0.001)-valor_y);
            opc[1]=Math.abs(calculo_gauss(valor_x, mu, radio-0.001)-valor_y);
            opc[2]=Math.abs(calculo_gauss(valor_x, mu, radio+0.01)-valor_y);
            opc[3]=Math.abs(calculo_gauss(valor_x, mu, radio-0.01)-valor_y);
            opc[4]=Math.abs(calculo_gauss(valor_x, mu, radio+0.1)-valor_y);
            opc[5]=Math.abs(calculo_gauss(valor_x, mu, radio-0.1)-valor_y);

            // Nos quedamos con el hijo que más disminuya el error
            for(int i=0;i<6;i++) {
                if(error>opc[i]) {
                    error=opc[i];
                    opci=i;
                }
            }
            if (opci==0) radio=radio+0.001;
            if (opci==1) radio=radio-0.001;
            if (opci==2) radio=radio+0.01;
            if (opci==3) radio=radio-0.01;
            if (opci==4) radio=radio+0.1;
            if (opci==5) radio=radio-0.1;

            System.out.println("ELIJO ESTA OPCION: "+opci);
            System.out.println(valor_x+" -> valor X para gaussiana");
            System.out.println(error+" -> ERROR para el RADIO");
            System.out.println(radio+" -> RADIO en accion");
            System.out.println(media_maximos+" -> VALOR MAXIMO para calculo intesidad");
            System.out.println(valor_y+" -> VALOR Y | "+valor_x+" -> VALOR X | "+mu+" -> VALOR MU");
        }
        // Devolvemos el radio calculado
        return radio;
}
    
    /** Calcula el valor del polinomio en un punto */
    private static double CalculaValor(double[] parametros, int x){
        double valor=parametros[3]*Math.pow(x,3)+parametros[2]*Math.pow(x,2)+parametros[1]*x+parametros[0];
        return valor;
    }
    
    /** Calcula el error del polinomio respecto a todos los centros */
    private double CalculaError(double[] parametros, int[] x, double[] y, boolean resta){
        // Definimos las variables
        double[] error=new double [this.NImagenesUsadas];
        double errorAcumulado=0;
        
        // Calculamos el error que comete en cada punto de la trayectoria
        for (int i=0; i<NImagenesUsadas; i++) {
            if (resta)
                error[i]=CalculaValor(parametros, x[i]-(int)this.incremento)-y[i];
            else
                error[i]=CalculaValor(parametros, x[i])-y[i];
            error[i]=Math.abs(error[i]);
            errorAcumulado+=error[i];
        }
        errorAcumulado=errorAcumulado/NImagenesUsadas;
        return errorAcumulado;
    }

    
    private double CambiaParametros(double[] parametros, int[] x, double[] y, double precision){
        // Definimos las variables
        double[] NuevosParametros = new double[4];
        double[] aux = new double[4];
        double error, errorInicial;

        // Calculamos el error cometido
        error = CalculaError(parametros, x, y,resta);
        errorInicial=error;
        
        NuevosParametros[3]=parametros[3];
        NuevosParametros[2]=parametros[2];
        NuevosParametros[1]=parametros[1];
        NuevosParametros[0]=parametros[0];

        for (int ia=-1; ia<=1; ia++) {
            for (int ib=-1; ib<=1; ib++) {
                for (int ic=-1; ic<=1; ic++) {
                    aux[3]=NuevosParametros[3]+ia*(precision)*0.001;
                    aux[2]=NuevosParametros[2]+ib*(precision)*0.01;
                    aux[1]=NuevosParametros[1]+ic*(precision)*0.1;
                    aux[0]=NuevosParametros[0];
                    double errorObtenido=CalculaError(aux, x, y, resta);

                    if (errorObtenido<error) {
                        parametros[3]=aux[3];
                        parametros[2]=aux[2];
                        parametros[1]=aux[1];
                        parametros[0]=aux[0];
                        error=errorObtenido;
                    }
                }
            }
        }
        
        return error;
    }
    
    /** Calculo de la trayectoria mediante el metodo de steeped ascend */
    public double CalculaTrayectoria(){   
        // Declaramos las variables auxiliares
        double error, NuevoError=0, precision;
        int i=0;
        
        precision= this.PrecisionInicial;
        // Calculamos el error inicial
        error=CalculaError(parametros, x, y, resta);
        
        //Cambiamos los parametros
        while ((i<MaxIters)&&(error>MaxError)&&(precision>MaxPrecision)) {

            NuevoError=CambiaParametros(parametros, x, y, precision);
            if (i%100==0)
                System.out.println("Iteracion "+i+". Error "+NuevoError+". Precision: "+(precision));
            
            if (NuevoError!=error) { 
                error=NuevoError;
            } else {
                precision*=this.IncrementoPrecision;
                System.out.println("Iteracion: "+i+" Maximo Local encontrado. Error: "+error+" Nueva Precision:"+precision);
            }
            i++;
        }
        
        // Calculamos el polinomio tambien por minimos cuadrados
        double []xd = new double[NImagenesUsadas];
        for (int w=0; w<NImagenesUsadas; w++) xd[w]=(double)x[w];
        this.poli=new PolRegresion(xd, y, 3);
        poli.calculaPolinomio();
        
        return error;
    }
        
    private void CalcularSalidas() {
        y1= new double[NImagenesUsadas];
        y2= new double[NImagenesUsadas];
        e1= new double[NImagenesUsadas];
        e2= new double[NImagenesUsadas];
        sumE1=0;
        sumE2=0;
        
        for (int i=0; i<NImagenesUsadas; i++) {
            y1[i]=CalculaValor(poli.a,x[i]);
            if (resta)
                y2[i]=CalculaValor(parametros, x[i]-this.incremento);
            else
                y2[i]=CalculaValor(parametros, x[i]);
            e1[i]=Math.abs(y[i]-y1[i]);
            e2[i]=Math.abs(y[i]-y2[i]);
            System.out.println("("+ x[i] +"," + y[i]+")-("+ x[i] +"," + y1[i]+")-("+ x[i] +"," + y2[i]+")-e1="+e1[i]+" e2="+e2[i]);
            sumE1+=e1[i];
            sumE2+=e2[i];
        }
        sumE1=(sumE1/NImagenesUsadas);
        sumE2=(sumE2/NImagenesUsadas);
        System.out.println("E1="+sumE1+" E2="+sumE2);
    }
    
    // Dada una matriz de pixeles dibuja la funcion del color dado
    private static void DibujaFuncion(int [][]matriz, int []x, double []y, int color, boolean inversa) {
        if (!inversa) {
            for (int i=0; i<x.length; i++){
                if (((int)y[i]<255)&&((int)x[i]<255)&&((int)x[i]>0)&&((int)y[i]>0))
                    matriz[(int)x[i]][(int)(y[i])]=color;
            }
        } else {
            for (int i=0; i<x.length; i++){
                if (((int)y[i]<255)&&((int)x[i]<255)&&((int)x[i]>0)&&((int)y[i]>0))
                    matriz[(int)y[i]][(int)(x[i])]=color;
            }
        }
    }
    
    public int[] CreaImagen(){
        
        // Calculamos las salidas de las funciones
        CalcularSalidas();
        
        // Declaramos las variables
        int [][]matriz=new int[iniAncho][iniAlto];
        int []vector=new int[(iniAncho)*(iniAlto)];
        
        // Ponemos el fondo en blanco
        for (int i=0; i<iniAncho; i++){
            for (int j=0; j<iniAlto; j++){
                matriz[i][j]=Color.white.getRGB();
            }
        }
        
        // Dibujamos la trayectoria real en negro
        DibujaFuncion(matriz, x, y, Color.black.getRGB(), inverso);
        
        // Dibujamos la trayectoria de minimos cuadrados en azul
        DibujaFuncion(matriz, x, y1, Color.blue.getRGB(), inverso);
        
        // Dibujamos la trayectoria de stepest-ascent en amarillo
        DibujaFuncion(matriz, x, y2, Color.red.getRGB(), inverso);
        
        // Pasamos la matriz a forma vectorial
        int k=0;
        for (int i=0; i<iniAncho; i++){
            for (int j=0; j<iniAlto; j++){
                vector[k]=matriz[i][j];
                k++;
            }
        }
        
        // Devolvemos la matriz de la imagen
        return vector;
    }

    public String getPolinomioStepest() { 
        return(parametros[3]+"x3 + "+parametros[2]+"x2 + "+parametros[1]+"x + "+parametros[0]);
    }
    
    public String getPolinomioMinimos() {
        return(poli.a[3]+"x3 + "+poli.a[2]+"x2 + "+poli.a[1]+"x + "+poli.a[0]);
    }
    
    public double getRadio() {
        return this.radio;
    }
    
    public double getSumE1() {
        return this.sumE1;
    }
    
    public double getSumE2() {
        return this.sumE2;
    }
}