/* ***************************************************************************+
 * ITX package (cnrg.itx) for telephony application programming.              *
 * Copyright (c) 1999  Cornell University, Ithaca NY.                         *
 * A copy of the license is distributed with this package.  Look in the docs  *
 * directory, filename GPL.  Contact information: bergmark@cs.cornell.edu     *
 ******************************************************************************/

package cnrg.itx.datax.devices;

import java.net.*;
import java.io.*;
import java.util.*;
import cnrg.itx.datax.*;

/**
 * Class to act as a channel endpoint. Sends data to the network.
 */
public class NetworkDestination implements Destination
{
	/**
	 * Attribute for storing the number of bytes written to the stream.
	 */
	private int bytesRead = 0;

	/**
	 * Attribute to hold number of packets sent
	 */
	private int iNumPackets = 0 ;
	
	/**
	 * Attribute for storing the device instance number. This is a static attribute and will be
	 * incremented in the constructor. This represents the total number of instances of
	 * the stream destinations.
	 */
	private static int instances = 0;
	
	/**
	 * Attribute for storing the device ID. This is the value of the instance at the moment.
	 */
	private int iDevice;
	
	/**
	 * Attribute for storing the datagram socket.
	 */
	private DatagramSocket socket;
	
	/**
	 * Attribute to store the mute state of the device.
	 */
	private boolean mute;

	/**
	 * Attribute to store the peer IPAddress
	 */
	private InetAddress iaPeer;
	
	/**
	 * Attribute to store peer port
	 */
	private int iPort;
	
	/**
	 * Class to package up the devices for FEC correction
	 */
	private FECBuffer fecBuffer;
	
	/**
	 * Creates valid RTP packets out of data with appropriate headers
	 */
	private RTPPacketFactory rtpFactory;
	
	/**
	 * If FEC and bufferig is supported.
	 */
	private boolean useFECandRTP = true;
	
	/**
	 * Default constructor to create a network destination.
	 * @exception DataException thrown if the socket cannot be created
	 */
	public NetworkDestination() throws DataException
	{		
		// Increment the number of instances and assign the device id
		instances++;
		iDevice = instances;
		
		mute = false;
		
		// Initialize the datagram socket
		try
		{
			socket = new DatagramSocket();
		}
		catch (SocketException e)
		{
			throw new DataException("Couldn't open socket");
		}
		
		rtpFactory = new RTPPacketFactory();
	}
	
	/**
	 * Constructor for the network device specifying if FEC and buffering should
	 * be used.
	 * @param useFECandRTP true if FEC and RTP are to be used
	 * @exception DataException thrown if resources cannot be allocated
	 */
	public NetworkDestination (boolean useFECandRTP) throws DataException
	{
		this ();
		
		if (!useFECandRTP) rtpFactory = null;
		
		this.useFECandRTP = useFECandRTP;
	}
	
	/**
	 * Method to write data to the stream.
	 * @param b The array of bytes to write to the stream
	 * @exception DataException This is thrown when an IO error occurs
	 */
	public void write(byte [] b) throws DataException
	{
		if(iaPeer == null)
			return;
		
		// Write to the device if it is not muted
		if(!mute)
		{
			bytesRead += b.length;
			
			// Comment out next 2 lines to not use FEC buffer
			if (useFECandRTP) {
				b = fecBuffer.pack(b);
				b = rtpFactory.createRTPPacket(b);
			}
			
			// Create a datagram packet to send to the peer destination
			DatagramPacket packet = new DatagramPacket(b, b.length, iaPeer, iPort);
			
			try
			{
				socket.send(packet);
			}
			catch (IOException e)
			{
				throw new DataException(e.toString());
			}
			
			iNumPackets ++;
		}
	}
	
	/**
	 * Method to close the network destination
	 */
	public void close()
	{
		socket.close();
	}
	
	/**
	 * Method to mute the network destination.
	 * @param mute true if the device has to be muted and false if it has to be activated
	 * @return boolean true if the device was muted and false if it was activated
	 */
	public boolean mute(boolean mute)
	{
		this.mute = mute;
		return mute;
	}
	
	public Stats getStatistics ()
	{
		Stats networkStats = new Stats();
		networkStats.addStat("Device", "Network destination");
		networkStats.addStat("<Network Destination " + iDevice + "> (Audio) Bytes sent to wire", new Integer(bytesRead));
		networkStats.addStat("<Network Destination " + iDevice + "> Packets sent to wire ", new Integer(iNumPackets));

		return networkStats; 
	}

	/**
	 * Returns a collection of properties supported.
	 */ 
	public PropertiesCollection getProperties() throws DataException
	{
		PropertiesCollection pc = new PropertiesCollection();
		
		if (useFECandRTP) {
			int seq = rtpFactory.getSequenceNumber();
			pc.addProperty (new NetworkProperty (seq));
		} else {
			pc.addProperty (new NetworkProperty (-1));
		}
		return pc;
	}

	/**
	 * Sets the given properties collection into the device
	 */
	public void setProperties(PropertiesCollection pc) throws DataException
	{
		// don't really care about local properties, so ignore them.
	}

	/**
	 * Interface to set the given properties collection into the device. WOrks under the 
	 * assumption that this is the properties collection of the peer.
	 */
	public void setPeerProperties(PropertiesCollection pc) throws DataException
	{
		boolean bGotProperty = false;
		Object prop;
		int pkSize;
		
		for (Enumeration e = pc.getProperties(); e.hasMoreElements() ; )
		{
			prop = e.nextElement();
			
			if (prop instanceof NetworkProperty)
			{
				if (! bGotProperty)
				{
					iPort = ((NetworkProperty)prop).getPort();
					iaPeer = ((NetworkProperty)prop).getAddress();
					pkSize = ((NetworkProperty)prop).getPacketSize();
					if ( ( pkSize >= 0 ) &&
					   ((NetworkProperty)prop).getInitialSequenceNumber () != -1)
						fecBuffer = new FECBuffer( pkSize );
					else
						useFECandRTP = false;
					
					bGotProperty = true;				
				}
				else 
				{
					throw new DataException("More than 1 network source that we need to send data to!");
				}
			}
		}
	}
}
