package AUSS;
import AUSS.*;
import java.io.*;
import java.net.*;
/**
 * The Connection class, basically a connection between the program and AUSS Connector
 */
public class Connection {

    //* connect: Connection connect to a host or to a pipe and return a connection which will abstract from the developer if they are connected to a pipe or a host. Connect will send the header.
    //      o (host:String,port:int,isConnector:boolean,sendHeader:boolean) For Connecting to a possible nonConnector AUSS Unit.
    //      o (host:String,port:int)Connect to Connector
    //      o (host:String,port:int,type:int)Connect to Connector and specify connection type.
    //      o (pipeName:String)Connect to STDIN/STDOUT/STDERR 

	/**
	 * Is the current Connection Interleaved?
	 */
    	public boolean interleaved = false;
	/**
	 * What is the sample rate of the connection
	 */
	public int rate = 44100;
	/**
	 * The number of channels
	 */
	public int channels = 1;
	/**
	 * The endian string
	 */
	public String endian = "little";
	/**
	 * The type of data
	 */
	public String type   = "short";
	/**
	 * The the name of the connection
	 */
	public String name   = "";
	/**
	 * the size of each data type
	 */
	public int typesize	= 2;
	/**
	 * the framesize, datatype*channels
	 */
	public int framesize 	= 2;

	public boolean	getInterleaved	(){ return	interleaved	;}
	public int	getRate         (){ return	rate    	;}
	public int	getChannels     (){ return	channels	;}
	public String	getEndian       (){ return	endian  	;}
	public String	getType         (){ return	type    	;}
	public String	getName         (){ return	name    	;}
	public int	getTypesize     (){ return	typesize	;}
	public int	getFramesize 	(){ return 	framesize	;}
	public void  setInterleaved	(boolean	interleave) { this.interleaved	=interleaved;}
	public void  setRate    	(int		rate      ) { this.rate        	=rate;}
	public void  setChannels	(int		channels  ) { this.channels    	=channels;}
	public void  setEndian  	(String		endian    ) { this.endian      	=endian;}
	public void  setType    	(String		type      ) { this.type        	=type;}
	public void  setName    	(String		name      ) { this.name        	=name;}
	public void  setTypesize	(int		typesize  ) { this.typesize    	=typesize;}
	public void  setFramesize 	(int		framesize ) { this.framesize 	=framesize;}

	InputStream input  = null;
	OutputStream output = null;
	/**
	 * Connect to the host:port pair
	 */
	public void connect(String host,int port) throws IOException {
		connect(host,port,true,true);
	}
	/**
	 * Connect to host:port, is it a connector and should we send the header
	 */
	public void connect(String host,int port,boolean isConnector,boolean sendHeader) throws IOException {
		Socket sock = new Socket(host,port);
		input = sock.getInputStream();
		output = sock.getOutputStream();
		if (sendHeader) {
			write(makeHeader());
		}
	}
	/**
	 * We're already connected just initalize and send header
	 */
	public void connect(Socket sock) throws IOException {
		input = sock.getInputStream();
		output = sock.getOutputStream();
		write(makeHeader());
	}
	/**
	 * set the inputstream
	 */
	public void connect(InputStream i) throws IOException {
		input = i;
	}
	/**
	 * the the outputstream and write the header
	 */
	public void connect(OutputStream o) throws IOException {
		output = o;
		write(makeHeader());
	}
	/**
	 *disconnect(conn:Connection)Closes and Disconnect the connection.
	 */
	public void disconnect() throws IOException {
		input.close();
		output.close();
	}
	/**
	 * write(conn:Connection,data:byte [],size:int)Write data to the connection.
	 */
	public void write(String h) throws IOException {
		output.write(h.getBytes());
	}
	/**
	 * write size bytes from data starting at the offset
	 */
	public void write(byte [] data,int offset, int size) throws IOException {
		output.write(data,offset,size);
	}
	/**
	 * write size bytes from data 
	 */
	public void write(byte [] data, int size) throws IOException {
		output.write(data,0,size);
	}
	/**
	 * Reverse and write Data from BE to LE 
	 */
	public void writeLEShort(byte [] data, int size) throws IOException {
		writeLEShort(data,0,size);
	}
	/**
	 * Reverse and write Data from BE to LE 
	 */
	public void writeLEShort(byte [] data, int offset,int size) throws IOException {
		byte tmp;
		int i1;
		int i2;
		for (int i = offset; i < (offset+size); i++) {
			i1 = i*2;
			i2 = i*2+1;
			tmp = data[i2];
			data[i2] = data[i1];
			data[i1] = tmp;
		}
		output.write(data,offset,size);
	}
    //* read(conn:Connection,data:byte [],size:int)Read data from the connection
    	private byte [] bytes = null;
	/**
	 * Reverse and write Data from BE to LE 
	 */
	public void writeLEShort(short [] data,int size) throws IOException {
		writeLEShort(data,size,0);
	}
	public void writeLEShort(short [] data,int offset,int size) throws IOException {
		byte s1;
		byte s2;
		if (bytes==null || bytes.length < 2*(size-offset)) {
			bytes = new byte[2*(size-offset)];
		}
		for (int i = offset; i < size; i++) {
			s2 = (byte)((data[i] & 0xFF00) >> 8);
			s1 = (byte)(data[i] & 0x00FF);
			bytes[2*i] = s1;
			bytes[2*i+1] = s2;
		}
		output.write(bytes,2*offset,2*size);
	}
	/**
	 * read shorts that are in little endian format
	 */
    	public void readLEShort(short [] data, int size) throws IOException {
		if (bytes==null || bytes.length < 2*(size)) {
			bytes = new byte[2*(size)];
		}
		input.read(bytes,0,size*2);
		for (int i = 0; i < size; i++) {
			short s2 = bytes[2*i+1];
			short s1 = bytes[2*i];
			data[i] = (short)( ((s2 << 8) & 0xFF00) | (0x00FF & (s1)));
		}
	}
	/**
	 * read data
	 */
    	public void read(byte [] data, int size) throws IOException {
		input.read(data,0,size);
	}
	/**
	 * read data
	 */
    	public void read(byte [] data,int offset, int size) throws IOException {
		input.read(data,offset,size);
	}
	/**
	 * makeHeader(name:String)Makes a proper header to send at the head of the stream.
	 */
	public String makeHeader() throws IOException {
		String str = null;
		String buff = "";
		str = "<header><name>"+name+"</name><interleaved>"+interleaved+"</interleaved><rate>"+rate+"</rate><channels>"+channels+"</channels><endian>"+endian+"</endian><type>"+type+"</type><framesize>"+framesize+"</framesize>";
		String stre = "</header>";
		int len  = (str.length() + stre.length() + buff.length())%framesize;
		if (len!=0) {
			for (int i = 0 ; i < len; i++) {
				buff = buff + " ";
			}
		}
		str = str + buff+stre;
		return str;
	}
    	private short [] mixDown = null;
    	private short [] outDown = null;
	/**
	 * mixDownShort(conns:Connection [],connCount:int, data:byte [],size:int) 
	 * Takes a list of connections read size bytes from them, mixes the stream to data.
	 */
	public void mixDownShort(Connection [] conns, int size) throws IOException {
		if (mixDown==null || mixDown.length < (size)) {
			mixDown = new short[(size)];
			for (int i = 0 ; i < (size); i++) {
				mixDown[i] = 0;
			}
		}
		if (outDown==null || outDown.length < (size)) {
			outDown = new short[(size)];
		}
		for (int i = 0 ; i < (size); i++) {
			outDown[i] = 0;
		}
		for (int i = 0; i < conns.length; i++) {
			conns[i].readLEShort(mixDown,size);
			for (int j = 0; j < size; j++) {
				outDown[j] += mixDown[j]/conns.length;
			}
		}
		writeLEShort(outDown,0,size);
	}
//TODO
//    * acceptConnections(port:int): ConnectionAcceptor Binds to a port on the localhost and returns a ConnectionAcceptor
//    * isConnectionPending(ca:ConnectionAcceptor): boolean Indicates if there is a pending connection.
//    * getPendingConnection(ca:ConnectionAcceptor): Connection returns a connection from the ConnectionAcceptor from the ConnectionAcceptor. 
//

}

