package com.fitbank.javascript;

import java.awt.Color;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.event.UndoableEditEvent;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.undo.CompoundEdit;

import com.fitbank.util.Debug;

/**
 * Clase que extiende un DefaultStyledDocument para que contenga un documento de
 * javascript.
 * 
 * @author Fit-Bank
 */
public class JavascriptDocument extends DefaultStyledDocument {
    /**
	 * 
	 */
    private static final long serialVersionUID = 1L;
    private static final String[] palabrasclaveArray = { "if", "else", "while",
            "for", "var", "in", "null", "function", "new", "return", "try",
            "catch", "switch", "case", "break", "default", "false", "true" };
    private boolean cambiando = false;
    private Pattern palabrasclave;
    private Pattern cadena = Pattern
            .compile("(\"[^\"\\\\\\n]*(\\\\.[^\"\\\\]*)*\")|('[^'\\\\\\n]*(\\\\.[^'\\\\]*)*')");
    private Pattern comentario1 = Pattern.compile("(//)[^\n]*$");
    private Pattern comentario2 = Pattern.compile("(/\\*)[^(\\*/)]*(\\*/)");
    private CompoundEdit ce = new CompoundEdit();
    private boolean editado = false;
    private int inicio = 0;
    private int fin = 0;

    /**
     * Constructor por defecto.
     */
    public JavascriptDocument() {
        super();

        String p = "(\\b" + palabrasclaveArray[0] + "\\b)";

        for (int a = 1; a < palabrasclaveArray.length; a++) {
            p += "|(\\b" + palabrasclaveArray[a] + "\\b)";
        }

        palabrasclave = Pattern.compile(p);

        Style def = StyleContext.getDefaultStyleContext().getStyle(
                StyleContext.DEFAULT_STYLE);

        Style regular = addStyle("normal", def);
        StyleConstants.setForeground(def, Color.BLACK);

        Style s;
        s = addStyle("palabraclave", regular);
        StyleConstants.setForeground(s, new Color(0, 110, 180));
        StyleConstants.setBold(s, true);

        s = addStyle("cadena", regular);
        StyleConstants.setForeground(s, Color.BLUE);

        s = addStyle("numero", regular);
        StyleConstants.setForeground(s, Color.ORANGE);

        s = addStyle("comentario", regular);
        StyleConstants.setForeground(s, new Color(0, 196, 0));
    }

    /**
     * Pinta de colores el texto.
     * 
     * @param lugar
     *            Lugar donde pintar
     */
    public void pintar(int lugar) {
        int inicio = Math.max(this.getText().lastIndexOf("\n", lugar - 1), 0);
        int fin = Math.min(this.getText().indexOf("\n", lugar), getLength());
        fin = fin == -1 ? getLength() : fin;

        setInicio(inicio);
        setFin(fin);
        this.pintar();
    }

    /**
     * Pinta de colores el texto.
     */
    public void pintarTodo() {
        if (!cambiando) {
            setInicio(0);
            setFin(getLength());
            this.pintar();
        }
    }

    /**
     * Pinta de colores el texto.
     */
    public void pintar() {
        cambiando = true;

        try {
            Matcher m;
            Style s;
            fin = Math.min(fin, getLength());

            setCharacterAttributes(inicio, fin - inicio, StyleContext
                    .getDefaultStyleContext().getStyle(
                            StyleContext.DEFAULT_STYLE), true);

            if (fin - inicio > 0) {
                String texto = this.getText(inicio, fin - inicio);

                m = palabrasclave.matcher(texto);
                s = getStyle("palabraclave");

                while (m.find()) {
                    setCharacterAttributes(inicio + m.start(), m.end()
                            - m.start(), s, true);
                }

                m = cadena.matcher(texto);
                s = getStyle("cadena");

                while (m.find()) {
                    setCharacterAttributes(inicio + m.start(), m.end()
                            - m.start(), s, true);
                }

                m = comentario1.matcher(texto);
                s = getStyle("comentario");

                while (m.find()) {
                    setCharacterAttributes(inicio + m.start(), m.end()
                            - m.start(), s, true);
                }

                m = comentario2.matcher(texto);

                while (m.find()) {
                    setCharacterAttributes(inicio + m.start(), inicio + m.end()
                            - m.start(), s, true);
                }
            }
        } catch (Exception e) {
            Debug.error("Error al encontrar una posicion: [" + inicio
                    + "-" + fin + "] de " + getLength(), e);
        }

        cambiando = false;
    }

    /**
     * Maneja cambios en el documento.
     * 
     * @param e
     *            Evento
     */
    @Override
    protected void fireUndoableEditUpdate(UndoableEditEvent e) {
        if (ce != null) {
            if (!editado) {
                editado = true;
                ce.addEdit(e.getEdit());
                e.getEdit().replaceEdit(ce);
                super.fireUndoableEditUpdate(e);
            } else if (!cambiando) {
                this.pintar();
                ce = new CompoundEdit();
                ce.addEdit(e.getEdit());
                e.getEdit().replaceEdit(ce);
                super.fireUndoableEditUpdate(e);
            } else {
                ce.addEdit(e.getEdit());
            }
        }
    }

    /**
     * Cambia el inicio de lo que se debe pintar.
     * 
     * @param inicio
     *            Inicio
     */
    public void setInicio(int inicio) {
        this.inicio = inicio < 0 ? 0 : inicio;
    }

    /**
     * Cambia el fin de lo que se debe pintar.
     * 
     * @param fin
     *            Fin
     */
    public void setFin(int fin) {
        this.fin = fin > getLength() - 1 ? getLength() - 1 : fin;
    }

    /**
     * Devuelve el texto de este documento.
     * 
     * @return String con el texto
     */
    public String getText() {
        String t = null;
        try {
            t = this.getText(0, getLength());
        } catch (BadLocationException b) {
            System.err.println("Este error nunca deberia ocurrir");
        }
        return t;
    }

    /**
     * Inserta un texto en el documento.
     * 
     * @param offs
     *            Punto inicial
     * @param str
     *            Texto a insertar
     * 
     * @throws BadLocationException
     *             en caso de error
     */
    public void insert(int offs, String str) throws BadLocationException {
        insertString(offs, str, StyleContext.getDefaultStyleContext().getStyle(
                StyleContext.DEFAULT_STYLE));
        this.pintar(offs);
    }

}
