/*
 *
 * The CallerDialog box is created and displyed by the main Simphony object
 * when the user selects the "call box" option from the main menu. It provides 
 * the user with GUI to allow them to place an outgoing call. It also spawns 
 * a thread to handle the SIP/SDP communication with a user agent server on the
 * destination host, therefore effectively serving  as a aser agent client. 
 * The thread implmentation within the CallerDialog object allows the user to 
 * interact with the dialog box while the call is proceeding. This allows the
 * user to cancel the call while the phone is ringing on the callee's host.
 * 
 * The call can be either unicast, two-party call or a conference call using 
 * multicast. When a conference call is selected, the ConfDialog box will prompting
 * the user to enter conference session information including the multicast address.
 * When the callee accepts the call, whatever media agnets currently defined on the
 * local host are started. It is assumed that media agents have already been defined 
 * prior to making calls. The progress of the call including state transition as well 
 * as SIP/SDP messages sent and received is displayed on a text window within the dialog 
 * box. Also a log file "outcall.log" is created in the current working directory.
 *
 * The SIP request messagess currently supported are:
 * 	INVITE, ACK, BYE
 * ALso supported is a simple SDP based session description as follows:
 *	protocol version
 *	origin
 *	session name
 * 	session info
 *	connection
 *	media
 *
 * Author: Janet H. Park
 * Date: August, 1998
 *
 */

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

public class CallerDialog extends Dialog
{
	public CallerDialog(Frame parent, boolean modal, Media[] ma)
	{
		super(parent, modal);

		mediaAgents = ma;

		// This code is automatically generated by Visual Cafe when you add
		// components to the visual environment. It instantiates and initializes
		// the components. To modify the code, only use code syntax that matches
		// what Visual Cafe can generate, or Visual Cafe may be unable to back
		// parse your Java file into its visual environment.
		//{{INIT_CONTROLS
		GridBagLayout gridBagLayout;
		gridBagLayout = new GridBagLayout();
		setLayout(gridBagLayout);
		setVisible(false);
		setSize(insets().left + insets().right + 437,insets().top + insets().bottom + 442);
		panel1 = new java.awt.Panel();
		panel1.setLayout(null);
		panel1.setBounds(insets().left + 10,insets().top + 10,417,422);
		panel1.setForeground(new Color(-16777125));
		panel1.setBackground(new Color(13231359));
		GridBagConstraints gbc;
		gbc = new GridBagConstraints();
		gbc.weightx = 100.0;
		gbc.weighty = 100.0;
		gbc.fill = GridBagConstraints.BOTH;
		gbc.insets = new Insets(10,10,10,10);
		((GridBagLayout)getLayout()).setConstraints(panel1, gbc);
		add(panel1);
		labelCallerId = new java.awt.Label("From:",Label.RIGHT);
		labelCallerId.setBounds(26,26,48,24);
		labelCallerId.setFont(new Font("Dialog", Font.PLAIN, 12));
		panel1.add(labelCallerId);
		labelCallerAddr = new java.awt.Label("At:",Label.RIGHT);
		labelCallerAddr.setBounds(170,26,24,24);
		labelCallerAddr.setFont(new Font("Dialog", Font.PLAIN, 12));
		panel1.add(labelCallerAddr);
		textFieldCallerId = new java.awt.TextField();
		textFieldCallerId.setBounds(86,14,84,28);
		textFieldCallerId.setBackground(new Color(-3545857));
		panel1.add(textFieldCallerId);
		labelCalleeId = new java.awt.Label("To:",Label.RIGHT);
		labelCalleeId.setBounds(38,50,36,24);
		labelCalleeId.setFont(new Font("Dialog", Font.PLAIN, 12));
		panel1.add(labelCalleeId);
		labelCalleeAddr = new java.awt.Label("At:",Label.RIGHT);
		labelCalleeAddr.setBounds(170,50,24,24);
		labelCalleeAddr.setFont(new Font("Dialog", Font.PLAIN, 12));
		panel1.add(labelCalleeAddr);
		textFieldCalleeId = new java.awt.TextField();
		textFieldCalleeId.setBounds(86,50,84,28);
		panel1.add(textFieldCalleeId);
		textFieldCalleeAddr = new java.awt.TextField();
		textFieldCalleeAddr.setBounds(206,50,192,28);
		panel1.add(textFieldCalleeAddr);
		textFieldCallerAddr = new java.awt.TextField();
		textFieldCallerAddr.setBounds(206,14,192,28);
		textFieldCallerAddr.setBackground(new Color(-3545857));
		panel1.add(textFieldCallerAddr);
		buttonCancel = new java.awt.Button();
		buttonCancel.setActionCommand("button");
		buttonCancel.setLabel("Cancel");
		buttonCancel.setBounds(170,194,72,22);
		buttonCancel.setForeground(new Color(16777215));
		buttonCancel.setBackground(new Color(4212140));
		panel1.add(buttonCancel);
		buttonCall = new java.awt.Button();
		buttonCall.setActionCommand("button");
		buttonCall.setLabel("Call");
		buttonCall.setBounds(278,194,72,22);
		buttonCall.setForeground(new Color(16777215));
		buttonCall.setBackground(new Color(4212140));
		panel1.add(buttonCall);
		textAreaState = new java.awt.TextArea();
		textAreaState.setBounds(26,254,372,156);
		panel1.add(textAreaState);
		labelState = new java.awt.Label("Session Information");
		labelState.setBounds(26,230,132,24);
		labelState.setFont(new Font("Dialog", Font.BOLD, 12));
		panel1.add(labelState);
		labelSubject = new java.awt.Label("Subject:",Label.RIGHT);
		labelSubject.setBounds(14,86,60,24);
		panel1.add(labelSubject);
		textFieldSubject = new java.awt.TextField();
		textFieldSubject.setBounds(86,86,312,28);
		panel1.add(textFieldSubject);
		labelPriority = new java.awt.Label("Priority:",Label.RIGHT);
		labelPriority.setBounds(14,122,60,24);
		panel1.add(labelPriority);
		GroupPrior = new CheckboxGroup();
		radioButtonNormal = new java.awt.Checkbox("normal", GroupPrior, true);
		radioButtonNormal.setBounds(86,122,72,24);
		panel1.add(radioButtonNormal);
		radioButtonUrgent = new java.awt.Checkbox("urgent", GroupPrior, false);
		radioButtonUrgent.setBounds(182,122,84,24);
		panel1.add(radioButtonUrgent);
		radioButtonLow = new java.awt.Checkbox("low", GroupPrior, false);
		radioButtonLow.setBounds(278,122,60,24);
		panel1.add(radioButtonLow);
		buttonClear = new java.awt.Button();
		buttonClear.setActionCommand("button");
		buttonClear.setLabel("Clear");
		buttonClear.setBounds(62,194,72,22);
		buttonClear.setForeground(new Color(16777215));
		buttonClear.setBackground(new Color(4212140));
		panel1.add(buttonClear);
		GroupConn = new CheckboxGroup();
		radioButtonTwo = new java.awt.Checkbox("two-party", GroupConn, true);
		radioButtonTwo.setBounds(122,158,84,24);
		panel1.add(radioButtonTwo);
		radioButtonConf = new java.awt.Checkbox("conference", GroupConn, false);
		radioButtonConf.setBounds(230,158,96,24);
		panel1.add(radioButtonConf);
		labelConn = new java.awt.Label("Connection:",Label.RIGHT);
		labelConn.setBounds(2,158,72,24);
		panel1.add(labelConn);
		setTitle("CallerDialog");
		//}}

		//{{REGISTER_LISTENERS
		SymWindow aSymWindow = new SymWindow();
		this.addWindowListener(aSymWindow);
		SymAction lSymAction = new SymAction();
		buttonCancel.addActionListener(lSymAction);
		buttonCall.addActionListener(lSymAction);
		buttonClear.addActionListener(lSymAction);
		SymMouse aSymMouse = new SymMouse();
		radioButtonConf.addMouseListener(aSymMouse);
		//}}
		

		String caller = System.getProperty("user.name");
		textFieldCallerId.setText(caller);
        	try {
		    String hostname = InetAddress.getLocalHost().getHostName();
		    textFieldCallerAddr.setText(hostname);
		} catch (UnknownHostException e) {
		    System.out.println("CallerDialog:" + e);
		}

		// open a log file
		try {
			logfilename = new File("outcall.log");
			fo = new FileOutputStream(logfilename);
			logfile = new PrintWriter(fo, true);
		} catch (IOException e) {
			System.out.println("CallerDialog:opening logfile" + e.getMessage());
		}
	}
	
	public void addNotify()
	{
  	    // Record the size of the window prior to calling parents addNotify.
	    Dimension d = getSize();

		super.addNotify();

		if (fComponentsAdjusted)
			return;

		// Adjust components according to the insets
		setSize(insets().left + insets().right + d.width, insets().top + insets().bottom + d.height);
		Component components[] = getComponents();
		for (int i = 0; i < components.length; i++)
		{
			Point p = components[i].getLocation();
			p.translate(insets().left, insets().top);
			components[i].setLocation(p);
		}
		fComponentsAdjusted = true;
	}

    // Used for addNotify check.
	boolean fComponentsAdjusted = false;


	public synchronized void show()
	{
		Rectangle bounds = getParent().bounds();
		Rectangle abounds = bounds();

		move(bounds.x + (bounds.width - abounds.width)/ 2,
			 bounds.y + (bounds.height - abounds.height)/2);

		super.show();
	}

	//{{DECLARE_CONTROLS
	java.awt.Panel panel1;
	java.awt.Label labelCallerId;
	java.awt.Label labelCallerAddr;
	java.awt.TextField textFieldCallerId;
	java.awt.Label labelCalleeId;
	java.awt.Label labelCalleeAddr;
	java.awt.TextField textFieldCalleeId;
	java.awt.TextField textFieldCalleeAddr;
	java.awt.TextField textFieldCallerAddr;
	java.awt.Button buttonCancel;
	java.awt.Button buttonCall;
	java.awt.TextArea textAreaState;
	java.awt.Label labelState;
	java.awt.Label labelSubject;
	java.awt.TextField textFieldSubject;
	java.awt.Label labelPriority;
	java.awt.Checkbox radioButtonNormal;
	CheckboxGroup GroupPrior;
	java.awt.Checkbox radioButtonUrgent;
	java.awt.Checkbox radioButtonLow;
	java.awt.Button buttonClear;
	java.awt.Checkbox radioButtonTwo;
	CheckboxGroup GroupConn;
	java.awt.Checkbox radioButtonConf;
	java.awt.Label labelConn;
	//}}

	class SymWindow extends java.awt.event.WindowAdapter
	{
		public void windowClosing(java.awt.event.WindowEvent event)
		{
			Object object = event.getSource();
			if (object == CallerDialog.this)
				Dialog1_WindowClosing(event);
		}
	}
	
	void Dialog1_WindowClosing(java.awt.event.WindowEvent event)
	{
		hide();
	}

	class SymMouse extends java.awt.event.MouseAdapter
	{
		public void mouseClicked(java.awt.event.MouseEvent event)
		{
			Object object = event.getSource();
			if (object == radioButtonConf)
				radioButtonConf_MouseClick(event);
		}
	}

	void radioButtonConf_MouseClick(java.awt.event.MouseEvent event)
	{
		// to do: code goes here.
			 
		//{{CONNECTION
		// Create and show as modal
		if (conf == null)
			conf = new ConfDialog((Frame)getParent(), true);
		conf.show();
		// (new ConfDialog((Frame)getParent(), true)).show();
		//}}
	}

	class SymAction implements java.awt.event.ActionListener
	{
		public void actionPerformed(java.awt.event.ActionEvent event)
		{
			Object object = event.getSource();
			if (object == buttonCancel)
				buttonCancel_Action(event);
			if (object == buttonCall)
				buttonCall_Action(event);
			else if (object == buttonClear)
				buttonClear_Action(event);
		}
	}

	void buttonClear_Action(java.awt.event.ActionEvent event)
	{
		// to do: code goes here.
			 
		//{{CONNECTION
		// Clear the text for TextField
		textFieldCalleeId.setText("");
		//}}
			 
		//{{CONNECTION
		// Clear the text for TextField
		textFieldCalleeAddr.setText("");
		//}}
			 
		//{{CONNECTION
		// Clear the text for TextField
		textFieldSubject.setText("");
		//}}
			 
		//{{CONNECTION
		// Check the Checkbox
		radioButtonNormal.setState(true);
		//}}
			 
		//{{CONNECTION
		// Disable the Checkbox
		radioButtonUrgent.setState(false);
		//}}
			 
		//{{CONNECTION
		// Disable the Checkbox
		radioButtonLow.setState(false);
		//}}
			 
		//{{CONNECTION
		// Check the Checkbox
		radioButtonTwo.setState(true);
		//}}
			 
		//{{CONNECTION
		// Disable the Checkbox
		radioButtonConf.setState(false);
		//}}
			 
	}


	void buttonCancel_Action(java.awt.event.ActionEvent event)
	{
		// user wants to hang up, cancel the current call.
		// send the BYE request message.
		//
		if (currState != INITIAL) {
			sip.sendReq("BYE", out, this);
			log("Session canceled");
			currState = INITIAL;
			log("Current State --> INITIAL");
		}

		// if in the midlle of invitation 
		// then send CANCEL, close the conn
			 
		//{{CONNECTION
		// Hide the Dialog
		setVisible(false);
		// dispose();
		//}}
	} // *************  end of buttonCancel_Action()  *******************



	void buttonCall_Action(java.awt.event.ActionEvent event)
	{
		// Place a outgoing call since user clicked on the "call" button.
		// First, gather all user input from GUI
	  	try {
			callerId = textFieldCallerId.getText().trim();
			callerAddr = InetAddress.getByName(textFieldCallerAddr.getText().trim());
			calleeId = textFieldCalleeId.getText().trim();
			calleeAddr = InetAddress.getByName(textFieldCalleeAddr.getText().trim());
			subject = textFieldSubject.getText().trim();
			priority = GroupPrior.getSelectedCheckbox().getLabel();
			connection = GroupConn.getSelectedCheckbox().getLabel(); 
	  	} catch (UnknownHostException e) {
				log("CallerDialog: UnknownHostException");
				e.printStackTrace();
	  	} 

		// open socket to the server and get io handles
		InetAddress server;
		try {
			server = calleeAddr;
			socket = new Socket(server, SIPPORT);
			log("\n***********   Connected to host: "+server);
	    	} catch (ConnectException e) {
	        	log("CallerDialog.invite():" + e.getMessage());
	        	return;
   		} catch (IOException e) { 
			log("CallerDialog.invite():" + e.getMessage());
			System.err.println(e);
			return;
		} 
		try {
			// get an output stream handle for the socket
			sout = socket.getOutputStream();
			out = new PrintWriter(sout, true);
 
			// get a input stream handle for the socket
			sin = socket.getInputStream();
			in = new BufferedReader(new InputStreamReader(sin));
		} catch (IOException e) { 
			log("CallerDialog.invite():Exception");
			e.printStackTrace();
			endSession();
		} 

		// if for conference, get session information from the ConfDialog GUI.
		// This info will be part of sdp messages to the callee. sess_addr 
		// indicates where the callee should send the data to.

		if (connection.equals("conference")) {
			sess_name = conf.name();
			sess_info = conf.info();
			sess_addr = conf.addr();
			conn_addr = sess_addr;
			sdp = new SDP(callerId, callerAddr.getHostAddress(), sess_name, 
		                      sess_info, sess_addr, mediaAgents);
		} else {
			sdp = new SDP(callerId, callerAddr.getHostAddress(), mediaAgents);
		}

		// construct sdp and sip messages and send to the callee
		sip = new SIP(callerId, callerAddr.getHostName(), calleeId, 
			      calleeAddr.getHostName(), subject, priority, sdp.length());
		sip.sendInvite(out, this);
		sdp.send(out, this);

		// update the current state
		currState = CALLING;
		log("current state --> CALLING");


	// now that INVITE is sent to the callee, spawn a thread to handle the 
	// connection with the callee. This will free up the dialog box to handle
	// other events, such as canceling the call, etc...

	Thread conn = new Thread() 
	{
		public void run() 
		{
		String line, code = "";
		while(currState != COMPLETED) {
		  try {
			// set timeout to 4-second. Upon timeout
			// the InterruptedIOException will occur and
			// INVITE will be sent again.
			socket.setSoTimeout(4000);

			// wait for response arrives
			line = in.readLine();

			// parse the first line of the response from the callee
			StringTokenizer st = new StringTokenizer(line);
			code = st.nextToken();code = st.nextToken();
			log("RECV> " + line);


			// based on the response code handle the rest of the
			// response message body accordingly
			String fwdAddr = null;
			while ((line = in.readLine()) != null) {
				log("RECV> " + line);
				if (line.startsWith("Location:")) {
					st = new StringTokenizer(line);
					String key = st.nextToken("@");
					fwdAddr = st.nextToken();
				}
				if (line.length() == 0)
					break;
			} 
			if (currState == INITIAL) {
				// caller's not supposed to recv msg in INITIAL state
				// log("out of sync - received message during INITIAL state");
				endSession();
				return;
			} else if (code.startsWith("1")) {
				currState = PROCEEDING;
				log("current state --> PROCEEDING");
				Toolkit.getDefaultToolkit().beep();
				Toolkit.getDefaultToolkit().beep();
			} else if (code.startsWith("302")) {
				sip.sendReq("ACK", out, win);
				endSession();	
				calleeAddr = InetAddress.getByName(fwdAddr);
				redirect(calleeAddr);
				sip.setCalleeAddr(fwdAddr);
				sip.sendInvite(out, win);
				sdp.send(out, win);
				currState = CALLING;
				log("current state --> CALLING");
			} else {
				if ((Integer.parseInt(code) == OK) && connection.equals("two-party"))
					readSdp();
				currState = COMPLETED;
				log("current state --> COMPLETED");
				sip.sendReq("ACK", out, win);
			} 
		  } catch (InterruptedIOException e) {
			log("repeating INVITE....");
			sip.sendInvite(out,win);
			sdp.send(out, win);
		  } catch (Exception e) {
			log("CallerDialog: IOException during read");
			e.printStackTrace();
		  }
		} // end of while

		switch(Integer.parseInt(code)) {
			case OK:
				startMedia();
				break;
			case DECLINE:
				log("The call has been declined by the callee!");
				log("End of session");
				endSession();
				break;
			case NOTAVAILABLE:
				log(calleeId + " is not available or does not exist on " + 
				 	    calleeAddr.getHostName());
				log("End of session");
				endSession();
				break;
			case MOVED:
				log(calleeId + " is not available or does not exist on " + 
				 	    calleeAddr.getHostName());
				log("End of session");
				endSession();
			default:
				log("Unknown state");
				log("The phone is IDLE");
				endSession();
		} // end of switch
	}
	}; // *********  end of thread  ********

	conn.start();

	} // **************   end of buttonCall_Action()   *******************



	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) {
			win.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();

			} else if (tok.equals("m"))
				mediaList.addElement(line);
		}
		// media = new String[mediaList.size()];
		// mediaList.copyInto(media);

	  } catch(IOException e) {
		win.log("IOException occured" );
		System.out.println("SIP.readRequest():"+e);
	  }
	} // end of readSDP()



	public void startMedia() {
		for (int i = 0;i < mediaAgents.length;  i++) {
			log("Starting media agent - " + mediaAgents[i].name());
			mediaAgents[i].start(conn_addr);
		}
		
	}

	public void endSession() {
		log("Ending the session");
	    	try {
		        sin.close();
		        sout.close();
		        in.close();
		        out.close();
		} catch (IOException e) { 
		    System.out.println("CallerDialog.invite():IOException");
		    e.printStackTrace();
		    return;
		}
	} // end of endSession()


	public void log(String msg) {
		textAreaState.append(msg+"\n");
		logfile.println(msg);
	}

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

	void redirect(InetAddress server)
	{
		// open socket to the server and get io handles
		try {
			socket = new Socket(server, SIPPORT);
			log("\n***********   Connected to host: "+server);
	    	} catch (ConnectException e) {
	        	log("CallerDialog.invite():" + e.getMessage());
	        	return;
   		} catch (IOException e) { 
			log("CallerDialog.redirect():" + e.getMessage());
			System.err.println(e);
			return;
		} 
		try {
			// get an output stream handle for the socket
			sout = socket.getOutputStream();
			out = new PrintWriter(sout, true);
 
			// get a input stream handle for the socket
			sin = socket.getInputStream();
			in = new BufferedReader(new InputStreamReader(sin));

		} catch (IOException e) { 
			log("CallerDialog.redirect():Exception");
			e.printStackTrace();
			endSession();
		}
	}


	// ****************   class attributes   **********************

	String callerId;          // caller user id
	String calleeId;          // callee user id
	InetAddress callerAddr;   // caller ip address
	InetAddress calleeAddr;   // callee ip address
	String subject = "";      // other call information
	String priority = "";
	String connection = "";
	String sess_name = "";
	String sess_info = "";
	String sess_addr = "";     // tells the callee to send the data to this address.
	                           // caller's address for unicast, or a multicast address
	String conn_addr;          // read from the callee's session description. 
                                   // caller's media data will be sent to this address.

	Socket socket;             // SIP/SDP channel between caller & callee
	InputStream sin;           // io streams and handles
	OutputStream sout;
	BufferedReader in;          
	PrintWriter out;

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

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

	CallerDialog win = this;   // Caller dialog box for outgoing call
	ConfDialog conf = null;    // dialog box to enter conference session information

	Media[] mediaAgents;       // Media agents

	boolean mediaStarted = false;   // flag used to avoid starting media 

	int currState = INITIAL;        // current state of the phone
	

	// states of the phone 
	final static int INITIAL = 0;    // initial  state
	final static int CALLING = 1;    // has sent INVITE
	final static int PROCEEDING = 2; // received informational response
	final static int COMPLETED = 3;  // received final response


	final static int SIPPORT = 5060; // default port number for SIP server
	final static int TRYING = 100;   // SIP response codes
	final static int RINGING = 180;
	final static int OK = 200;
	final static int DECLINE = 603;
	final static int NOTAVAILABLE = 604;
	final static int MOVED = 302;


} // end of CallerDialog
