import java.awt.Frame;
import java.awt.Panel;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.BorderLayout;
import javax.media.ControllerListener;
import javax.media.Player;
import javax.media.ControllerEvent;
import javax.media.RealizeCompleteEvent;
import javax.media.ControllerErrorEvent;
import javax.media.protocol.DataSource;
import javax.media.rtp.ReceiveStreamListener;
import javax.media.rtp.SessionListener;
import javax.media.rtp.RemoteListener;
import javax.media.rtp.ReceiveStream;
import javax.media.rtp.RTPManager;
import javax.media.rtp.SessionAddress;
import javax.media.rtp.Participant;
import javax.media.rtp.RTPControl;
import javax.media.rtp.rtcp.ReceiverReport;
import javax.media.rtp.rtcp.SenderReport;
import javax.media.rtp.event.RemoteEvent;
import javax.media.rtp.event.SessionEvent;
import javax.media.rtp.event.ReceiveStreamEvent;
import javax.media.rtp.event.ReceiverReportEvent;
import javax.media.rtp.event.SenderReportEvent;
import javax.media.rtp.event.NewParticipantEvent;
import javax.media.rtp.event.RemotePayloadChangeEvent;
import javax.media.rtp.event.NewReceiveStreamEvent;
import javax.media.rtp.event.StreamMappedEvent;
import javax.media.rtp.event.ByeEvent;
import com.sun.media.rtp.RTPSessionMgr;

//clase que implementa un receptor
public class Receptor implements ReceiveStreamListener, SessionListener,
						ControllerListener, RemoteListener
{
	//constante de la clase
	protected static final int BUFFERLEN = 100;

	//variables miembro de la clase
	protected Target destino;
    protected RTPManager rtpManager;
	private PlayerWindow playerWindow;
	private Object dataSync;
    private boolean dataReceived;

	//constructor de la clase
	public Receptor(Target destino)
	{
		//inicializamos las variables
		System.out.println(Misc.ind() + "Inicializando variables");
		this.destino = destino;
		playerWindow = null;
		dataSync = new Object();
		dataReceived = false;
	}

	//método que cierra el receptor
	protected void close()
	{
		System.out.println(Misc.ind() + "Cerrando ventana de reproduccion");
		//cerramos la ventana de reproduccion
		try
		{
			playerWindow.close();
		}
		catch (Exception e) {}
		//cerramos todos el destino del manejador RTP
		System.out.println(Misc.ind() + "Cerrando sesion de recepcion");
		rtpManager.removeTargets("Cerrando sesion de recepcion");
		//eliminamos el manejador RTP
		rtpManager.dispose();
		rtpManager = null;
	}

	//método que se ejecuta cuando se actualiza el controlador
	public synchronized void controllerUpdate(ControllerEvent ce)
	{
		//creamos un reproductor
		Player p = (Player)ce.getSourceController();

		if (p == null)
			return;

		//si el evento está completamente generado
		if (ce instanceof RealizeCompleteEvent)
		{
			//inicializamos la ventana de reproduccion
			PlayerWindow pw = playerWindow;
			if (pw == null)
			{
				System.err.println(Misc.ind() + "Error interno");
				System.exit(-1);
			}
			pw.initialize();
			//visualizamos la ventana de reproduccion
			pw.setVisible(true);
			//iniciamos el reproductor
			p.start();
		}
		//si hay un evento de error del controlador
		if (ce instanceof ControllerErrorEvent)
		{
			//eliminamos el ControllerListener
			p.removeControllerListener(this);
			//cerramos la ventana de reproduccion
			PlayerWindow pw = playerWindow;
			if (pw != null)
			{
				pw.close();
				playerWindow = null;
			}

			System.err.println(Misc.ind() + "Error Interno de recepcion: " + ce);
		}
	}

	//metodo ejecutado al actualizarse un evento remoto
	public void update(RemoteEvent event)
	{
		//si se trata de un informe de entrega
		if (event instanceof ReceiverReportEvent)
		{
			//cogemos el informe
			ReceiverReport rr = ((ReceiverReportEvent) event).getReport();
			//creamos un buffer
			StringBuffer sb = new StringBuffer();
			//le vamos dando formato al buffer
			sb.append("Recepcion");
			//si hay informe
			if(rr != null)
			{
				//obtenemos el participante
				Participant participant = rr.getParticipant();
				//añadimos información al buffer
				if( participant != null)
				{
					sb.append(" desde " + participant.getCNAME());
					sb.append(" ssrc = " + rr.getSSRC());
				}
				else
				{
					sb.append(" ssrc = " + rr.getSSRC());
				}
			}
		}
		//si se trata de un informe de envio
		else if(event instanceof SenderReportEvent)
		{
			//cogemos el informe
			SenderReport sr = ((SenderReportEvent) event).getReport();
			//creamos un buffer
			StringBuffer sb = new StringBuffer();
			//le vamos dando formato al buffer
			sb.append("Emision");
			//si hay informe
			if( sr != null)
			{
				//obtenemos el participante
				Participant participant= sr.getParticipant();
				//añadimos información al buffer
				if( participant != null)
				{
					sb.append( " desde " + participant.getCNAME());
					sb.append( " ssrc = " + sr.getSSRC());
				}
				else
				{
					sb.append( " ssrc = " + sr.getSSRC());
				}
			}
		}
		//si no es nada de lo anterior
		else
		{
			System.out.println("Evento remoto: " + event);
		}
	}

	//metodo ejecutado al actualizarse un evento de sesion
	public synchronized void update(SessionEvent evt)
	{
		//si hay un participante nuevo
		if (evt instanceof NewParticipantEvent)
		{
			//creamos el participante nuevo
			Participant p = ((NewParticipantEvent)evt).getParticipant();
			System.err.println(Misc.ind() + "  - Un nuevo participante se ha unido: " + p.getCNAME());
		}
	}

	//metodo ejecutado al recibir una secuencia
	public synchronized void update(ReceiveStreamEvent evt)
	{
		//obtenemos el origen de la cadena
		RTPManager rtpManager = (RTPManager)evt.getSource();
		//obtenemos el participante
		Participant participant = evt.getParticipant();
		//obtenemos la secuencia
		ReceiveStream stream = evt.getReceiveStream();

		//si la secuencia es un "Payload" nos salimos
		if (evt instanceof RemotePayloadChangeEvent)
		{
			System.err.println(Misc.ind() + "  - Recivido un evento RTP no soportado.");
			System.exit(0);
		}
		//si es una nueva cadena recibida
		else if (evt instanceof NewReceiveStreamEvent)
		{
			try
			{
				//obtenemos la secuencia
				stream = ((NewReceiveStreamEvent)evt).getReceiveStream();
				//obtenemos la fuente
				DataSource ds = stream.getDataSource();
				//segun sea una secuencia de control o no, escribimos en el log una cosa u otra
				RTPControl ctl = (RTPControl)ds.getControl("javax.media.rtp.RTPControl");
				if (ctl != null)
				{
					System.err.println(Misc.ind() + "  - Recibida nueva secuencia RTP: " + ctl.getFormat());
				}
				else
				{
					System.err.println(Misc.ind() + "  - Recibida nueva secuencia RTP");
				}

				//si hay participante o no, escribimos una cosa u otra
				if (participant == null)
				{
					System.err.println(Misc.ind() + "  - El emisor no pudo ser identificado");
				}
				else
				{
					System.err.println(Misc.ind() + "  - La secuencia proviene de: " + participant.getCNAME());
				}

				//creamos un reproductor a partir de la fuente de datos
				Player p = javax.media.Manager.createPlayer(ds);
				if (p == null)
				{
					return;
				}
				//inicializamos el reproductor
				p.addControllerListener(this);
				p.realize();
				//generamos una ventana de reproducción
				PlayerWindow pw = new PlayerWindow(p, stream);
				playerWindow = pw;

				//Notificamos que se ha recibido una nueva secuencia
				synchronized(dataSync)
				{
					dataReceived = true;
					dataSync.notifyAll();
				}
			}
			//capturamos la excepcion
			catch (Exception e)
			{
				System.err.println(Misc.ind() + "Excepcion NewReceiveStreamEvent " + e.getMessage());
				return;
			}
		}
		//si es parte de una cadena anterior
		else if (evt instanceof StreamMappedEvent)
		{
			//si hay una secuencia y con una fuente de datos
			if (stream != null && stream.getDataSource() != null)
			{
				//usamos la fuente de datos fuente de datos
				DataSource ds = stream.getDataSource();
				//Obtenemos el formato
				RTPControl ctl = (RTPControl)ds.getControl("javax.media.rtp.RTPControl");
				System.err.println(Misc.ind() + "  - La secuencia anteriormente no identificada ");
				//escribimos el formato
				if (ctl != null)
				{
					System.err.println(Misc.ind() + "  - " + ctl.getFormat());
				}
				System.err.println(Misc.ind() + "  - ha sido identificada como de: " + participant.getCNAME());
				//creamos un manejador de sesion RTP
				RTPSessionMgr rtpMgr = (RTPSessionMgr)evt.getSessionManager();
				//obtenemos una sesion de direccion
				SessionAddress addr = rtpMgr.getRemoteSessionAddress();
			}
		}
		//si es una despedida
		else if (evt instanceof ByeEvent)
		{
			//crea,ps im buffer
			StringBuffer sb = new StringBuffer();
			//aplicamos un formato
			sb.append("ADIOS");
			//generamos una razón
			String reason = ((ByeEvent)evt).getReason();
			//añadimos al buffer información adicional
			sb.append(" desde " + participant.getCNAME());
			sb.append(" ssrc = " + stream.getSSRC());
			sb.append(" razon = '" + reason + "'");

			if (playerWindow != null)
			{
				playerWindow.close();
				playerWindow = null;
			}
		}
	}

	//subclase que implementa una ventana de reproduccion
	class PlayerWindow extends Frame
	{
		//variables miembro
		private Player player;
		private ReceiveStream stream;
		private String senderPort;
		private String senderAddress;

		//constructor de la clase
		public PlayerWindow(Player p, ReceiveStream strm)
		{
			player = p;
			stream = strm;
			//fijamos el título
			super.setTitle( "Ventana de reproducción");
		}

		//metodo que fija el titulo
		public void setTitle(String address, int port)
		{
			senderAddress= address;
			senderPort= port + "";

			String title = senderAddress + ":" + senderPort;

			super.setTitle(title);
		}

		//metodo que inicializa la ventana de reproduccion
		public void initialize()
		{
			add(new PlayerPanel(player));
		}

		//metodo que cierra la ventana de reproduccion
		public void close()
		{
			player.close();
			setVisible(false);
			dispose();
		}

		//metodo que añade un notificador
		public void addNotify()
		{
			super.addNotify();
			pack();
		}
	}

	//Subclase que implementa un panel de reproduccion
	class PlayerPanel extends Panel
	{
		//variables miembro
		private Component vc, cc;

		//constructor de la clase
		public PlayerPanel(Player p)
		{
			setLayout(new BorderLayout());
			if ((vc = p.getVisualComponent()) != null)
				add("Center", vc);
			if ((cc = p.getControlPanelComponent()) != null)
				add("South", cc);
		}

		//metodo que devuelve las dimensiones del panel de reproduccion
		public Dimension getPreferredSize()
		{
			int w = 0, h = 0;
			if (vc != null)
			{
				Dimension size = vc.getPreferredSize();
				w = size.width;
				h = size.height;
			}
			if (cc != null)
			{
				Dimension size = cc.getPreferredSize();
				if (w == 0)
				w = size.width;
				h += size.height;
			}
			if (w < 160)
				w = 160;
			return new Dimension(w, h);
		}
	}
}
