package client;

import java.awt.*;
import java.awt.event.*;

import java.io.*;
import java.net.InetAddress;
import java.util.Vector;

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.*;
import javax.swing.*;

public class ATransmit extends Thread
{
	// 新增
	public int setaudioFormat; // 0~11
	public float jpegquality; // 0.0f~100.0f
	public int jpeg_or_h263;
	
	// Input MediaLocator
	// Can be a file or http or capture source
	private MediaLocator locator;

	private String ipAddress;

	private int portBase;

	private Processor processor = null;
	private Player player = null;

	private RTPManager rtpMgrs[];

	private DataSource source = null;
	private DataSource dataOutput = null;
	
	private boolean isFile = false;
	
	private PlayerWindow window;
	private DataSink filewriter;

	public ATransmit( MediaLocator locator, String ipAddress, String pb,
			Format format, boolean isfile )
	{

		this.locator = locator;
		this.ipAddress = ipAddress;
		isFile = isfile;
		Integer integer = Integer.valueOf(pb);
		if (integer != null)
			this.portBase = integer.intValue();
	}

	/**
	 * Starts the transmission. Returns null if transmission started ok.
	 * Otherwise it returns a string with the reason why the setup failed.
	 */
//	public synchronized String start() {
	public  void run() {
		String result;

		// Create a processor for the specified media locator
		// and program it to output JPEG/RTP
		result = createProcessor();
//		if (result != null)
//			return result;
		if (result != null) {
			System.err.println("Error : " + result);
//			System.exit(0);
			JOptionPane.showMessageDialog( null,
					"Error: " + result + "\n請稍後再傳" );
			return;
		}

		// Create an RTP session to transmit the output of the
		// processor to the specified IP address and port no.
		result = createTransmitter();
		if (result != null) {
			processor.close();
			processor = null;
			System.err.println("Error : " + result);
//			System.exit(0);
			JOptionPane.showMessageDialog( null,
					"Error: " + result + "\n請稍後再傳" );
			return;
		}

		// Start the transmission
		processor.start();

//		return null;
	}

	/**
	 * Stops the transmission if already started
	 */
	public void stoptransmit() {
		synchronized (this) {
			if (processor != null) {
				processor.stop();
				processor.close();
				processor = null;
				for (int i = 0; i < rtpMgrs.length; i++) {
					rtpMgrs[i].removeTargets("Session ended.");
					rtpMgrs[i].dispose();
				}
			}
		}
	}

	private String createProcessor() {
		if (locator == null)
			return "Locator is null";

//		DataSource ds;
		DataSource clone;

		try {
			DataSource d = Manager.createDataSource(locator);
			source = Manager.createCloneableDataSource( d );
			clone = ( (SourceCloneable)source ).createClone();
		} catch (Exception e) {
			return "Couldn't create DataSource";
		}
		
		window = new PlayerWindow();
		if( isFile )
		{
			try {
				player = Manager.createRealizedPlayer( clone );				
				window.startPlayer();
			} catch (NoPlayerException e1) {
				e1.printStackTrace();
			} catch (IOException e1) {
				e1.printStackTrace();
			} catch (CannotRealizeException e) {
				e.printStackTrace();
			}		
		}
		

		// Try to create a processor to handle the input media locator
		try {
			processor = Manager.createProcessor( source );
		} catch (NoProcessorException npe) {
			return "Couldn't create processor";
		} catch (IOException ioe) {
			return "IOException creating processor";
		}

		// Wait for it to configure
		boolean result = waitForState(processor, Processor.Configured);
		if (result == false)
			return "Couldn't configure processor";

		// Get the tracks from the processor
		TrackControl[] tracks = processor.getTrackControls();

		// Do we have atleast one track?
		if (tracks == null || tracks.length < 1)
			return "Couldn't find tracks in processor";

		// Set the output content descriptor to RAW_RTP
		// This will limit the supported formats reported from
		// Track.getSupportedFormats to only valid RTP formats.
		
		ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW_RTP);
		processor.setContentDescriptor(cd);

		Format supported[];
		Format chosen;
		boolean atLeastOneTrack = false;

		boolean isvideo=false;
		// Program the tracks.
		
		for (int i = 0; i < tracks.length; i++) {
			Format format = tracks[i].getFormat();
			if (tracks[i].isEnabled()) {
				
				supported = tracks[i].getSupportedFormats();
	
				// We've set the output content to the RAW_RTP.
				// So all the supported formats should work with RTP.
				// We'll just pick the first one.
				
				if (supported.length > 0) {
					
					//audio
					
//					if (supported[0] instanceof VideoFormat) {
//						// For video formats, we should double check the
//						// sizes since not all formats work in all sizes.
//						isvideo=true;
//						chosen = checkForVideoSizes(tracks[i].getFormat(),
//								supported[0]);
					if (supported[0] instanceof AudioFormat){
//						System.out.print("audio");
//						double sampleRate=44100; 
//						int sampleSizeInBits=16;
//						int channel=2;
					//	chosen= (Format)(new AudioFormat(AudioFormat.MPEG_RTP,sampleRate,sampleSizeInBits,channel));						
						chosen=supported[0];
					}
					else if (supported[0] instanceof VideoFormat) {
						// For video formats, we should double check the
						// sizes since not all formats work in all sizes.
						isvideo=true;
						chosen = checkForVideoSizes(tracks[i].getFormat(),
								supported[0]);
					}
					else {
						chosen = supported[0];
					}
					tracks[i].setFormat(chosen);
				//	tracks[i].setFormat((Format)(new AudioFormat(AudioFormat.MPEG_RTP)));
					System.err
							.println("Track " + i + " is set to transmit as:");
					System.err.println("  " + chosen);
//					System.err.println("  " + (Format)(new AudioFormat(AudioFormat.MPEG_RTP)));
					atLeastOneTrack = true;
				} else
					tracks[i].setEnabled(false);
			} else
				tracks[i].setEnabled(false);
		}

		if (!atLeastOneTrack)
			return "Couldn't set any of the tracks to a valid RTP format";

		// Realize the processor. This will internally create a flow
		// graph and attempt to create an output datasource for JPEG/RTP
		// audio frames.
		result = waitForState(processor, Controller.Realized);
		if (result == false)
			return "Couldn't realize processor";

		if( !isFile)
			window.startRealMedia();
		// Get the output data source of the processor
		dataOutput = processor.getDataOutput();

		return null;
	}

	/**
	 * Use the RTPManager API to create sessions for each media track of the
	 * processor.
	 */
	private String createTransmitter() {

		// Cheated. Should have checked the type.
		PushBufferDataSource pbds = (PushBufferDataSource) dataOutput;
		PushBufferStream pbss[] = pbds.getStreams();

		rtpMgrs = new RTPManager[pbss.length];
		SendStream sendStream;
		int port;
		SourceDescription srcDesList[];

		for (int i = 0; i < pbss.length; i++) {
			try {
				rtpMgrs[i] = RTPManager.newInstance();

				port = portBase + 2 * i;

				// Initialize the RTPManager with the RTPSocketAdapter
				rtpMgrs[i].initialize(new RTPSocketAdapter(InetAddress
						.getByName(ipAddress), port));

				System.err.println("Created RTP session: " + ipAddress + " "
						+ port);

				sendStream = rtpMgrs[i].createSendStream(dataOutput, i);
				sendStream.start();
			} catch (Exception e) {
				return e.getMessage();
			}
		}

		return null;
	}

	/**
	 * For JPEG and H263, we know that they only work for particular sizes. So
	 * we'll perform extra checking here to make sure they are of the right
	 * sizes.
	 */
	Format checkForVideoSizes(Format original, Format supported) {

		int width, height;
		Dimension size = ((VideoFormat) original).getSize();
		Format jpegFmt = new Format(VideoFormat.JPEG_RTP);
		Format h263Fmt = new Format(VideoFormat.H263_RTP);
		

		if (supported.matches(jpegFmt)) {
			// For JPEG, make sure width and height are divisible by 8.
			width = (size.width % 8 == 0 ? size.width
					: (int) (size.width / 8) * 8);
			height = (size.height % 8 == 0 ? size.height
					: (int) (size.height / 8) * 8);
		} else if (supported.matches(h263Fmt)) {
			// For H.263, we only support some specific sizes.
			if (size.width < 128) {
				width = 128;
				height = 96;
			} else if (size.width < 176) {
				width = 176;
				height = 144;
			} else {
				width = 352;
				height = 288;
			}
		}  else {
			// We don't know this particular format. We'll just
			// leave it alone then.
			return supported;
		}

		return (new VideoFormat(null, new Dimension(width, height),
				Format.NOT_SPECIFIED, null, Format.NOT_SPECIFIED))
				.intersects(supported);
	}

	/**
	 * Setting the encoding quality to the specified value on the JPEG encoder.
	 * 0.5 is a good default.
	 */
	void setJPEGQuality(Player p, float val) {

		Control cs[] = p.getControls();
		QualityControl qc = null;
		VideoFormat jpegFmt = new VideoFormat(VideoFormat.JPEG);

		// Loop through the controls to find the Quality control for
		// the JPEG encoder.
		for (int i = 0; i < cs.length; i++) {

			if (cs[i] instanceof QualityControl && cs[i] instanceof Owned) {
				Object owner = ((Owned) cs[i]).getOwner();

				// Check to see if the owner is a Codec.
				// Then check for the output format.
				if (owner instanceof Codec) {
					Format fmts[] = ((Codec) owner)
							.getSupportedOutputFormats(null);
					for (int j = 0; j < fmts.length; j++) {
						if (fmts[j].matches(jpegFmt)) {
							qc = (QualityControl) cs[i];
							qc.setQuality(val);
							System.err.println("- Setting quality to " + val
									+ " on " + qc);
							break;
						}
					}
				}
				if (qc != null)
					break;
			}
		}
	}
	


	/***************************************************************************
	 * Convenience methods to handle processor's state changes.
	 **************************************************************************/

	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;

		// Call the required method on the processor
		if (state == Processor.Configured) {
			p.configure();
		} else if (state == Processor.Realized) {
			p.realize();
		}

		// Wait until we get an event that confirms the
		// success of the method, or a failure event.
		// See StateListener inner class
		while (p.getState() < state && !failed) {
			synchronized (getStateLock()) {
				try {
					getStateLock().wait();
				} catch (InterruptedException ie) {
					return false;
				}
			}
		}

		if (failed)
			return false;
		else
			return true;
	}

	/***************************************************************************
	 * Inner Classes
	 **************************************************************************/

	class StateListener implements ControllerListener {

		public void controllerUpdate(ControllerEvent ce) {

			// If there was an error during configure or
			// realize, the processor will be closed
			if (ce instanceof ControllerClosedEvent)
				setFailed();

			// All controller events, send a notification
			// to the waiting thread in waitForState method.
			if (ce instanceof ControllerEvent) {
				synchronized (getStateLock()) {
					getStateLock().notifyAll();
				}
			}
		}
	}

	class PlayerWindow extends JFrame implements ActionListener
	{
		Container c;
		JButton rec, stopRec;
		DataSource ds = null;
		Processor p = null;

		public PlayerWindow()
		{
			c  = getContentPane();
			c.setLayout( new BorderLayout() );
			
			rec = new JButton( "錄音" );
			stopRec = new JButton( "停止" );
			rec.addActionListener( this );
			stopRec.addActionListener( this );
			c.add( rec, BorderLayout.WEST );
			c.add( stopRec, BorderLayout.EAST );
			stopRec.setEnabled( false );
			
		    addWindowListener(
					new WindowAdapter(){
							public void windowClosing( WindowEvent e ){
								stoptransmit();
								close();
							}
					}
			);
		}
		
		public void startPlayer()
		{
//			Container c = getContentPane();
//			c.setLayout( new BorderLayout() );
			rec.setEnabled( false );
			stopRec.setEnabled( false );
			
			Component comp;
			player.start();
//			p.realize();
//			p.start();
			
			if(( comp = player.getControlPanelComponent()) != null )
				c.add( comp, BorderLayout.SOUTH );
//			if(( comp = p.getVisualComponent()) != null )
//				c.add( comp, BorderLayout.CENTER );
//			if(( comp = p.getControlPanelComponent()) != null )
//				c.add( comp, BorderLayout.SOUTH );
			validate();			
			pack();
			setVisible( true );
		}
		
		public void startRealMedia()
		{			
			Component comp;
//			p.realize();
//			p.start();
			
			if(( comp = processor.getControlPanelComponent()) != null )
				c.add( comp, BorderLayout.SOUTH );
			validate();			
			pack();
			setVisible( true );
		}
		
		public void close() {
			if( player != null )
				player.close();
		    setVisible(false);
		    dispose();
		}
		
		public void recordAudio()
		{
			ds = ((SourceCloneable)source).createClone();

			try {
				p = Manager.createProcessor( ds );
				FileTypeDescriptor type = new FileTypeDescriptor(
						FileTypeDescriptor.WAVE );
				MediaLocator dest = new MediaLocator( "file:///C:/foo.wav" );
//				System.out.println("before");
				p.configure();
				
				while( p.getState() != Processor.Configured );
//				if( p.getState() == Processor.Configured )
//					System.out.println("after");
				p.setContentDescriptor( type );
				p.realize();
				
				while( p.getState() != Processor.Realized );

				p.start();
				filewriter = Manager.createDataSink( p.getDataOutput(), dest );
				filewriter.open();
				filewriter.start();
				
			} catch (NoProcessorException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (NoDataSinkException e) {
				e.printStackTrace();
			}
		}
		
		public void stopRecord()
		{
			if( p != null )
			{
				p.stop();
				p.close();
			}
			if( filewriter != null )
			{
				try {
					filewriter.stop();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				filewriter.close();
			}
		}
		
		public void actionPerformed( ActionEvent e )
		{
			if( e.getSource() == rec )
			{
				rec.setEnabled( false );
				stopRec.setEnabled( true );
				recordAudio();
			}
			else if( e.getSource() == stopRec )
			{
				rec.setEnabled( true );
				stopRec.setEnabled( false );
				stopRecord();
			}
		}
	}

}


