import java.awt.*;
import java.io.*;
import java.net.InetAddress;
import javax.media.*;
import javax.media.protocol.*;
import javax.media.protocol.DataSource;
import javax.media.format.*;
import javax.media.control.TrackControl;
import javax.media.control.QualityControl;
import javax.media.rtp.*;
import javax.media.rtp.rtcp.*;
import com.sun.media.rtp.*;

public class Emisor {

    private MediaLocator locator;
    private String ipAddress;
    private int portBase;

    private Processor processor = null;
    private RTPManager rtpMgrs[];
    private DataSource dataOutput = null;
    private Format format;
    
    public Emisor(MediaLocator locator, String ipAddress, String pb, Format format) {
	
	this.locator = locator;
	this.ipAddress = ipAddress;
	Integer integer = Integer.valueOf(pb);
	if (integer != null)
	    this.portBase = integer.intValue();
        this.format=format;
    }

    public synchronized String start() {
	String result;
	result = createProcessor();
	if (result != null)
	    return result;
	result = createTransmitter();
	if (result != null) {
	    processor.close();
	    processor = null;
	    return result;
	}
        processor.start();
	return null;
    }

    public void stop() {
	synchronized (this) {
	    if (processor != null) {
		processor.stop();
		processor.close();
		processor = null;
	        rtpMgrs[0].removeTargets( "Session ended.");
		rtpMgrs[0].dispose();
	    }
	}
    }

    private String createProcessor() {
	if (locator == null)
	    return "Locator es nulo";

	DataSource ds;
	DataSource clone;
	try {
	    ds = javax.media.Manager.createDataSource(locator);
	} catch (Exception e) {
	    return "No se puede crear el DataSource";
	}
	try {
	    processor = javax.media.Manager.createProcessor(ds);
	} catch (NoProcessorException npe) {
	    return "No se puede crear el processor";
	} catch (IOException ioe) {
	    return "Excepcion de entrada/salida creando el processor";
	} 

	boolean result = waitForState(processor, Processor.Configured);
	if (result == false)
	    return "No se puede configurar el processor";
	TrackControl [] tracks = processor.getTrackControls();
	if (tracks == null || tracks.length < 1)
	    return "Couldn't find tracks in processor";
	ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP);
	processor.setContentDescriptor(cd);
	for (int i = 0; i < tracks.length; i++) {
	    Format format = tracks[i].getFormat();
	    if (tracks[i].isEnabled()) {
		    tracks[i].setFormat(this.format);
		    System.err.println("Track " + i + " is set to transmit as:");
	    } else
		tracks[i].setEnabled(false);
	}
	result = waitForState(processor, Controller.Realized);
	if (result == false)
	    return "No entra el processor en el estado Realizado";
	dataOutput = processor.getDataOutput();
	return null;
    }

    private String createTransmitter() {

	PushBufferDataSource pbds = (PushBufferDataSource)dataOutput;
	PushBufferStream pbss[] = pbds.getStreams();
	rtpMgrs = new RTPManager[1];
	SessionAddress localAddr, destAddr;
	InetAddress ipAddr;
	SendStream sendStream;
	int port;
	SourceDescription srcDesList[];

	for (int i = 0; i < pbss.length; i++) {
	    try {
		rtpMgrs[i] = RTPManager.newInstance();	    
		port = portBase + 2*i;
		ipAddr = InetAddress.getByName(ipAddress);
		localAddr = new SessionAddress( InetAddress.getLocalHost(),port);
		destAddr = new SessionAddress( ipAddr, port);
		rtpMgrs[i].initialize( localAddr);
		rtpMgrs[i].addTarget( destAddr);
		System.err.println( "Creada sesion RTP: " + ipAddress + " " + port);
		sendStream = rtpMgrs[i].createSendStream(dataOutput, i);		
		sendStream.start();
	    } catch (Exception  e) {
		return e.getMessage();
	    }
	}

	return null;
    }

    private Integer stateLock = new Integer(0);
    private boolean failed = false;
    
    Integer getStateLock() {
	return stateLock;
    }

    void setFailed() {
	failed = true;
    }
    
    private synchronized boolean waitForState(Processor p, int state) {
	p.addControllerListener(new StateListener());
	failed = false;

	if (state == Processor.Configured) {
	    p.configure();
	} else if (state == Processor.Realized) {
	    p.realize();
	}
	while (p.getState() < state && !failed) {
	    synchronized (getStateLock()) {
		try {
		    getStateLock().wait();
		} catch (InterruptedException ie) {
		    return false;
		}
	    }
	}

	if (failed)
	    return false;
	else
	    return true;
    }

    class StateListener implements ControllerListener {

	public void controllerUpdate(ControllerEvent ce) {
	    if (ce instanceof ControllerClosedEvent)
		setFailed();
	    if (ce instanceof ControllerEvent) {
		synchronized (getStateLock()) {
		    getStateLock().notifyAll();
		}
	    }
	}
    }
}

