package com.uvigo.mhp.textPanel;

import com.uvigo.mhp.*;
import com.uvigo.mhp.xml.textPanel.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.text.html.*;
import javax.xml.bind.*;
import com.uvigo.tMaestro.SCOCreator.tool.MainFrame;

// En esta versi�n, por simplicidad, no se han programado gotoNextContext y gotoPreviousContext
// Tampoco se hace nada con los subcontextos.

// La carga de la p�gina es as�ncrona, y cuando se completa se llama al m�todo
// de la interfaz PropertyChangeListener.
// Las anclas (anchors) delimitan contextos, de modo tal que el contexto asociado
// a un ancla se extiende desde esta hasta la siguiente que aparezca en el listado
// cargado del fichero XML con un nombre de contexto asociado; si empieza dicho nombre
// con una "/", termina el contexto anterior; si no, se cierra el contexto anterior
// y empieza uno nuevo.
// Se hace as� porque en un fichero puede haber muchas anclas, de las cuales s�lo interesan
// a efectos de contextos unas pocas. Las anclas que no aparecen en el listado no afectan
// a la definici�n de contextos, pero siguen siendo v�lidas como destinos de hiperenlaces.

public class TextPanel extends JEditorPane implements Serializable, HyperlinkListener, ComponentListener, PropertyChangeListener, ContextHandler {
    public static final int TIME_ALWAYS = 0;
    public static final int TIME_OK = 1;
    public static final int TIME_WRONG = 2;
    public static final int TIME_DELAYED = 4;
    public static final int TIME_LIMITED = 6;
    
    private Vector references; // contiene todas las anclas, incluidas las que no delimitan contextos
    private Vector positions; // contiene las localizaciones de las anclas del vector references
    private Document former; // indica el documento cuyas referencias est�n calculadas
    private int pos;
    
    private JAXBContext jc;
    private DocumentList dl;
    private java.util.List resourceList;
    private java.util.List currentAnchorList;
    private String configurationFile;
    private int delayedTimeMode;
    private int delayedTime;
    private URL currentURL;
    
    private int current;
    private ContextManager contextManager;
    private Vector listeners;
    private Vector contextsInCurrentPage; // contendr� objetos de tipo TextPanelContext
    private String context;
    private String subcontext;
    private String contextToBeSet;
    // al arrancar la aplicaci�n, puede llamarse a setContext sin haberse cargado todav�a
    // la p�gina, y hay que tener eso en cuenta para fijar el contexto cuando se haya cargado.
    
    private boolean cyclic;
    
    public TextPanel() {
        try {
            jc=JAXBContext.newInstance("com.uvigo.mhp.xml.textPanel");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        
        configurationFile="";
        delayedTimeMode=TIME_ALWAYS;
        delayedTime=0;
        currentURL=null;
        contextToBeSet=null;
        setEditable(false);
        setMargin(new Insets(0,0,0,0));
        addHyperlinkListener(this);
        addComponentListener(this);
        addPropertyChangeListener(this);
        setDoubleBuffered(true);
        references=new Vector();
        positions=new Vector();
        
        pos=0;
        
        current=-1;
        contextManager=null;
        listeners=new Vector();
        context="";
        subcontext="";
        contextsInCurrentPage=null;
    }
    
     public int getDelayedTimeMode() {
        return delayedTimeMode;
    }

    public void setDelayedTimeMode(int i) {

        int old = delayedTimeMode;
        if (i != old) {
            delayedTimeMode = i;
            repaint();
            firePropertyChange("delayedTimeMode", new Integer(old), new Integer(i));

        }
    }
    
    public int getDelayedTime() {
        return delayedTime;
    }

    public void setDelayedTime(int m) {
        int old = delayedTime;
        if (m != old) {
            delayedTime = m;
            firePropertyChange("delayedTime", new Integer(old), new Integer(m));
        }
    }
    public String getConfigurationFile() {
        return configurationFile;
    }
    
    public void setConfigurationFile(String s) {
        String old=configurationFile;
      //  if (!(s.equals(old))) {
            try {
                //M: html de inicializaci�n
                if ((s==null) || (s=="")) {
                   setPage(getClass().getResource("/com/uvigo/mhp/ui/empty.html"));
                    return;
                }
                
                
               //M: Direccionamiento relativo a "user.dir"
                String sBase= new String (MainFrame.oMainFrame.sDir+ "/");  //-M-
                URL url=new URL("file:"+sBase+s);
                
                //Parte de s que corresponde a ruta (que no es fichero)
                String[] split = s.split("/");
                String sRuta=new String();
                int z=0;
                for (z=0;z<split.length-1;z++) {
                    sRuta = sRuta.concat(split[z]+"/");
                }
                
                
                //System.out.println("Cargando configuraci�n");
                URLConnection con=url.openConnection();
                
                Unmarshaller u=jc.createUnmarshaller();
                u.setValidating(true);
                dl=(DocumentList) u.unmarshal(con.getInputStream());
                
                configurationFile=s;
                firePropertyChange("configurationFile",old,s);
                
                resourceList=dl.getResource();
                if (resourceList.size()==0) return;
                
                ResourceType rt=(ResourceType) dl.getResource().get(0);
                currentAnchorList=rt.getAnchor();
                
                url=new URL("file:"+sBase+sRuta+rt.getLocator());
                currentURL=url;
                setPage(url);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        //}
    }
    
    public void setPage(URL page) throws IOException {
        if (System.getProperties().getProperty("netbeans.design.time")==null)
            super.setPage(page);
    }
    
    public void setPage(String url) throws IOException {
        if (System.getProperties().getProperty("netbeans.design.time")==null)
            super.setPage(url);
    }
    
    public void hyperlinkUpdate(HyperlinkEvent e) {
        if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
            if (e instanceof HTMLFrameHyperlinkEvent)
                ((HTMLDocument) getDocument()).
                        processHTMLFrameHyperlinkEvent((HTMLFrameHyperlinkEvent)e);
            else {
            try {
                setPage(e.getURL());
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            }
    }
    
    public void scrollToReference(String reference) {
        for (int i=0;i<references.size();i++) {
            String s=(String) references.get(i);
            if (!(s.equalsIgnoreCase(reference)))
                continue;
            
            Rectangle r=getVisibleRect();
            r.y=((Integer) positions.get(i)).intValue()+15;
            scrollRectToVisible(r);
        }
    }
    
    private void calculateReferences() {
        Document d = getDocument();
        if (d==former)
            return;
        
        references.clear();
        positions.clear();
        if (contextsInCurrentPage==null)
            contextsInCurrentPage=new Vector();
        else
            contextsInCurrentPage.clear();
        
        former=d;
        
        if (d instanceof HTMLDocument) {
            HTMLDocument doc = (HTMLDocument) d;
            HTMLDocument.Iterator iter = doc.getIterator(HTML.Tag.A);
            int height=getBounds().height;
            Rectangle vis=getVisibleRect();
            int visibleHeight=vis.height;
            
            try {
                for (; iter.isValid(); iter.next()) {
                    // Vamos recorriendo todas las anclas del documento actual.
                    // Si tienen nombre, pasan a formar parte de los vectores references
                    // y positions. En cuanto a los contextos, si aparecen en currentAnchorList
                    // empiezan un nuevo contexto, salvo que el nombre empiece con "/"
                    // en cuyo caso se cierra el �ltimo contexto, si no estaba cerrado ya.
                    
                    AttributeSet a = iter.getAttributes();
                    String nm = (String) a.getAttribute(HTML.Attribute.NAME);
                    
                    if ((nm==null) || (nm.equals("")))
                        continue;
                    
                    Rectangle r = modelToView(iter.getStartOffset());
                    Integer newPosition=new Integer(r.y-visibleHeight/2);
                    
                    references.addElement(nm);
                    positions.addElement(newPosition);
                    
                    if (currentAnchorList==null)
                        continue;
                    
                    for (int i=0;i<currentAnchorList.size();i++) {
                        // Vemos si el ancla aparece en el fichero de configuraci�n
                        // delimitando alg�n contexto.
                        AnchorType at=(AnchorType) currentAnchorList.get(i);
                        if (!(at.getAnchor().equals(nm)))
                            continue;
                        
                        // El ancla aparece en la posici�n i del vector currentAnchorList.
                        TextPanelContext tpc;
                        String contextName=at.getContextName();
                        if ((contextName==null) || (contextName.equals("")))
                            break; // procede con la siguiente ancla
                        
                        if (contextsInCurrentPage.size()>0) {
                            tpc=(TextPanelContext) contextsInCurrentPage.get(contextsInCurrentPage.size()-1);
                            if (!tpc.finished) {
                                tpc.end=newPosition.intValue()-1;
                                tpc.finished=true;
                            }
                            if (contextName.startsWith("/"))
                                break; // procede con la siguiente ancla
                        }
                        
                        // El ancla abre un nuevo contexto
                        tpc=new TextPanelContext();
                        tpc.anchor=nm;
                        tpc.contextName=contextName;
                        tpc.begin=newPosition.intValue();
                        contextsInCurrentPage.add(tpc);
                    }
                }
            } catch (Exception ex) {
                //ex.printStackTrace();
            }
        }
    }
    
/*    public void showReferences()
    {
        for (int i=0;i<contextsInCurrentPage.size();i++)
            System.out.println(contextsInCurrentPage.get(i));
    }*/
    
    public void propertyChange(PropertyChangeEvent evt) {
        // Este m�todo se utiliza para detectar el instante en que se completa
        // la carga de un documento, al tiempo que tambi�n se ejecuta cada vez
        // que se acciona un hiperenlace
        
        if (!(evt.getPropertyName().equalsIgnoreCase("page")))
            return;
        calculateReferences();
        if (contextToBeSet!=null) {
            setContext(contextToBeSet);
            contextToBeSet=null;
        }
    }
    
    public void componentHidden(ComponentEvent e) {
    }
    
    public void componentMoved(ComponentEvent e) {
        // Cuando se hace scroll, hay que comprobar si se entra en un contexto,
        // buscando s�lo entre los definidos para el documento actual, claro est�.
        
        if (Math.abs(getBounds().y)==pos)
            return;
        
        pos=getVisibleRect().y;
        
        for (int i=0;i<contextsInCurrentPage.size();i++) {
            TextPanelContext tpc=(TextPanelContext) contextsInCurrentPage.get(i);
            if ((pos>=tpc.begin) && (pos<=tpc.end)) {
                // Se ha entrado en el contexto tpc
                if (current!=i) {
                    current=i;
                    context=tpc.contextName;
                    fireEvent();
                }
                return;
            }
        }
        
        // Si no estamos en ning�n contexto, se guarda constancia de ello y se avisa.
        if (current!=-1) {
            current=-1;
            context="";
            fireEvent();
        }
        return;
    }
    
    public void componentResized(ComponentEvent e) {
        former=null; // para obligar a recalcular las posiciones de las anclas
        calculateReferences();
    }
    
    public void componentShown(ComponentEvent e) {
    }
    
    ///////////////////////////////////////////////////////////////////////////////7
    // GESTION DE CONTEXTOS
    ///////////////////////////////////////////////////////////////////////////////7
    
    public String getContext() {
        return context;
    }
    
    public void setContext(String str) {
        // Puede fijarse un contexto antes de completarse la carga de la p�gina,
        // y hay que cubrir esa circunstancia. El problema tiene lugar al arrancar
        // la aplicaci�n, por lo cual se identifica si se llama a setContext siendo
        // contextsInCurrentPage=null (m�s adelante, como poco es un vector vac�o).
        if (contextsInCurrentPage==null) {
            contextToBeSet=str;
            return;
        }
        
        if ((str==null) || (str.equals("")))
            return;
        if (resourceList.size()==0)
            return;
        if (str.equals(context))
            return;
        
        String old=context;
        
        for (int i=0;i<dl.getResource().size();i++) {
            ResourceType rt=(ResourceType) dl.getResource().get(i);
            java.util.List anchors=rt.getAnchor();
            for (int j=0;j<anchors.size();j++) {
                AnchorType at=(AnchorType) anchors.get(j);
                if (str.equals(at.getContextName())) {
                    currentAnchorList=anchors;
                    switchTo(i, j);
                    return;
                }
            }
        }
        //System.out.println("Contexto desconocido");
    }
    
    public void gotoFirstContext() {
        switchTo(0,0);
    }
    
    public void gotoLastContext() {
        switchTo(resourceList.size()-1, currentAnchorList.size()-1);
    }
    
    public void gotoNextContext() {
        // de momento, queda vac�o
    }
    
    public void gotoPreviousContext() {
        // de momento, queda vacio
    }
    
    public boolean isCyclic() {
        return cyclic;
    }
    
    public void setCyclic(boolean b) {
        boolean old=cyclic;
        if (old!=b) {
            cyclic=b;
            firePropertyChange("cyclic",new Boolean(old), new Boolean(b));
        }
    }
    
    public ContextManager getContextManager() {
        return contextManager;
    }
    
    public void setContextManager(ContextManager c) {
        ContextManager old=contextManager;
        
        // si se indica el mismo ContextManager que estaba configurado, no se hace nada
        if (c==old)
            return;
        
        // si hab�a alguno configurado, nos damos de baja
        if (old!=null) {
            old.remove(this);
            removeContextListener(old);
        }
        
        // nos registramos en el nuevo ContextManager, pero s�lo si no es null
        if (c!=null) {
            addContextListener(c);
            c.add(this);
        }
        
        contextManager=c;
        firePropertyChange("contextManager",old,c);
    }
    
    public void addContextListener(ContextListener c) {
        if (!(listeners.contains(c)))
            listeners.add(c);
    }
    
    public void removeContextListener(ContextListener c) {
        if (listeners.contains(c))
            listeners.remove(c);
    }
    
    private void switchTo(int resource, int anchor) {
        try {
            ResourceType rt=(ResourceType) dl.getResource().get(resource);
            AnchorType at=(AnchorType) currentAnchorList.get(anchor);
            
            if ((currentURL!=null) && (currentURL.toString().startsWith(rt.getLocator())))
                scrollToReference(at.getAnchor());
            else {
                URL url=new URL(rt.getLocator()+"#"+at.getAnchor());
                currentURL=url;
                setPage(url);
            }
            context=at.getContextName();
            fireEvent();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private void fireEvent() {
        for (int i=0;i<listeners.size();i++) {
            ContextListener c=(ContextListener) listeners.get(i);
            c.contextChanged(new ContextEvent(this, context));
        }
    }
    
    public String getSubcontext() {
        return subcontext;
    }
}