/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package algoritmobr;

import java.util.*;

/**
 * @author Joel
 * @author Cristian
 */
public class CaliperRotatorio {

    arista parAntipodal;
    ArrayList<vertice> dentro;
    ArrayList<tangente> OR;
    int d, h;

    /**
     * Constructor del caliper Rotatorio
     */
    public CaliperRotatorio() {
        parAntipodal = new arista();
        dentro = new ArrayList();
    }

    /**
     * Ingresa un elemento a la Lista de los elementos dentro del Caliper
     * Rotatorio
     *
     * @param e - elemento a ingresar dentro de los limites del caliper
     */
    public void ingresa(vertice e) {
        dentro.add(e);
    }

    /**
     * La cantidad de elementos que se encuentran dentro de las líneas paralelas
     * del caliper Rotatorio en ese instante
     *
     * @return La cantidad de elementos en el Array dentro
     */
    public int elementosDentro() {
        return dentro.size();
    }

    /**
     * Elimina el elemento de la lista de los vertices que están dentro del
     * Caliper Rotatorio
     *
     * @param indice
     */
    public void saleDentro(int indice) {
        dentro.remove(indice);
    }

    /**
     * Se obtienen los elementos dentro del cáliper en ese momento
     *
     * @param indice
     * @return El vértice dado por la posición en el Array
     */
    public vertice getElementoDentro(int indice) {
        return dentro.get(indice);
    }

    /**
     * se encarga de consultar si un elemento del tipo vertice se encuentra
     * dentro de las líneas paralelas del Caliper Rotatorio en ese instante
     *
     * @return Verdadero si el elemento se encuentra dentro
     */
    public boolean estaDentro(vertice x, tangente OR) {
        vertice di = new vertice();
        vertice hi = new vertice();
        di.setVertice(0, obtenerY(0, parAntipodal.getDesde(), OR.getM()));
        hi.setVertice(0, obtenerY(0, parAntipodal.getHasta(), OR.getM()));

        if (estaIzquierda(di, parAntipodal.getDesde(), x) && estaIzquierda(parAntipodal.getHasta(), hi, x)) {
            return true;
        } else {
            if (estaIzquierda(hi, parAntipodal.getHasta(), x) && estaIzquierda(parAntipodal.getDesde(), di, x)) {
                return true;
            } else {
                return false;


            }
        }

    }

    /**
     * Método realiza el giro de las líneas paralelas dejando al par Antipodal
     * correcto a la pendiente del elemento del Orden Rotacional
     *
     */
    public void girar(tangente OR, ArrayList<vertice> CH) {
        boolean dh = false;
        while (!aceptaCaliper(OR, CH)) {
            if (OR.getTan() == parAntipodal.getDesde()) {
                avanzarAntipodalHasta(CH);
            } else {
                if (OR.getTan() == parAntipodal.getHasta()) {
                    avanzarAntipodalDesde(CH);
                } else {

                        if( resta(CH,OR,d)<=resta(CH,OR,h))
                            avanzarAntipodalDesde(CH);
                        else
                            avanzarAntipodalHasta(CH);
                }
            }
            if (h != d) {
                parAntipodal.setArista(CH.get(d), CH.get(h));
            }
        }

    }

    /**
     * Busca el primer par antipodal del caliper (un caliper horizontal)
     *
     * @param desde - primer vertice del Cierre Convexo (No necesariamente del
     * Oden Rotacional)
     * @param CH - la Lista del cierre Convexo
     */
    public void buscarAntipodal(vertice desde, ArrayList<vertice> CH) {
        parAntipodal.setDesde(desde);
        int p;
        d = CH.indexOf(desde);
        if (CH.size() == 2) {
            h = 1;
        } else {
            if (CH.size() == 3) {
                if (CH.get(1).getY() > CH.get(2).getY()) {
                    h = 1;
                } else {
                    h = 2;
                }
            } else {

                if (d == CH.size() - 1) {
                    h = 0;
                } else {
                    h = d + 1;
                }

                //para no caer fuera de rango
                if (h == CH.size() - 1) {
                    p = 0;
                } else {
                    p = h + 1;
                }

                while (CH.get(p).getY() > CH.get(h).getY()) {
                    if (h == CH.size() - 1) {
                        h = 0;
                    } else {
                        h++;
                    }
                }
            }
        }
        parAntipodal.setHasta(CH.get(h));
    }

    /**
     * Consulta si los vertices dados son capaces de aceptar un caliper en la
     * dirección de la pendiente dada
     *
     * @param OR - tangente del Orden Rotacional
     * @param CH - Lista del Cierre Convexo
     * @return true si logra acepta Caliper, false si no logra aceptar caliper.
     */
    public boolean aceptaCaliper(tangente OR, ArrayList<vertice> CH) {
        
        if(CH.size()==2)
            return true;
        
        if (parAntipodal.getDesde().equals(parAntipodal.getHasta())) 
            return false;
        
        boolean acepta = false;

        if (parAntipodal.getDesde().equals(OR.getTan())) {
            acepta=corroborarPunto(h, parAntipodal.getHasta(),OR,CH);
        }else{
            if (parAntipodal.getHasta().equals(OR.getTan())) {
                acepta=corroborarPunto(d, parAntipodal.getDesde(),OR,CH);
            }else{
                acepta=false;
            }
        }
           
        return acepta;

    }

    public boolean corroborarPunto(int i, vertice p, tangente OR, ArrayList<vertice> CH)
    {
        vertice pfantasma = new vertice();
        vertice panterior = new vertice();
        vertice psiguiente = new vertice();
        
        if(OR.esInfinita())
            pfantasma.setVertice(p.getX(), 0);
        else
            pfantasma.setVertice(p.getX()-1, obtenerY(p.getX()-1, p, OR.getM()));
        
        if(i==CH.size()-1)
        {
            panterior=CH.get(i-1);
            psiguiente=CH.get(0);
        }else{
            if(i==0)
            {
                panterior=CH.get(CH.size()-1);
                psiguiente=CH.get(i+1);
            }else{
                panterior=CH.get(i-1);
                psiguiente=CH.get(i+1);
            }
        }
        
        //todos los puntos ya estan asignados
        //pregunta crucial
        if((estaIzquierda(pfantasma,p,panterior) && estaIzquierda(pfantasma,p,psiguiente)) ||
                (estaDerecha(pfantasma,p,panterior)&&estaDerecha(pfantasma,p,psiguiente)))
        {
            return true;
        }else
            return false;
    }
    
    /**
     * Calcula la coordenada Y dada la coordenada x, un punto de la recta y la
     * pendiente, formando la ecuación de la recta correspondiente.
     *
     * @param x - coordenada X
     * @param A - Vertice correspondiente a la recta
     * @param m - pendiente de la recta
     * @return la coordenada Y
     */
    public double obtenerY(double x, vertice A, double m) {
        double b = A.getY() - (m * A.getX());
        return ((m * x) + b);
    }

    /**
     * Consulta si un vertice dado está o no a la izquierda de una recta
     *
     * @param A - primer vertice de la recta
     * @param B - segundo vertice de la recta
     * @param C - vertice a consulta
     * @return true si está en la recta o en a la izquierda, false si no lo
     * está.
     */
    public boolean estaIzquierda(vertice A, vertice B, vertice C) {
        if ((((B.getX() - A.getX()) * (C.getY() - A.getY())) - ((B.getY() - A.getY()) * (C.getX() - A.getX()))) >= 0) {
            return true;
        } else {
            return false;
        }
    }
    
        /**
     * Consulta si un vertice dado está o no a la derecha de una recta
     *
     * @param A - primer vertice de la recta
     * @param B - segundo vertice de la recta
     * @param C - vertice a consulta
     * @return true si está en la recta o en a la Derecha, false si no lo
     * está.
     */
    public boolean estaDerecha(vertice A, vertice B, vertice C) {
        if ((((B.getX() - A.getX()) * (C.getY() - A.getY())) - ((B.getY() - A.getY()) * (C.getX() - A.getX()))) <= 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * entrega información del CALIPER ROTATORIO, especialmente el par Antipodal
     *
     * @return un string con la arista del par Antipodal
     */
    public String toString() {
        return parAntipodal.toString();
    }

    /**
     * Consulta si dos segmentos se intersectan dados los vertices que los
     * conforman (comparando con el par antipodal del caliper)
     *
     * @param aIntersectar - arista a consultar
     * @return true si logran intersecarse, false si no se intersecan
     */
    public boolean intersectaCon(arista aIntersectar) {
        if ((estaIzquierda(parAntipodal.getDesde(), parAntipodal.getHasta(), aIntersectar.getDesde()) && !estaIzquierda(parAntipodal.getDesde(), parAntipodal.getHasta(), aIntersectar.getHasta())) || (estaIzquierda(parAntipodal.getDesde(), parAntipodal.getHasta(), aIntersectar.getHasta()) && !estaIzquierda(parAntipodal.getDesde(), parAntipodal.getHasta(), aIntersectar.getDesde()))) {
                        if ((estaIzquierda(aIntersectar.getDesde(), aIntersectar.getHasta(), parAntipodal.getDesde()) && !estaIzquierda(aIntersectar.getDesde(), aIntersectar.getHasta(), parAntipodal.getHasta())) || (estaIzquierda(aIntersectar.getDesde(), aIntersectar.getHasta(), parAntipodal.getHasta()) && !estaIzquierda(aIntersectar.getDesde(), aIntersectar.getHasta(), parAntipodal.getDesde()))) {
                            return true;
                        }
                        else return false;
                    }
        else return false;
    }

    /**
     * Consulta si se intersectan aristas verticales y paralelas
     *
     * @param aIntersectar - arista a comprobar
     * @return true si es alguna de las alternativas, false si no lo es
     */
    private boolean paralelo(arista aIntersectar) {
        if (!colineal(parAntipodal.getDesde(), parAntipodal.getHasta(), aIntersectar.getDesde())) {
            return false;
        }
        if (entre(parAntipodal.getDesde(), parAntipodal.getHasta(), aIntersectar.getDesde())) {
            return true;
        }
        if (entre(parAntipodal.getDesde(), parAntipodal.getHasta(), aIntersectar.getHasta())) {
            return true;
        }
        if (entre(aIntersectar.getDesde(), aIntersectar.getHasta(), parAntipodal.getDesde())) {
            return true;
        }
        if (entre(aIntersectar.getDesde(), aIntersectar.getHasta(), parAntipodal.getHasta())) {
            return true;
        }

        return false;
    }

    /**
     * Consulta si se intersectan aristas verticales y paralelas; además,
     * almacena el punto de intersección
     *
     * @param aIntersectar - arista a comprobar
     * @param p - vértice a almacenar el punto de intersección.
     * @return true si es alguna de las alternativas, false si no lo es
     */
    private boolean paralelo(arista aIntersectar, vertice p) {
        if (!colineal(parAntipodal.getDesde(), parAntipodal.getHasta(), aIntersectar.getDesde())) {
            return false;
        }

        if (entre(parAntipodal.getDesde(), parAntipodal.getHasta(), aIntersectar.getDesde())) {
            asignar(p, aIntersectar.getDesde());
            return true;
        }
        if (entre(parAntipodal.getDesde(), parAntipodal.getHasta(), aIntersectar.getHasta())) {
            asignar(p, aIntersectar.getHasta());
            return true;
        }
        if (entre(aIntersectar.getDesde(), aIntersectar.getHasta(), parAntipodal.getDesde())) {
            asignar(p, parAntipodal.getDesde());
            return true;
        }
        if (entre(aIntersectar.getDesde(), aIntersectar.getHasta(), parAntipodal.getHasta())) {
            asignar(p, parAntipodal.getHasta());
            return true;
        }

        return false;
    }

    /**
     * consulta si tres puntos son colineales
     *
     * @param a
     * @param b
     * @param c
     * @return true si es cierto, false si no son colineales.
     */
    private boolean colineal(vertice a, vertice b, vertice c) {
        return area(a, b, c) == 0;
    }

    /**
     * obtiene el área de un triangulo formado por tres vertices
     *
     * @param a
     * @param b
     * @param c
     * @return un double que es el área de un triángulo
     */
    private double area(vertice a, vertice b, vertice c) {
        return (b.getX() - a.getX()) * (c.getY() - a.getY()) - (c.getX() - a.getX()) * (b.getY() - a.getY());
    }

    /**
     * Asigna a p los datos de a
     *
     * @param p
     * @param a
     */
    private void asignar(vertice p, vertice a) {
        p.setVertice(a.getX(), a.getY());
    }

    /**
     * Comprueba posibilidades de que un punto este en el segmento evaluado
     *
     * @param a
     * @param b
     * @param c
     * @return true si cumple la condición o false si no la cumple.
     */
    private boolean entre(vertice a, vertice b, vertice c) {
        /*
         * Si ab no es vertical, verificar cordenadas x, sino cordenadas y
         */
        if (a.getX() != b.getX()) {
            return ((a.getX() <= c.getX()) && (c.getX() <= b.getX())) || ((a.getX() >= c.getX()) && (c.getX() >= b.getX()));
        } else {
            return ((a.getY() <= c.getY()) && (c.getY() <= b.getY())) || ((a.getY() >= c.getY()) && (c.getY() >= b.getY()));
        }
    }

    public void cambiarAntipodal(ArrayList<vertice> CH, vertice d, vertice h) {
        this.parAntipodal.setArista(d, h);
        this.d=CH.indexOf(d);
        this.h=CH.indexOf(h);
    }
    
    private void avanzarAntipodalDesde(ArrayList<vertice> CH)
    {
        if(this.d==CH.size()-1)
        {
            this.parAntipodal.setDesde(CH.get(0));
            this.d=0;
        }
        else
        {
            this.parAntipodal.setDesde(CH.get(d+1));
            this.d++;
        }
    }
    
    private void avanzarAntipodalHasta(ArrayList<vertice> CH)
    {
        if(this.h==CH.size()-1)
        {
            this.parAntipodal.setHasta(CH.get(0));
            this.h=0;
        }
        else
        {
            this.parAntipodal.setHasta(CH.get(h+1));
            this.h++;
        }
    }
    
    int resta(ArrayList<vertice> CH,tangente OR, int i)
    {
        int tan=CH.indexOf(OR.getTan());
        
        if( tan > i )
            return tan-i;
        else
            return tan+(CH.size()-i+1);
    }
    
}
