/*
 * This class represents an User Agent Server and is implemented as 
 * a thread that is spawned by the "SipServer" server every time
 * there is an incoming call. The life of this server thread is only
 * for the duration of the call and ends at the termination of the call. 
 * The server creates a pop up dialog box to display incoming call 
 * information and to allow the callee either to answer or reject
 * the call. 
 *
 * The server implements "finger" on the localhost to see if the callee 
 * is logged in. If not, it sends the "604 Does not exist anywhere" message. 
 * Also, if the user has set the call forwading feature, it responds with
 * the "302 Moved temporarily" message and the Location field.
 * The supported SIP response messages are: 
 *	"180 Ringing";
 *	"200 OK";
 *	"603 Decline";
 *	"302 Moved Temporarily";
 *	"604 Does not exist anywhere";
 * 
 * The server also reads SDP messages and uses the connection information
 * description to  obtain the ip address of the host it wants to start
 * a session (media session) with.  In case of a conference call the connection
 * address will be different from the originator's(caller's) ip address.
 * For unicast two-party calls, those two addresses will be the same.
 *
 * Author: Janet H. Park
 * Date: August, 1998
 *
 */

import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.*;
import sun.audio.*;

public class UAServer extends Thread {


	Socket s;            // socket connection given from the SipServer
	InputStream sin;     // io streams and handles
	OutputStream sout;
	BufferedReader in;
	PrintWriter out;
	
	AudioStream as;      // audio (telephone ringing) file stream
	AudioPlayer ap;

	int state = INITIAL;  // current state and previous state
	int prevstate = INITIAL;

	String conn_addr;     // session address obtained from session description.
	                      // media agents will be started against this address.
	                      // can be either caller's address or multicast address
	                      // in case of conference
	CalleeDialog win;     // GUI dialog box to display incoming call info

	SIP sip;              // SIP message handler
	SDP sdp;              // SDP message handler

	// SIP request message synopsis
	final static String INVITE = "INVITE";
	final static String ACK = "ACK";
	final static String BYE = "BYE";

	// SIP response message synopsis
	final static String RINGING = "180 Ringing";
	final static String OK = "200 OK";
	final static String MOVED = "302 Moved Temporarily";
	final static String DECLINE = "603 Decline";
	final static String NOTAVAILABLE = "604 Does not exist anywhere";
	final static String SIPVERSION = "SIP/2.0";

	// state the phone goes through
	final static int INITIAL = 0;
	final static int PROCEEDING = 100;
	final static int FINAL_STATUS_OK = 200;
	final static int FINAL_STATUS_DECLINE = 603;
	final static int FINAL_STATUS_NOTAVAILABLE = 604;
	final static int FINAL_STATUS_MOVED = 302;
	final static int CONFIRMED = 999;

	// logfile name and output handles
	File logfilename;          
	FileOutputStream fo;
	PrintWriter logfile;

	
	public UAServer(Socket s) {
		this.s = s;

		// open log file
		try {
			logfilename = new File("incall.log");
			fo = new FileOutputStream(logfilename);
			logfile = new PrintWriter(fo, true);
	  	} catch (IOException e) {
			System.out.println("UAServer: opening logfile:" + e.getMessage());
		}

		ap =  AudioPlayer.player;
		// try {
            	// 	as = new AudioStream(new FileInputStream("ring.au"));
		// } catch (IOException e) {
		// 	System.err.println("UAServer:" + e);
		// }

	}

	public void run() {
	  	try {
			// get socket input/output stream handle
			sin = s.getInputStream();
			in = new BufferedReader(new InputStreamReader(sin));
			sout = s.getOutputStream();
			out = new PrintWriter(sout, true);

	  	} catch (Exception e) {
			e.printStackTrace();
			System.out.println("UAServer.run(): Exception ");
	  	} 

		// create a dialog box to display an incoming call
		// one dialog per call, new one is created for each new call
		//
		win = new CalleeDialog("Call Box - Incoming Call",this);

		// wait for an incoming call
		String line, req = "";
		while (true) {
		  	try {
				line = in.readLine();
				if (line == null)
					continue;
				log("RECV> " + line);
				req = new StringTokenizer(line).nextToken();
		  	} catch(IOException e) {
		 	 	System.out.println("UAServer:IOException:" + e );
		  	}

			if ((state == INITIAL) && (!req.equals(INVITE))) {
		 	 	log("non-INVITE message recvd during INITIAL state -> terminate");
				log("End of session - closing the connection");
				endSession();
				return;
			}
	
			if (req.equals(INVITE)) {
				if (state == INITIAL) {
					// read sip and sdp messages
					sip = new SIP(in, win, this);
					readSdp();

					String callee = sip.getCalleeId();
					if (Simphony.callforwarding && Simphony.uid.equals(callee)) {
						// send back the redirect message
						sip.sendMoved(MOVED, Simphony.fwdAddr, out, win, this);
						state = FINAL_STATUS_MOVED;
						log("Current State --> FINAL_STATUS_MOVED");
					} else if (finger(callee)) {
						// if the callee is logged in the localhost,
						// play telephone ringing sound file,
						// pop up the CalleeDialog window. and
						// send RINGING response to the caller
						ring();
						win.show();
						sip.sendResp(RINGING, out, win, this);
						state = PROCEEDING;
						log("Current State --> CALL PROCEEDING");
					} else {
						sip.sendResp(NOTAVAILABLE, out, win,this);
						state = FINAL_STATUS_NOTAVAILABLE;
						log("Current State --> FINAL_STATUS_NOTAVAILABL");
					}
				} else {
					// repeat invitation recvd. keep ringing
					// and sending the RINGING reaponse msg
					ring();
					sip.readRequest(in, win, this);
					readSdp();
					if (state == PROCEEDING) 
						sip.sendResp(RINGING, out, win, this);
				}
			} else if (req.equals(ACK)) {

				sip.readRequest(in, win, this);

				// ACK received. transition to the CONFIRMED state
				// and proceed based on what the previous state was -
				// whether the callee wanted to accept or decline.
				//
				if (state >= 200) {
					prevstate  = state;
					state = CONFIRMED;
					log("Current State --> CONFIRMED");
					switch (prevstate) {
						case FINAL_STATUS_OK:
							startMedia();
							endSession();
							return;
						case FINAL_STATUS_DECLINE:
							endSession();
							log("End of session - closing the connection");
							return;
						case FINAL_STATUS_MOVED:
							endSession();
							log("End of session - closing the connection");
							return;
						default:
					    	log("Error: unknown state :"+prevstate);
							return;
					} // end of swicth
				} else {
					log("ACK received while in a non-FINAL_STATUS state");
				}
			} else if (req.equals(BYE)) {
				sip.readRequest(in, win, this);
				state = INITIAL;
				prevstate = INITIAL;
				sip.sendResp(OK, out, win, this);
				endSession();
				log("End of session - closing the connection");
				return;
			} else {
				sip.readRequest(in, win, this);
				log("unknown request - perhaps out of sync condition");
			}
		} // end of while

	} // ****************  end of run()  ***********************


	public void readSdp() 
	{
	// reads SDP messages from the inputstream handle
	  try {

		String line;
		Vector mediaList = new Vector();  // to save callee's media information

		while ((line = in.readLine()) != null) {
			log("RECV> " + line);
			if (line.length() == 0)
				break;

			StringTokenizer st = new StringTokenizer(line, "=");
			String tok = st.nextToken();

			// get connection info as to where to send the data.
			// also media info is obtained but currently not used.

			if (tok.equals("c")) {
				tok = st.nextToken(" ");
				tok = st.nextToken();
				conn_addr = st.nextToken();
				if (conn_addr.startsWith("224")) {
					st = new StringTokenizer(conn_addr, "/");
					conn_addr = st.nextToken();
					win.setConn("conference");
				} else
					win.setConn("two-party");

			} else if (tok.equals("s")) {
				win.setSess(st.nextToken());
			} else if (tok.equals("i")) {
				win.setSessInfo(st.nextToken());
			} else if (tok.equals("m"))
				mediaList.addElement(line);
		}
		// media = new String[mediaList.size()];
		// mediaList.copyInto(media);

	  } catch(IOException e) {
		log("IOException occured" );
		System.out.println("SIP.readRequest():"+e);
	  }

	} // ****************  end of readSdp()  ***********************



	void sendSdp() 
	{
		// write SDP messages to the given outputstream handler.
		// should be invoked only for sending two-party call OK response
		// 
		String ver = "v=0";
		out.println(ver);
		win.log("SEND> " + ver);

		String owner = "o=" + sip.getCalleeId() + " " + "IN IP4 " + sip.getCalleeAddr();
		out.println(owner);
		win.log("SEND> " + owner);

		String conn = "c=IN IP4 " + sip.getCalleeAddr();
		out.println(conn);
		win.log("SEND> " + conn);

		Media[] ma = Simphony.mediaAgents;
		if (ma == null){
			System.out.println("\nNo Media Agent defined\n");
			log("\nNo Media Agent defined\n");
		} else {
			String media;
			for (int i = 0; i < ma.length;  i++)  {
				media = "m=" + ma[i].type() + " " + ma[i].port() + " " + 
		             	ma[i].transport() + " " + ma[i].format();
				out.println(media);
				win.log("SEND> " + media);
				
			}
		}

		out.println("");
		win.log("SEND>");

	} // ****************  end of sendSdp()  ***********************



	void ring() {

		// ap =  AudioPlayer.player;
		try {
            	 	as = new AudioStream(new FileInputStream("ring.au"));
			ap.start(as);
		} catch (IOException e) {
			System.err.println("UAServer.ring():" + e);
		}

	}


	
	void answer() {
		try {
			if (as != null) as.close(); 
		} catch (IOException e) {
			System.out.println("UAServer.answer(): closing audio file handles ");
			System.out.println(e);
		}
		if (state == PROCEEDING) {
			sip.sendResp(OK, out, win, this);
			// send session description only if not conference call
			if (!conn_addr.startsWith("224"))
				sendSdp();
			state = FINAL_STATUS_OK;
			log("Current State --> FINAL_STATUS_OK");
		}
	}


	public void startMedia()
	{ 
		// execption will be thrown if no media is set
		if (Simphony.mediaAgents.length == 0)
			System.out.println("No Media Agent defined ");

		// start media agents
		for (int i = 0;i < Simphony.mediaAgents.length;  i++) {
			log("Starting media agent - ");
			Simphony.mediaAgents[i].start(conn_addr);
		}
	}


	void reject() {
		if (state != FINAL_STATUS_DECLINE) {
			state = FINAL_STATUS_DECLINE;
			sip.sendResp(DECLINE, out, win, this);
			log("Current State --> FINAL_STATUS_DECLINE");
		}
		// endSession();
	}


	boolean finger(String calleeId) 
	{
		// using finger see if the callee is logged in the local machine

		Socket s;
		InputStream sin = null;     
		OutputStream sout = null;
		BufferedReader in = null;
		PrintWriter out = null;

		try {
			s = new Socket(InetAddress.getLocalHost().getHostName(), 79, true);
			sin = s.getInputStream();
			in = new BufferedReader(new InputStreamReader(sin));
			sout = s.getOutputStream();
			out = new PrintWriter(sout, true);

			out.println();
			String line;
			while ((line = in.readLine()) != null) {
				if (line.startsWith(calleeId))
					return true;
			}
			sin.close();
			sout.close();
			in.close();
			out.close();
			return false;
	  	} catch (ConnectException e) {
			System.out.println("UAServer.finger(): " + e.getMessage());
			System.out.println("UAServer.finger(): unable to finger ");
			return true;
	  	} catch (Exception e) {
			e.printStackTrace();
			System.out.println("UAServer.finger(): " + e.getMessage());
			return true;
	  	}
/*
	    	try {
			if (sin != null ) sin.close();
			if (sout != null ) sout.close();
			if (in != null ) in.close();
			if (out != null ) out.close();
			} catch (IOException e) {
				System.out.println("UAServer.endSession(): IOException ");
				e.printStackTrace();
			}
		return false;
*/
	}
		
	
	void moved() {
		sip.sendResp(MOVED, out, win, this);
		state = FINAL_STATUS_MOVED;
		log("Current State --> FINAL_STATUS_MOVED");
		// endSession();
	}
	
	void endSession() {
		// log("End of session - closing the connection");
	    	try {
			if (as != null ) as.close(); 
			if (sin != null ) sin.close();
			if (sout != null ) sout.close();
			if (in != null ) in.close();
			if (out != null ) out.close();
		} catch (IOException e) {
			System.out.println("UAServer.endSession(): IOException ");
			e.printStackTrace();
		}
	}

	synchronized void log(String msg) {
		win.log(msg);
		logfile.println(msg);
	}

	synchronized void debug(String msg) {
		System.out.println(msg);
	}

}
