/* 
* Copyright 2016 Agencia Estatal de Administración Tibutaria
*
* Licencia con arreglo a la EUPL, Versión 1.1 o –en cuanto
* sean aprobadas por la Comisión Europea– versiones
* posteriores de la EUPL (la «Licencia»);
* Solo podrá usarse esta obra si se respeta la Licencia.
* Puede obtenerse una copia de la Licencia en:
*  
* https://joinup.ec.europa.eu/software/page/eupl
*  
* Salvo cuando lo exija la legislación aplicable o se acuerde
* por escrito, el programa distribuido con arreglo a la
* Licencia se distribuye «TAL CUAL», SIN GARANTÍAS NI CONDICIONES
* DE NINGÚN TIPO, ni expresasni implícitas.
*
* Véase la Licencia en el idioma concreto que rige los permisos y 
* limitaciones que establece la Licencia.
*/ 

package javax.swing.grid;

import java.awt.Component;
import java.awt.event.*;
import java.awt.Font;
import java.io.Serializable;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class JGrid extends JTable implements Serializable {

    private int cabeceraColumnaPulsada = -1;
    private int celdaFilaPulsada = -1;
    private int celdaColumnaPulsada = -1;
    private ArrayList<Integer> columnasBotones;
    private JLabel botonFila;
    private boolean cabeceraResaltada = false;
    private boolean cabeceraPulsable = false;
    private IndObj[] indice;
    private int numFilasOrdenadas;
    private int columnaIndice = -1;
    private boolean ordAsc = true;
    private ComparadorOrdenacion comparador = new ComparadorOrdenacion();
    private boolean iconoOrdenacion = true;
    private ImageIcon iconoAscendente;
    private ImageIcon iconoDescendente;
    private BuscadorIncremental busIncr;
    private int indiceGuardado;

    public JGrid() {
        super();
        listenerList = new EventListenerList();
        columnasBotones = new ArrayList<Integer>();
        preparaCabecera();
        preparaBotonFila();
        iconoAscendente = new ImageIcon(getClass().getResource("/recursos/ascendente.png"));
        iconoDescendente = new ImageIcon(getClass().getResource("/recursos/descendente.png"));
        busIncr = new BuscadorIncremental();
    }

    void preparaCabecera() {
        if (cabeceraPulsable || cabeceraResaltada) {
            tableHeader.setDefaultRenderer(new BotonCabeceraRenderer());
        }
        if (cabeceraPulsable) {
            tableHeader.addMouseListener(new HeaderMouseListener());
        }
    }

    void preparaBotonFila() {
        botonFila = new JLabel();
        botonFila.setBackground(tableHeader.getBackground());
        Font fuente = botonFila.getFont();
        botonFila.setFont(fuente.deriveFont(Font.PLAIN));
        botonFila.setOpaque(true);
    }

    public JLabel etiquetaBoton() {
        return botonFila;
    }

    public void ponColumnaFija(int v) {
        if (columnasBotones.contains(v)) {
            return;
        }
        columnasBotones.add(v);
        columnModel.getColumn(v).setCellRenderer(new BotonFilaRenderer());
    }

    public void quitaColumnaFija(int v) {
        if (!columnasBotones.contains(v)) {
            return;
        }
        columnasBotones.remove(v);
        columnModel.getColumn(v).setCellRenderer(new DefaultTableCellRenderer());
    }

   @Override
    protected void processMouseEvent(MouseEvent e) {
        int colTabla = columnAtPoint(e.getPoint());
        int colModel = convertColumnIndexToModel(colTabla);
        if (columnasBotones.contains(colModel)) {
            int fila = rowAtPoint(e.getPoint());
            switch (e.getID()) {
                case MouseEvent.MOUSE_PRESSED:
                    celdaFilaPulsada = fila;
                    celdaColumnaPulsada = colTabla;
                    repaint();
                    break;
                case MouseEvent.MOUSE_RELEASED:
                    celdaFilaPulsada = -1;
                    lanzaCeldaPulsada(e, fila, colModel);
                    repaint();
                    break;
            }
        }
        super.processMouseEvent(e);
    }

    class BotonCabeceraRenderer extends DefaultTableCellRenderer {

        Border bordeNormal;
        Border bordePresionado;

        public BotonCabeceraRenderer() {
            if (cabeceraResaltada) {
                bordeNormal = BorderFactory.createBevelBorder(SoftBevelBorder.RAISED);
                bordePresionado = null;
            } else {
                bordeNormal = BorderFactory.createEtchedBorder();
                bordePresionado = BorderFactory.createBevelBorder(SoftBevelBorder.LOWERED);
            }
        }

      @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                boolean hasFocus, int row, int column) {
            if (column == cabeceraColumnaPulsada) {
                setBorder(bordePresionado);
            } else {
                setBorder(bordeNormal);
            }
            setHorizontalAlignment(SwingConstants.CENTER);
            setHorizontalTextPosition(SwingConstants.LEADING);
            setValue(value);
            if (iconoOrdenacion) {
                if (convertColumnIndexToModel(column) == columnaIndice) {
                    if (ordAsc) {
                        this.setIcon(iconoAscendente);
                    } else {
                        this.setIcon(iconoDescendente);
                    }
                } else {
                    this.setIcon(null);
                }
            }
            return this;
        }
    }

    class BotonFilaRenderer extends DefaultTableCellRenderer {

        Border bordeNormal;
        Border bordePresionado;

        public BotonFilaRenderer() {
            bordeNormal = BorderFactory.createBevelBorder(SoftBevelBorder.RAISED);
            bordePresionado = null;
        }

      @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                boolean hasFocus, int row, int column) {
            if (row == celdaFilaPulsada && column == celdaColumnaPulsada) {
                botonFila.setBorder(bordePresionado);
            } else {
                botonFila.setBorder(bordeNormal);
            }
            botonFila.setText((String) value);
            return botonFila;
        }
    }

    class HeaderMouseListener extends MouseAdapter {

      @Override
        public void mousePressed(MouseEvent ev) {
            int col = tableHeader.columnAtPoint(ev.getPoint());
            cabeceraColumnaPulsada = col;
            tableHeader.repaint();
        }

      @Override
        public void mouseReleased(MouseEvent ev) {
            if (cabeceraColumnaPulsada != -1) {
                int sePulso = cabeceraColumnaPulsada;
                cabeceraColumnaPulsada = -1;
                lanzaCabeceraPulsada(ev, sePulso);
                tableHeader.repaint();
            }
        }
    }

    public boolean isCabeceraResaltada() {
        return cabeceraResaltada;
    }

    public void setCabeceraResaltada(boolean cabeceraResaltada) {
        if (this.cabeceraResaltada != cabeceraResaltada) {
            this.cabeceraResaltada = cabeceraResaltada;
            preparaCabecera();
        }
    }

    public boolean isCabeceraPulsable() {
        return cabeceraPulsable;
    }

    public void setCabeceraPulsable(boolean cabeceraPulsable) {
        if (cabeceraPulsable != this.cabeceraPulsable) {
            this.cabeceraPulsable = cabeceraPulsable;
            preparaCabecera();
        }
    }

   @Override
    public int getSelectedRow() {
        if (indice == null) {
            return super.getSelectedRow();
        }
        if (indice.length == 0) {
            return 0;
        }
        int r = super.getSelectedRow();
        if (columnaIndice == -1 || r == -1) {
            return r;
        }
        return indice[r].idx;
    }

    /**
     * Gestion de eventos de las cabeceras
     */
    public void addCabeceraPulsadaListener(CabeceraPulsadaListener listener) {
        listenerList.add(CabeceraPulsadaListener.class, listener);
    }

    public void removeCabeceraPulsadaListener(CabeceraPulsadaListener listener) {
        listenerList.remove(CabeceraPulsadaListener.class, listener);
    }

    public void lanzaCabeceraPulsada(MouseEvent ev, int columna) {
        CeldaPulsadaEvent evt = new CeldaPulsadaEvent(this, 0, columna, ev.getModifiersEx());
        EventListener[] listeners = listenerList.getListeners(CabeceraPulsadaListener.class);
        for (int i = 0; i < listeners.length; i++) {
            CabeceraPulsadaListener listener = (CabeceraPulsadaListener) listeners[i];
            listener.cabeceraPulsada(evt);
        }
    }

    public int busquedaIncremental(char c) {
        return busIncr.buscarIncremental(c);
    }

    /*********************************************************
     * Gestion de eventos de los botones de columna
     *******************************************************/
    public void addCeldaPulsadaListener(CeldaPulsadaListener listener) {
        listenerList.add(CeldaPulsadaListener.class, listener);
    }

    public void removeCeldaPulsadaListener(CeldaPulsadaListener listener) {
        listenerList.remove(CeldaPulsadaListener.class, listener);
    }

    public void lanzaCeldaPulsada(MouseEvent e, int fila, int columna) {
        CeldaPulsadaEvent evt = new CeldaPulsadaEvent(this, fila, columna, e.getModifiersEx());
        EventListener[] listeners = listenerList.getListeners(CeldaPulsadaListener.class);
        for (int i = 0; i < listeners.length; i++) {
            CeldaPulsadaListener listener = (CeldaPulsadaListener) listeners[i];
            listener.celdaPulsada(evt);
        }
    }

    /*********************************************************************
     * Ordenacion del grid
     *********************************************************************/
   @Override
    public Object getValueAt(int row, int column) {
        int col = convertColumnIndexToModel(column);
        if (columnaIndice == -1) {
            return getModel().getValueAt(row, col);
        } else {
            if (indice.length != getModel().getRowCount()) {
                ordenar();
            }
            return getModel().getValueAt(indice[row].idx, col);
        }
    }

    public boolean isIconoOrdenacion() {
        return iconoOrdenacion;
    }

    public void setIconoOrdenacion(boolean value) {
        iconoOrdenacion = value;
    }

    public void quitarOrden() {
        columnaIndice = -1;
    }

    public boolean ordenAscendente() {
        return ordAsc;
    }

    public int columnaOrdenacionDatos() {
        return columnaIndice;
    }

    public void ordenar(int columna, boolean invertir) {
        if (!(dataModel instanceof SortTableModel)) {
            return;
        }
        columna = convertColumnIndexToModel(columna);
        if (columna == columnaIndice) {
            if (invertir) {
                ordAsc = !ordAsc;
            }
        } else {
            ordAsc = true;
            columnaIndice = columna;
        }
        Object ob1 = ((SortTableModel) dataModel).getSortValueAt(0, columna);
        if (!(ob1 instanceof Comparable)) {
            return;
        }
        numFilasOrdenadas = dataModel.getRowCount();
        indice = new IndObj[numFilasOrdenadas];
        for (int i = 0; i < numFilasOrdenadas; i++) {
            indice[i] = new IndObj(i, ((SortTableModel) dataModel).getSortValueAt(i, columna));
        }
        Arrays.sort(indice, comparador);
    }

    public void ordenar() {
        numFilasOrdenadas = dataModel.getRowCount();
        indice = new IndObj[numFilasOrdenadas];
        for (int i = 0; i < numFilasOrdenadas; i++) {
            indice[i] = new IndObj(i, ((SortTableModel) dataModel).getSortValueAt(i, columnaIndice));
        }
        Arrays.sort(indice, comparador);
    }

    public boolean comprobarYOrdenar() {
        if (columnaIndice == -1) {
            return false;
        }
        if (indice.length != getModel().getRowCount()) {
            ordenar();
            return true;
        }
        return false;
    }

    public void guardaPosicion() {
        indiceGuardado = getSelectedRow();
    }

    public void bajarUnaLinea() {
        int i = getSelectedRow();
        changeSelection(i + 1, 0, false, false);
    }

    public void recuperarPosicion() {
        if (indiceGuardado == -1) {
            return;
        }
        if (columnaIndice >= 0) {
            for (int i = 0; i < indice.length; i++) {
                if (indice[i].idx == indiceGuardado) {
                    changeSelection(i, 0, false, false);
                    return;
                }
            }
        } else {
            changeSelection(indiceGuardado, 0, false, false);
        }
    }

    class ComparadorOrdenacion implements Comparator<IndObj> {

      @SuppressWarnings("unchecked")
      @Override
        public int compare(IndObj o1, IndObj o2) {
            Comparable c1 = (Comparable) o1.valor;
            Comparable c2 = (Comparable) o2.valor;
            if (ordAsc) {
                return c1.compareTo(c2);
            } else {
                return c2.compareTo(c1);
            }
        }
    }

    class IndObj {

        int idx;
        Object valor;

        IndObj(int n, Object ob) {
            idx = n;
            valor = ob;
        }
    }

    public class BuscadorIncremental {

        long tiempoTecla = 0;
        long maxTiempo = 1500;
        StringBuffer claveAcumulada = new StringBuffer();

        public BuscadorIncremental() {
        }

        public int buscarIncremental(char c) {
            if (columnaIndice == -1) {
                return -1;
            }
            if ((int) c == 27 || c == '\b') {
                claveAcumulada.delete(0, 20);
                return -1;
            }
            try {
                long tiempoActual = System.currentTimeMillis();
                if ((tiempoActual - tiempoTecla) > maxTiempo) {
                    claveAcumulada.delete(0, 20);
                }
                claveAcumulada.append(c);
                tiempoTecla = tiempoActual;
                IndObj claveBuscar = new IndObj(0, claveAcumulada.toString().toUpperCase());
                int r = Arrays.binarySearch(indice, claveBuscar, comparador);
                if (r < 0) {
                    r = Math.abs(r + 1);
                }
                if (r >= indice.length) {
                    r = indice.length - 1;
                } else if (!ordAsc && r > 0) {
                    r--;
                }
                return r;
            } catch (Exception exc) {
            }
            return -1;
        }
    }
}
