package apps.pint;

import cnrg.itx.signal.*;
import cnrg.itx.signal.SignalEvent.*;
import cnrg.itx.ds.*;
import cnrg.itx.datax.*;
import cnrg.itx.datax.devices.*;
import java.util.*;

import java.net.SocketException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.*;

/**
 * Pint is a portable ITX client application. This class can be used without the 
 * PintUI class by specifying a null UI in the consrtuctor.
 */
public class pint implements SignalingObserver{ 
	
	/**
	 * Sample main method builds Pint with a UI.
	 */
	public static void main(String args[]) {
		
		PintUI ui = new PintUI(); //UI is built, creates an instance of pint.
	}
	
	/** Should pint look for id's in a directory?	 */
	private boolean useDS = true;
	
	/** The current dialing state */
	private int state = 0;
	
	/**constant for the idle dialing state	 */
	public static final int IDLE = 0;
	
	/**Constant for the dialing dialing state 	 */
	public static final int DIALING = 1;
	
	/**Constant for the connected dialing state	 */
	public static final int CONNECTED = 2;
	
	/**the one-line status of the client for display by the UI	 */
	private String status = "";
	
	/**the connection being used by the client */
	private SignalConnection sc = null;
	
	/**the client's desktop signaling	 */
	DesktopSignaling sig = null;
	
	/**the client's UI	 */
	private PintUI ui;
	
	/**the user's UID	 */
	private String uid = "";
	
	/**the user's password	 */
	private String pass;
	
	/**the thread that listens for updates of the conference participant list	 */
	private partListServer thread = null;
	
		
	/**Creates a new ITX client. Specify a null ui for no ui
	 * 
	 * @param ui The user interface to notify of status changes
	 * @param uid the user ID of the user
	 * @param pass the password of the user
	 * @param useDS Should pint contact the directory or dial given strings as ip addresses
	*/
	public pint (PintUI ui,String uid, String pass, boolean useDS) {
		this.ui = ui;
		this.uid = uid;
		this.pass = pass;
		this.useDS = useDS;
		
		try {
			thread = new partListServer(ui);
			thread.start();
		} catch (IOException e) {
			//someone else is using port 5556. We can't see who is in a conference we are attached to 
			status = "Conf. disabled. Free up port 5556.";
			System.out.println("\n"+status+"!!!!\n");
		}
		
		
		
		try {
			
			//assume we have access to Directory Services
			if (!useDS) throw new DirectoryServiceException("Directory disabled"); //if we don't
			sig = new DesktopSignaling(this,uid,pass);	//or if login fails, go on without directory.
		}catch (DirectoryServiceException dse) {
			
			//no Directory Services:
			try {
				sig = new DesktopSignaling(this,uid,5060); //trys the default sip port
			} catch (Throwable t) {
				//TODO:Currently nonfunctional: DS does not throw exception when port unavailable.
				sig = new DesktopSignaling(this,uid,0); //if anything goes wrong, let java pick the port.
			}
			status = "Directory disabled";			
		}
				
				
			

	}

	/** Returns the dialing state.
	 * @return int the current dialing state
	 */
	public int getState() {
		return state;
	}
	
	/**
	 * Returns the current one-line status of the app. This is useful if you are a GUI
	 * @return String the client's status	 */
	public String getStatus() {
		return status;
	}
	
	/**Returns the vector of participant name strings for conference part. listing
	 * @return Vector a vector of String
	 */
	public Vector getParticipants() {
		return thread.getParticipants();
	}
	
	/** method for dialing another ITX application.
	 * @param callee the phone number, uid, or ip:port of the application to dial
	 */
	public void call(String callee) {
		

		if (state == IDLE) {
			this.state = DIALING;
			status = "Dialing "+callee;
			changed(); //inform the UI that status/state has changed
			
			try {

				try { //first assume the address is an internet address.
					callee = verifyAddr(callee);
					sc = sig.Dial(callee,new Location("I",callee,uid));
				} catch (AddrVerifyException ae) {
					//if it is not an internet address, try it as a phone number
					
					if (!useDS) throw ae; //if we have no directory, give up
					sc = sig.Dial(callee);//else, try to dial it.

				}
				
				if (sc != null) {
					sc.open();
					this.state = CONNECTED;
					status = "Connected to "+callee;
				} else {		
					//TODO: does this ever happen?
					state = IDLE;
				}
				changed();
				
			//display something appropriate if something goes wrong
			}catch (AddrVerifyException e) {
				status = "Invalid internet address";
				state = IDLE;
				changed();
				
			} catch (DesktopSignalingException dse) {
				 System.out.println(dse.getMessage());
				 state = IDLE;
				 status = dse.getMessage();
				 changed();
				 
			} catch (DirectoryServiceException dse) {
				  System.out.println(dse.getMessage());
				  state = IDLE;
				  status = "Directory lookup failed";
				  changed();
				  
			} catch (DataException de) {
				  System.out.println(de.getMessage());
				  this.state = IDLE;
				  status = "Unable to build data connection";
				  changed();		
			} catch (java.lang.UnsatisfiedLinkError use) {
				  System.out.println(use.getMessage());
				  this.state = IDLE;
				  status = "Missing shared library";
				  changed();
			}

		}
	}
	
	/** Disconnects the current call
	 */
	public void abort() {
		if (state == IDLE) {
			
		} else {
			
			this.state = IDLE;
			status = "Call aborted";
			changed();
			
			if (sig != null && sc != null){
				try {
					sig.Hangup(sc);
				} catch (ConnectException ce) {
					System.out.println(ce);
				}
			} 
		}
	}
	
	
	/**Closes up the client object: disconnects all calls and kills the conf part server thread, logs out of DS
	 */
	public void close() {
		this.abort();
		
		try {
			thread.killServer();
		} catch (Exception e) {
		}
		
		try {
			sig.logout();
		} catch (Exception e) {
			e.printStackTrace();
		}
			
	}
	
	/**pint calls this to notify the ui of changes 	 */
	protected void changed() {
		if (ui!= null) {
			ui.dirtyStatus();
		}
	}
	/** Utility method to verify that a specified net address is valid
	 * @param in the internet address and port. </n> eg. localhost:5060 or 192.168.1.1:1881
	 * @return String the ip address and port of the given name
	 * @exception AddrVerifyException thrown when address is invalid
	 */
	private String verifyAddr(String in) throws AddrVerifyException{
		try {
			StringTokenizer tokens = new StringTokenizer(in,":");
			if (tokens.countTokens() == 1){
				throw new AddrVerifyException("Specify a port using the ':' character");
			}
			String host = tokens.nextToken();
			
			InetAddress ia = InetAddress.getByName(host);
			
			String port = tokens.nextToken();
			
			int portnum = Integer.parseInt(port);
			
			return ia.getHostAddress()+":"+portnum;
				
			
			
		}catch (Exception e) {
			throw new AddrVerifyException("Not a valid internet address");
		}
	}
	
	/* **************SignalObserver***********************************/
	
	/**
	 * This method informs the application that a peer application's invitation
	 * has been received. </n>Pint accepts all calls if it is not busy. No ring sound
	 * is produced, ui indicates caller's name.
	 * 
	 * @param ise is the InviteSignalEvent that contains all the information about
	 * the caller application.
	 * @return  void
	 * 
	 * @see cnrg.itx.signal.SignalEvent.InviteSignalEvent
	 */
	public void onInvite(InviteSignalEvent ise){
		if (state == IDLE){
			ise.accept();
			status = "Recieved call from "+ise.getSenderID();
		} else {
			ise.busy();
		}
	}

	/**
	 * This method informs the application that a peer application has sent
	 * a confirmation and the call setup is complete. </n>
	 * UI is updated, sc is assigned to given connection.
	 * 
	 * @param  sc is the SignalConnection the Application should use for data exchange.
	 *         The connection within SignalConnection may be instantiated by the application.
	 * 
	 * @return  void
	 * 
	 * @see cnrg.itx.signal.pint.SignalConnection
	 */
	public void onStartCall(SignalConnection sc){
		//if (state == DIALING) {
			
			try {
				sc.open();
				this.sc = sc;
				state = CONNECTED;
				changed();
			} catch (DataException de) {
				de.printStackTrace();
			}
		//} else {
		//	sc.abortCall();
		//}
	}
	/**
	 * This method informs the application that it should abort the call it was
	 * waiting for.
	 * 
	 * @param ase is the AbortSignalEvent describing the reason for the abort and
	 * which indicates the user that aborted the invite and returns the connection object
	 * the onInvite call gave signaling, if any.
	 * @return  void
	 */
	public void onAbortCall(AbortSignalEvent ase){
		this.state = IDLE;
		status = "Connection aborted";
		changed();	
	}
	
	/**
	 * This method informs the application that a peer application has hung up.
	 * 
	 * @param hse is the HangupSignalEvent that contains all the information about
	 * the application that has hung up.
	 * @return  void
	 * 
	 * @see cnrg.itx.signal.SignalEvent.HangupSignalEvent
	 */
	public void onHangup(HangupSignalEvent hse){
		this.state = IDLE;
		status = "Connection closed";
		changed();	
		try {
			hse.getSignalConnection().close();
		} catch(Exception e){e.printStackTrace();}
	}
	
	/**
	 * This method informs the application that a DTMF has been received.
	 * 
	 * @param dtmfse is the DTMFSignalEvent that contains the tone(s).
	 * @return  void
	 * 
	 * @see cnrg.itx.signal.SignalEvent.DTMFSignalEvent
	 */
	public void onDTMF(DTMFSignalEvent dtmfse){
		System.out.println("Got DTMF: "+dtmfse.getDTMF());
	}


/** This class maintains the participant list server. It listens on port 5556 for strings separated by /r/n
 * Upon recipt of a group of strings, the thread updates the UI
 */
class partListServer extends Thread {
	private ServerSocket recvSock = null;
	private Socket rcvS = null;
	private boolean flag = true;
	private PintUI ui= null;
	private Vector participants = new Vector(); 
	
	public partListServer(PintUI ui) throws IOException{
		this.ui = ui;
		
		recvSock = new ServerSocket(5556, 20);
		if (recvSock == null) {
			throw new IOException("Pint failed to open conference interface socket. Make sure only one pint is running");
		}
		//myPort = recvSock.getLocalPort();	
		//System.out.println("DesktopSignalingServer listening on port-> "+myPort+"\n");
	}

	
	
	
	/**
	 * Method called when the thread is started.
	 * 
	 * @param   None.
	 * @return  void
	 */
	public void run(){
		
		while(flag){
			
			if (recvSock != null) {
				try{
					//System.out.println("Ready to accept requests...");
					rcvS = recvSock.accept();
					rcvS.setSoTimeout(20000);

					//System.out.println("Received something...");
					
					LineNumberReader lr = new LineNumberReader(new InputStreamReader(rcvS.getInputStream()));
					participants.removeAllElements();
					String data;
					while((data = lr.readLine()) != null){
						participants.addElement(data);
					}
					if (ui != null) {
						ui.dirtyStatus();
					}
					
				}
				catch (SocketException se){
				}
				catch(IOException ioe){
				}
			}else break;
		}
		//System.out.println("conf participant reader Thread exiting");
	}
	
	/**
	 * This method kills the Server thread by closing the listening socket 
	 * and exiting the run() method.  If the socket is already closed,
	 * this tends to hang on the recvSock.close
	 * 
	 * @param   None.
	 * @return  void
	 */
	public void killServer(){
		flag = false;
		try{
		//System.out.println("killServer is closing the socket...");
			recvSock.close();
		//System.out.println("socket closed.");
		}
		catch(IOException ioe){
			ioe.printStackTrace();
		}
	}
	
	public Vector getParticipants() {
		return participants;
	}
}

}