/*
 * Javier Abellán. 30 de Abril de 2003
 *
 * Clase de control para el puzzle. Permite ordenar y desordenar el puzzle.
 */
import java.lang.Runnable;
import java.lang.Thread;
import java.util.LinkedList;

/**
 * Ordena y desordena el puzzle.
 * Para poder ordenar el puzzle, la clase Ordenador se suscribe a los 
 * movimientos del puzzle para almacenarlos y poder repetirlos luego en orden 
 * inverso. Cuando el puzzle esté ordenado, borrará todos los movimientos de
 * su almacén de movimientos.
 * Para desordenar el puzzle se hacen 10 movimientos al azar.
 */
public class Ordenador implements Runnable {
    
    /**
     * Constructor.
     * Se le pasa un modelo de puzzle y se suscribe a sus movimientos.
     */
    public Ordenador (Puzzle unPuzzle)
    {
        this.unPuzzle = unPuzzle;
        
        /* Se crea el observador */
        observador = new ObservadorMovimiento() {
            public void tomaMovimiento (int filaVieja, int columnaVieja,
                int filaNueva, int columnaNueva)
            {
                /* Cuando se produce un movimiento, se guarda dicho movimiento
                 */
                anhadeMovimiento (filaNueva, columnaNueva);
            }
            public void ordenado()
            {
                /* Cuando el puzzle está ordenado, se borran todos los
                 * movimientos almacenados */
                borraMovimientos();
            }
        };
        
        /* Se añade el observador */
        unPuzzle.anhadeObservador(observador);
        
        /* Se crea la lista de movimientos */
        movimientos = new LinkedList();
    }
    
    /**
     * Se le dice a esta clase que ordene el puzzle. Se hace con un hilo
     * separado, de forma que mientras este hilo ordena el puzzle, el hilo
     * de AWT (de eventos y repintados) pueda ir dibujando los movimientos
     * de las piezas.
     */
    public void ordena()
    {
        /* Se pone el comando de ordenar y se le indica al hilo que arranque */
        comando = ORDENA;
        arrancaHilo();
    }
    
    /**
     * Se le dice a la clase que desordene el puzzle. Se hace con un hilo 
     * separado por el mismo motivo que el método ordena().
     */
    public void desordena()
    {
        /* Se pone el comando de desordenar y se le indica al hilo que arranque
         */
        comando=DESORDENA;
        arrancaHilo();
    }
    
    /**
     * Bucle infinito con un retardo y que ejecuta en cada iteración el comando
     * que se indique.
     */
    public void run() {
        while (true)
        {
            /* Si es el comando de ordenar, se ordena el puzzle y se vuelve
             * a poner el comando de no hacer nada. */
            if (comando == ORDENA)
            {
                ordenaPuzzle();
                comando=Ordenador.HACER_NADA;
            }
            
            /* Si el comando es de desordenar, se desordena el puzzle y se
             * vuelve a poner el comando de no hacer nada */
            else if (comando == DESORDENA)
            {
                desordenaPuzzle();
                comando=Ordenador.HACER_NADA;
            }
            else
            {
                /* Si no es ninguno de los dos comandos anteriores, se espera
                 * 100 ms y se vuelve a repetir el proceso */
                try {
                    Thread.sleep (100);}
                catch (Exception e){}
            }
        } /* Fin del while */
    } 
      
    /**
     * Añade el movimiento a la lista de movimientos que se han realizado en
     * el puzzle.
     */
    void anhadeMovimiento (int fila, int columna)
    {
        movimientos.add (new Casilla(fila, columna));
    }
    
    /**
     * Elimina todos los movimientos de la lista de movimientos realizados en
     * el puzzle.
     */
    void borraMovimientos ()
    {
        movimientos.clear();
    }
    
    /**
     * Si el hilo no esta creado, lo crea. En caso contrario no hace nada. 
     */
    void arrancaHilo()
    {
        if (unHilo == null)
        {
            unHilo = new Thread (this);
            unHilo.start();
        }
    }
    
    /**
     * Ordena el puzzle, deshaciendo todos los movimientos que se habían hecho
     * previamente en él.
     */
    void ordenaPuzzle()
    {
        int i;
        Casilla aux;
        if (unPuzzle == null)
            return;
        
        /* Quitamos el observador para no registrar nuestros propios movimientos
         * de ordenación */
        unPuzzle.quitaObservador(observador);
        
        /* Recorremos la lista de movimientos en orden inverso y los vamos
         * deshaciendo en el puzzle */
        for (i=movimientos.size()-1; i>=0; i--)
        {
            aux = (Casilla)movimientos.get(i);
            unPuzzle.mueve (aux.fila, aux.columna);
            
            /* Se pone un retardo para que la interface de usuario vaya moviendo
             * las piezas lentamente */
            try {
            Thread.sleep (500);
            } catch (Exception e){}
        }
        
        /* Volvemos a poner el observador */
        unPuzzle.anhadeObservador(observador);
        
        /* Se supone que el puzzle está ordenado. Borramos nuestra lista de
         * movimientos. Esta llamada hace falta, puesto que al habernos borrado
         * como observadores, el modelo de puzzle no nos avisó que el puzzle
         * estaba ordenado */
        borraMovimientos();
        
    }
    
    /**
     * Desordena el puzzle, realizando 10 movimientos aletatorios en él.
     */
    void desordenaPuzzle ()
    {
        int i;
        int eleccion;
        Casilla [] posiblesMovimientos;
        
        if (unPuzzle == null)
            return;
        
        /* Bucle para realizar 10 movimientos aleatorios */
        for (i=0; i<10; i++)
        {
            /* Se pide a unPuzzle los posibles movimientos válidos en la
             * situación actual del tablero */
            posiblesMovimientos = unPuzzle.damePosiblesMovimientos();
            
            /* Se elige al azar uno de los movimientos */
            eleccion = (int)(Math.random()*posiblesMovimientos.length);
            
            /* Si ya se había realizado algún movimiento antes, se comprueba que
             * el movimiento recién elegido no lo deshace (comprobando con el
             * último movimiento de la lista movimientos). Si es así, se coge
             * el siguiente movimiento posible, yendo al primero si nos pasamos 
             * con la operación módulo */
            
            /* Si hay movimientos anteriores */
            if (movimientos.size() != 0)
                
                /* Si la pieza aleatoria elegida para mover coincide con la 
                 * última pieza movida ... */
                if (posiblesMovimientos[eleccion].compareTo (
                    (Casilla)(movimientos.get(movimientos.size()-1)))==0)
                {
                    /* ... se elige el siguiente movimiento aleatorio,
                     * asegurando de no salirnos del array de movimientos
                     * posibles por medio de la operación módulo % */
                    eleccion=(eleccion+1)%posiblesMovimientos.length;
                }
            
            /* Se realiza el movimiento elegido */
            unPuzzle.mueve (posiblesMovimientos[eleccion].fila,
                posiblesMovimientos[eleccion].columna);
            
            /* Un retardo para que la interface gráfica pueda hacer el
             * movimiento */
            try { Thread.sleep (500);
            } catch (Exception e) {};
        }
    }
    
    /* El modelo con el tablero del puzzle y las piezas */
    Puzzle unPuzzle;
    
    /* Comando que debe realizar el hilo */
    int comando = Ordenador.HACER_NADA;
    
    /* Comando de ordenar */
    static final int ORDENA=0;
    
    /* Comando de desordenar */
    static final int DESORDENA=1;
    
    /* Comando de no hacer nada */
    static final int HACER_NADA=-1;
    
    /* El hilo encargado de ordenar y desordenar */
    Thread unHilo;
    
    /* Lista de movimientos realizados en el puzzle */
    LinkedList movimientos;
    
    /* Observador de movimientos del puzzle */
    ObservadorMovimiento observador;
}


