
package com.uvigo.mhp.mediaPlayer;

import com.uvigo.mhp.*;
import com.uvigo.mhp.xml.mediaPlayer.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.media.*;
import javax.xml.bind.*;
import org.havi.ui.*;
import com.uvigo.tMaestro.SCOCreator.tool.MainFrame;

// En caso de que se utilicen ficheros de audio, deber�a especificarse una imagen
// para la representaci�n visual (posible mejora: un plugin, como en el WinAmp)
// Debe poder asociarse una barra para controlar el desplazamiento, el avance... pero como un
// objeto aparte.
// No se necesitan controles de volumen (para algo est� el mando a distancia)
// Permitir zoom mayor que 1!

public class MediaPlayer extends HContainer implements Serializable, 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 boolean fixedAspectRatio;
    private boolean looping;
    private boolean started;
    private boolean startOnContextChange;
    private MediaPlayerThread thread; // sincroniza los eventos de contexto
    private MediaLocator mrl; // contendr� el MediaLocator del reproductor actual
    public Player player; // se pueden hacer cosas con �l desde fuera
    private ControllerListener selfListener;
    protected Component visualComponent;
    private int preferredHeight;
    private int preferredWidth;
    private int state; // se utiliza en coordinaci�n con el MediaPlayerControllerAdapter
    protected transient GainControl gainControl;
    protected transient float curVolumeValue;
    protected transient float curZoomValue;
    protected transient Time mediaTime;
    
    // variables utilizadas en la gesti�n de contextos
    private int timestampIndex; // indica, dentro del reproductor actual, el indice del siguiente sello temporal
    private int current;
    private ContextManager contextManager;
    private Vector listeners;
    private Vector contextsInCurrentPage; // contendr� objetos de tipo TextPanelContext
    private String context;
    private String subcontext; // para comentarios asociados a los sellos temporales
    private String contextToBeSet;
    private boolean cyclic;
    
    // Variables relativas a la configuraci�n seg�n un fichero XML
    private JAXBContext jc;
    private MediaList ml;
    private java.util.List resourceList;
    private java.util.List currentTimestampList;
    private String configurationFile;
    
    //Variables relativas al instante de presentacion del elemento
    private int delayedTimeMode;
    private int delayedTime;
    
    public MediaPlayer() {
        try {
            jc=JAXBContext.newInstance("com.uvigo.mhp.xml.mediaPlayer");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        delayedTimeMode = TIME_ALWAYS;
        delayedTime=0;
        configurationFile="";
        fixedAspectRatio = true;
        looping = true;
        player = null;
        started = false;
        curVolumeValue = 0.6F;
        curZoomValue = 1.0F;
        preferredWidth=50;
        preferredHeight=50;
        setLayout(null);
        thread=null;
        timestampIndex=0;
        
        current=-1;
        context="";
        subcontext="";
        contextManager=null;
        listeners=new Vector();
        cyclic=false;
        startOnContextChange=false;
    }
    
    private synchronized void showPlayer() {
        gainControl = player.getGainControl();
        if(gainControl != null)
            gainControl.setLevel(curVolumeValue);
        
        removeAll();
        
        visualComponent = player.getVisualComponent();
        if(visualComponent != null) {
            visualComponent.setVisible(false);
            setVisible(false);
            
            add(visualComponent);
            addComponentListener(new VisualComponentAdapter(this));
            
            setPreferredSize(visualComponent.getPreferredSize());
            zoomTo(curZoomValue);
            
            visualComponent.setVisible(true);
            setVisible(true);
        }
    }
    
    private void zoomTo(float z) {
        int pwidth = getSize().width;
        int pheight = getSize().height;
        
        int width;
        int height;
        
        if (!fixedAspectRatio) {
            width=(int) (z*pwidth);
            height=(int) (z*pheight);
        } else {
            float a=(float) preferredWidth / (float) pwidth;
            float b=(float) preferredHeight / (float) pheight;
            if (a>b) {
                width=(int) (z*pwidth);
                height=(int) ((float) width/getAspectRatio());
            } else {
                height=(int) (z*pheight);
                width=(int) ((float) height*getAspectRatio());
            }
        }
        
        if (visualComponent!=null) {
            visualComponent.setSize(width, height);
            center();
        }
    }
    
    private void center() {
        int pwidth = getSize().width;
        int pheight = getSize().height;
        int width = visualComponent.getSize().width;
        int height = visualComponent.getSize().height;
        
        visualComponent.setLocation(pwidth / 2 - width / 2, pheight / 2 - height / 2);
        visualComponent.setSize(width, height);
        validate();
    }
    
    public float getAspectRatio() {
        return (float) preferredWidth / (float) preferredHeight;
    }
    
    public void setBounds(int x, int y, int w, int h) {
        super.setBounds(x,y,w,h);
        zoomTo(curZoomValue);
    }
    
    public Dimension getPreferredSize() {
        return new Dimension(preferredWidth, preferredHeight);
    }
    
    public void setPreferredSize(Dimension d) {
        preferredWidth=d.width;
        preferredHeight=d.height;
    }
    
    public Dimension getMinimumSize() {
        return new Dimension(preferredWidth, preferredHeight);
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // MANIPULACI�N DE PROPIEDADES
    ///////////////////////////////////////////////////////////////////////////////
    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))) { //Necesario eliminarlo, ya que puede cambiar el contenido del fichero y no su ruta
            try {
                if ((s==null) || (s=="")) return;
                
               //M: Direccionamiento relativo a "user.dir"
                  String oBase= new String (MainFrame.oMainFrame.sDir+ "/");

                 URL url=new URL("file:"+oBase+s);
                 System.out.println("***********url "+url.getPath());
                URLConnection con=url.openConnection();
                
                Unmarshaller u=jc.createUnmarshaller();
                u.setValidating(true);
                ml=(MediaList) u.unmarshal(con.getInputStream());
                
                configurationFile=s;
                firePropertyChange("configurationFile",old,s);
                
                resourceList=ml.getResource();
                if (resourceList.size()==0) return;
                
                ResourceType rt=(ResourceType) ml.getResource().get(0);
                currentTimestampList=rt.getTimestamp();
                if (currentTimestampList.size()>0) {
                    TimestampType ts=(TimestampType) currentTimestampList.get(0);
                    setContext(ts.getContextName());
                }
            } catch (Exception ex) {
                // El fichero de configuraci�n no se encuentra, no existe...
                ex.printStackTrace();
            }
        //}
    }
    
    public boolean getPlaybackLoop() {
        return looping;
    }
    
    public void setPlaybackLoop(boolean val) {
        boolean old = looping;
        looping = val;
        firePropertyChange("playbackLoop", new Boolean(old), new Boolean(looping));
    }
    
    public boolean isPlayBackLoop() {
        return looping;
    }
    
    public float getZoom() {
        return curZoomValue;
    }
    
    public void setZoom(float f) {
        if ((f<0F) || (f>1F))
            return;
        
        float old=curZoomValue;
        curZoomValue=f;
        zoomTo(curZoomValue);
        firePropertyChange("zoom", new Float(old), new Float(curZoomValue));
    }
    
    public float getVolume() {
        return curVolumeValue;
    }
    
    public void setVolume(float f) {
        if ((f<0F) || (f>1F))
            return;
        
        float old=curVolumeValue;
        curVolumeValue = f;
        if(gainControl != null)
            gainControl.setLevel(curVolumeValue);
        firePropertyChange("volume", new Float(old), new Float(curVolumeValue));
    }
    
    public boolean isFixedAspectRatio() {
        return fixedAspectRatio;
    }
    
    public void setFixedAspectRatio(boolean isFixed) {
        boolean old = fixedAspectRatio;
        fixedAspectRatio = isFixed;
        firePropertyChange("fixedAspectRatio", new Boolean(old), new Boolean(fixedAspectRatio));
    }
    
    public boolean getStartOnContextChange() {
        return startOnContextChange;
    }
    
    public void setStartOnContextChange(boolean b) {
        boolean old=startOnContextChange;
        startOnContextChange=b;
        firePropertyChange("startOnContextChange", new Boolean(old), new Boolean(b));
    }
    
    public float getRate() {
        if(player != null)
            return player.getRate();
        else
            return 0.0F;
    }
    
    public float setRate(float factor) {
        if(player != null)
            return player.setRate(factor);
        return 0.0F;
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////
    
    public Player getPlayer() {
        return player;
    }
    
    public Time getDuration() {
        if(player != null)
            return player.getDuration();
        else
            return Duration.DURATION_UNKNOWN;
    }
    
    public String getMediaLocation() {
        if(mrl != null)
            return mrl.toString();
        return null;
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // CONTROL DE REPRODUCCI�N Y DEL ESTADO DEL PLAYER
    ///////////////////////////////////////////////////////////////////////////////
    
    public Time getMediaTime() {
        if(player != null)
            return player.getMediaTime();
        else
            return Controller.LATENCY_UNKNOWN;
    }
    
    public void setMediaTime(Time now) {
        if(player != null)
            player.setMediaTime(now);
    }
    
    public void start() {
        if (started) return;
        if(player != null) {
            player.setMediaTime(player.getMediaTime()); // (soluciona errores con el sonido)
            if (currentTimestampList!=null) {
                if (currentTimestampList.size()>timestampIndex) {
                    //System.out.println("Se va a establecer una nueva temporizaci�n");
                    int millis=getMillisTo(((TimestampType) currentTimestampList.get(timestampIndex)).getTimestamp());
                    thread=new MediaPlayerThread(this,millis);
                }
            }
            
            player.start();
            started=true;
        } else {
            // si no hay ning�n reproductor seleccionado, se elige el primero.
            if ((resourceList==null) || (resourceList.size()==0)) {
                System.out.println("There are no configured locators");
                return;
            }
            
            ResourceType rt=(ResourceType) resourceList.get(0);
            currentTimestampList=rt.getTimestamp();
            
          //M: Direccionamiento relativo a "user.dir"
                String sBase= new String (MainFrame.oMainFrame.sDir+ "/");
            //Parte de configurationFile que corresponde a ruta (que no es fichero)
            /*String[] split = configurationFile.split("/");
            String sRuta=new String();
            int z=0;
            for (z=0;z<split.length-1;z++) {
                sRuta = sRuta.concat(split[z]+"/");
            }*/
            String sRuta=configurationFile.substring(0, configurationFile.lastIndexOf("/"));
            java.net.URL oURL=null;
            try {oURL=new URL("file:"+sBase+sRuta+rt.getLocator());} catch(Exception oE){System.out.println(oE);}
            
            mrl= new MediaLocator(oURL);
            
            try {
                player = Manager.createRealizedPlayer(mrl);
                showPlayer();
                timestampIndex=0;
                player.addControllerListener(selfListener = new MediaPlayerControllerAdapter(this));
                
                start();
            } catch(Exception e) {
                player = null;
                mrl = null;
                System.out.println("Unable to create player");
                return;
            }
        }
    }
    
    public void stop() {
        if(player != null) {
            player.stop();
            started=false;
            if (thread!=null) {
                thread.abort();
                thread=null;
            }
            player.setMediaTime(new Time(0));
        }
    }
    
    public void pause() {
        if(player != null) {
            player.stop();
            started=false;
            if (thread!=null) {
                thread.abort();
                thread=null;
            }
            player.setMediaTime(player.getMediaTime()); // para provocar la ejecuci�n de whenTimeIsSet()
        }
    }
    
    public void close() {
        if(player != null) {
            removeAll();
            player.close();
            player = null;
        }
    }
    
    protected synchronized void waitForState(int s) {
        while(state != s)
            try {
                wait(1000L);
            } catch(Exception e) {
            }
    }
    
    protected void setState(int s) {
        state=s;
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // GESTION DE SELLOS TEMPORALES
    ///////////////////////////////////////////////////////////////////////////////
    
    protected synchronized void ring() {
        // vence la temporizaci�n establecida; si quedan m�s sellos temporales,
        // se fija el siguiente vencimiento para entonces.
        
        TimestampType ts=(TimestampType) currentTimestampList.get(timestampIndex);
        String contextName=ts.getContextName();
        if ((contextName!=null) && (!(contextName.equals("")))) {
            // hay que ver si el instante actual s�lo termina el contexto en curso, para
            // lo cual contextName debe empezar con una "/", o si bien adem�s empieza un
            // contexto nuevo.
            if (contextName.startsWith("/"))
                context="";
            else
                context=contextName;
            fireEvent();
        }
        
        subcontext=ts.getComment();
        fireSubcontextEvent();
        
        timestampIndex++;
        if (currentTimestampList.size()>timestampIndex) {
            int millis=getMillisTo(((TimestampType) currentTimestampList.get(timestampIndex)).getTimestamp());
            thread=new MediaPlayerThread(this,millis);
        }
    }
    
    protected synchronized void whenTimeIsSet() {
        //System.out.println("TIME SET TO "+player.getMediaTime().getNanoseconds());
        if (visualComponent!=null)
            visualComponent.repaint();
        
        if (thread!=null) {
            thread.abort(); // al cambiar el instante de reproducci�n, la temporizaci�n que pudiera estar lanzada nunca vale
            thread=null;
        }
        
        // hay que buscar el sello temporal m�s pr�ximo al tiempo fijado
        timestampIndex=currentTimestampList.size()+1; // si no hay ninguno por delante, esto queda as�
        for (int i=0;i<currentTimestampList.size();i++) {
            TimestampType ts=(TimestampType) currentTimestampList.get(i);
            double d=ts.getTimestamp();
            if (getMillisTo(d)>=0) {
                timestampIndex=i;
                if (started)
                    thread=new MediaPlayerThread(this, getMillisTo(d));
                break;
            }
        }
    }
    
    // devuelve los milisegundos que faltan hasta el instante indicado
    // desde el instante de reproducci�n actual.
    private int getMillisTo(double d) {
        Time now=player.getMediaTime();
        long nanos=(new Time(d).getNanoseconds())-now.getNanoseconds();
        return (int) (nanos/1000000L);
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // GESTION DE CONTEXTOS
    ///////////////////////////////////////////////////////////////////////////////
    
    public String getContext() {
        return context;
    }
    
    public void setContext(String str) {
        if ((str==null) || (str.equals("")))
            return;
        if (str.equals(context))
            return;
        
        if ((resourceList==null)||(resourceList.size()==0)) return;
        
        // Se busca en la lista de sellos temporales el contexto indicado
        for (int i=0;i<ml.getResource().size();i++) {
            ResourceType rt=(ResourceType) ml.getResource().get(i);
            java.util.List stamps=rt.getTimestamp();
            for (int j=0;j<stamps.size();j++) {
                TimestampType ts=(TimestampType) stamps.get(j);
                if (ts.getContextName().equals(str)) {
                    // Se ha localizado el contexto. Hay que comprobar si forma parte
                    // del recurso que se est� mostrando, o si por el contrario hay
                    // que crear un nuevo Player.
                    
                    context=str;
                    
                   //M: Direccionamiento relativo a "user.dir"
                        String sBase= new String (MainFrame.oMainFrame.sDir+ "/");
                    
                    //Parte de configurationFile que corresponde a ruta (que no es fichero)
                    /*String[] split = configurationFile.split("/");
                    String sRuta=new String();
                    int z=0;
                    for (z=0;z<split.length-1;z++) {
                        sRuta = sRuta.concat(split[z]+"/");
                    }*/
                    String sRuta=configurationFile.substring(0, configurationFile.lastIndexOf("/"))+"/";
                    
                    //System.out.println(sBase+" "+sRuta+" "+rt.getLocator());
                    java.net.URL oURL=null;
                    try {oURL=new URL("file:"+sBase+sRuta+rt.getLocator());} catch(Exception oE){System.out.println(oE);}
                    
                    MediaLocator temp=new MediaLocator(oURL);
                    if ((mrl!=null) && ((temp.toString()).equals(mrl.toString()))) {
                        //System.out.println("El contexto nuevo es del medio actual");
                        player.setMediaTime(new Time(ts.getTimestamp()));
                        if (startOnContextChange)
                            start();
                        return;
                    } else {
                        //System.out.println("El contexto nuevo es de otro medio");
                        
                        if(mrl != null) // si hay alg�n reproductor en marcha, se quita
                        {
                            removeAll();
                            if(player != null) {
                                player.stop();
                                player.close();
                            }
                        }
                        
                        try {
                           
                            oURL=new URL("file:"+sBase+sRuta+rt.getLocator());
                            // System.out.println("oUrl "+oURL.getPath());
                            mrl= new MediaLocator(oURL);
                            player = Manager.createRealizedPlayer(mrl);
                            showPlayer();
                            currentTimestampList=stamps;
                            timestampIndex=j;
                            player.addControllerListener(selfListener = new MediaPlayerControllerAdapter(this));
                            player.setMediaTime(new Time(ts.getTimestamp()));
                            
                            if (startOnContextChange)
                                start();
                        } catch(Exception e) {
                            player = null;
                            mrl = null;
                            System.out.println(e+" - Unable to create player");
                        }
                    }
                    return;
                }
            }
        }
        //System.out.println("Contexto desconocido");
    }
    
    public void gotoFirstContext() {
        // de momento, queda vac�o
    }
    
    public void gotoLastContext() {
        // de momento, queda vac�o
    }
    
    public void gotoNextContext() {
        // de momento, queda vac�o
    }
    
    public void gotoPreviousContext() {
        // de momento, queda vac�o
    }
    
    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 fireEvent() {
        for (int i=0;i<listeners.size();i++) {
            ContextListener c=(ContextListener) listeners.get(i);
            c.contextChanged(new ContextEvent(this, context));
        }
    }
    
    private void fireSubcontextEvent() {
        for (int i=0;i<listeners.size();i++) {
            ContextListener c=(ContextListener) listeners.get(i);
            c.subcontextEntered(new ContextEvent(this, subcontext));
        }
    }
    
    public String getSubcontext() {
        return subcontext;
    }
}