import java.lang.Math.*;
import java.util.*;

/**
 * Un cubo que representa una de las piezas del cubo rubik completo.
 * Contiene informacion de la ubicacion espacial de sus ocho vertices
 * y sus caras.
 * @author Carlos Cortes
 */
public class Pieza implements Comparable<Pieza>
{
   /**
    * Punto desde donde son vistas las piezas por el observador (la camara).
    */
   private static Point3D vista;
   
   /**
    * Arreglo con los ocho vertices del cubo que representa esta Pieza.
    */
   private Point3D[] vertice = {null,null,null,null,null,null,null,null};
   
   /**
    * Arreglo con las 12 Cara3D (dos triangulos por cada cara cuadrada).
    */
   private Cara3D[] cara = {null,null,null,null,null,null,
                            null,null,null,null,null,null};
   
   /**
    * Determina la orientacion de la Pieza (rotaciones).
    */
   private Ejes3D eje;
   
   /**
    * Crea e inicializa una Pieza.
    * @param x posicion en x
    * @param y posicion en y
    * @param z posicion en z
    * @param a color de la cara que mira arriba
    * @param i color de la cara que mira izquieda
    * @param f color de la cara que mira frente
    * @param d color de la cara que mira derecha
    * @param t color de la cara que mira atras
    * @param b color de la cara que mira abajo
    */
   public Pieza(int x, int y, int z, int a, int i, int f, int d, int t, int b)
   {
      vertice[0] = new Point3D(x*2-.94f, y*2-.94f, z*2-.94f);
      vertice[1] = new Point3D(x*2+.94f, y*2-.94f, z*2-.94f);
      vertice[2] = new Point3D(x*2+.94f, y*2+.94f, z*2-.94f);
      vertice[3] = new Point3D(x*2-.94f, y*2+.94f, z*2-.94f);
      vertice[4] = new Point3D(x*2-.94f, y*2-.94f, z*2+.94f);
      vertice[5] = new Point3D(x*2+.94f, y*2-.94f, z*2+.94f);
      vertice[6] = new Point3D(x*2+.94f, y*2+.94f, z*2+.94f);
      vertice[7] = new Point3D(x*2-.94f, y*2+.94f, z*2+.94f);
      
      cara[0]  = new Cara3D(this, 4, 5, 6, a);
      cara[1]  = new Cara3D(this, 6, 7, 4, a);
      cara[2]  = new Cara3D(this, 0, 1, 5, i);
      cara[3]  = new Cara3D(this, 5, 4, 0, i);
      cara[4]  = new Cara3D(this, 1, 2, 6, f);
      cara[5]  = new Cara3D(this, 6, 5, 1, f);
      cara[6]  = new Cara3D(this, 2, 3, 7, d);
      cara[7]  = new Cara3D(this, 7, 6, 2, d);
      cara[8]  = new Cara3D(this, 3, 0, 4, t);
      cara[9]  = new Cara3D(this, 4, 7, 3, t);
      cara[10] = new Cara3D(this, 0, 3, 2, b);
      cara[11] = new Cara3D(this, 2, 1, 0, b);
      
      eje = new Ejes3D();
   }
   
   /**
    * Usado por clone().
    */
   private Pieza(Point3D[] ve, Cara3D[] ca, Ejes3D ej)
   {
      vertice = ve;
      cara = ca;
      eje = ej;
   }
   
   /**
    * Cambia la referencia punto desde donde son vistas las Piezas.
    * @param _v Nuevo punto de vista.
    */
   public static void setVista(Point3D _v)
   {
      vista = _v;
   }
   
   /**
    * Retorna el arraglo de vertices de esta Pieza.
    * @return El arraglo de vertices de esta Pieza.
    */
   public Point3D[] getVerticeArray()
   {
      return vertice;
   }
   
   /**
    * Retorna el arraglo de caras de esta Pieza.
    * @return El arraglo de caras de esta Pieza.
    */
   public Cara3D[] getCaraArray()
   {
      return cara;
   }
   
   /**
    * Retorna los ejes de esta Pieza.
    * @return Los ejes de esta Pieza.
    */
   public Ejes3D getEjes()
   {
      return eje;
   }
   
   /**
    * Retorna un Point3D que indica el punto central de esta pieza en el
    * espacio 3D.
    * @return Un Point3D que indica el punto central de esta pieza en el
    * espacio 3D.
    */
   public Point3D getCentro()
   {
      float _x=0, _y=0, _z=0;
      Point3D pTemp;
      for (int j=0; j<8; j++)
      {
         pTemp = vertice[j].clone();
         pTemp.normalizar(eje);
         _x += pTemp.getX();
         _y += pTemp.getY();
         _z += pTemp.getZ();
      }
      _x /= 8;
      _y /= 8;
      _z /= 8;
      Point3D centro = new Point3D(_x, _y, _z);
      return centro;
   }
   
   /**
    * Rota los ejes de esta Pieza sobre el eje X.
    * @param giro grados del giro en sentido horario.
    */
   public void rotar_X(float giro)
   {
      float vy, vz;
      vy = (float)Math.cos(Math.PI*giro/180);
      vz = (float)Math.sin(Math.PI*giro/180);
      eje = new Ejes3D(new Point3D(1,   0,   0),
                       new Point3D(0,  vy,  vz),
                       new Point3D(0, -vz,  vy));
   }
   
   /**
    * Rota los ejes de esta Pieza sobre el eje Y.
    * @param giro grados del giro en sentido horario.
    */
   public void rotar_Y(float giro)
   {
      float vx, vz;
      vx = (float)Math.cos(Math.PI*giro/180);
      vz = (float)Math.sin(Math.PI*giro/180);
      eje = new Ejes3D(new Point3D( vx,  0, -vz),
                       new Point3D(  0,  1,   0),
                       new Point3D( vz,  0,  vx));
   }
   
   /**
    * Rota los ejes de esta Pieza sobre el eje Z.
    * @param giro grados del giro en sentido horario.
    */
   public void rotar_Z(float giro)
   {
      float vx, vy;
      vx = (float)Math.cos(Math.PI*giro/180);
      vy = (float)Math.sin(Math.PI*giro/180);
      eje = new Ejes3D(new Point3D( vx, vy, 0),
                       new Point3D(-vy, vx, 0),
                       new Point3D(  0,  0, 1));
   }
   
   /**
    * Rota esta pieza para hacer que sus ejes coincidan con los ejes canonicos.
    */
   public void normalizar()
   {
      for (int i=0; i<8; i++)
         vertice[i].normalizar(eje);
      
      resetEjes();
   }
   
   /**
    * Invoca el metodo 'ajustar()' de los ejes de esta pieza y luego normaliza
    * esta pieza. Sirve para evitar que se acumulen errores en los giros de la
    * Pieza. Por ejemplo despues de hacer muchos giros de 90 grados, se iran
    * acumulando errores por el redondeo a float, que harian que despues de
    * muchos giros, las piezas se vean chuecas.
    */
   public void ajustar()
   {
      eje.ajustar();
      normalizar();
   }
   
   /**
    * Devuelve los ejes de la pieza a sus orientaciones canonicas.
    */
   public void resetEjes()
   {
      eje = new Ejes3D();
   }
   
   /**
    * Crea una copia de esta Pieza.
    * @return Una copia de esta Pieza.
    */
   public Pieza clone()
   {
      Point3D[] ve = {null,null,null,null,null,null,null,null};
      Cara3D[] ca = {null,null,null,null,null,null,null,null,null,null,null,null};
      Ejes3D ej;
      int i;
      
      for (i=0; i<8; i++)
         ve[i] = vertice[i].clone();
      
      for (i=0; i<12; i++)
         ca[i] = cara[i].clone();
      
      ej = eje.clone();
      
      Pieza nueva = new Pieza(ve, ca, ej);
      
      for (i=0; i<12; i++)
         nueva.getCaraArray()[i].setPieza(nueva);
      
      return nueva;
   }
   
   /**
    * Compara la distancia de las piezas respecto a la camara,
    * la mas lejana va primero.
    */
   public int compareTo(Pieza otra)
   {
      Point3D posEsta = getCentro();
      Point3D posOtra = otra.getCentro();
      
      posEsta.menos(Pieza.vista);
      posOtra.menos(Pieza.vista);
      
      float d1 = posEsta.getMagnitud();
      float d2 = posOtra.getMagnitud();
      
      if (d1 == d2) return 0;
      if (d1 > d2) return -1;
      else return 1;
   }
}
