<html>
<xmp>
/**
 * Title: GPS Simulator
 * Copyright: Copyright (c) 2002
 * Company:   University of Oregon Computer Science Dept.
 *
 * @author Jason Prideaux
 * @version 1.0
 *
 */

package controller;


import gui.*;
import gui.tray.*;
import util.*;
import agent.*;
import transport.*;
import transport.protocol.*;

import javax.swing.JFrame;
import java.awt.*;
import java.util.*;



/* ====================================================================== */
/** This class is the core of the simulator.  It initializes the interface
 *  components, holds the agents, and controls the sockets for sending and
 *  recieving data.
 *
 */
public class Controller implements SocketReceiverListener{


	/** The chart is the visual map. */
	private ChartComp chart;

	/** The main frame which displays the map ChartComp. */
  	private SimulatorFrame frame;

	/** An interface component for viewing agent stats, etc. */
	private TrayDialog tray;

	/** The agent that is being moved, or has center of screen. */
	private Agent current_agent;

	/** The list of all access points in the simulation. */
	private Vector points = new Vector();

	/** The list of all users in the simulation. */
	private Vector users = new Vector();

	/** Components that want to listen for when information changes in controller. */
	private Vector listeners = new Vector();




    /* ===================================================================== */
    /** This constructor initializes the controller by creating the interface
     *  components, so that the user can begin interacting with the simulator.
     *	It also starts the SocketReceiver for listening to incoming messages.
     *
     */
	public Controller(){

		chart = new ChartComp(this, false );
		chart.loadChart(&quot;uo_data.xml&quot;);

		frame = new SimulatorFrame(this, chart);
		tray = new TrayDialog(this, frame);

		try{ Thread.sleep(500); }catch(Exception e){}

		frame.setVisible(true);

		SocketReceiver sr = new SocketReceiver(this, 2323);
		sr.start();



	} //Constructor




    /* ===================================================================== */
    /** This method is for adding a new user into the simulator. The user is
     *  created based on the given information, and is added into the map, and
     *  all controller listeners are notified of the new addition.
     *
     *  @param  r   The range for the new user.
     *  @param  p   The port for the new user.
     *
     */
	public void addUser(int r, int p){

		Point pt = frame.getChartPane().getScrollPosition();
		Dimension d= frame.getChartPane().getViewportSize();

		User u = new User( new Point((int)(pt.getX()+(d.width)/2),(int)(pt.getY()+(d.height)/2)), p );

		current_agent = u;
		users.add( current_agent);

		current_agent.setRange(r);
		current_agent.setName( &quot;user&quot;+users.indexOf(current_agent) );


		int index = users.indexOf(u);


		updateAgentPosition( current_agent );

		fireControllerChange();


	} //method: addUser




    /* ===================================================================== */
    /** This method is for removing a user from the simulator. The actual
     *  user is given to the method, and the remove from the simulation, and
     *  all controller listeners are notified of the new removal.
     *
     *  @param  u   The User object to remove from the users list.
     *
     */
	public void removeUser(User u){

		users.remove(u);
		if( current_agent.getName().equals( u.getName()) &amp;&amp; users.size() &gt; 0){
			current_agent = (User)users.elementAt(0);
		}
		chart.repaint();
		fireControllerChange();


	} //method: removeUser





    /* ===================================================================== */
    /** This method is for adding a new access point into the simulator. The
     *  accesspoint is created based on the given information, and is added
     *  into the map, and all controller listeners are notified of the new
     *  addition.
     *
     *  @param  r   The range for the new Access Point
     *
     */
	public void addAccessPoint(int r ){

		Point pt = frame.getChartPane().getScrollPosition();
		Dimension d= frame.getChartPane().getViewportSize();

		AccessPoint u = new AccessPoint( &quot; &quot;, new Point((int)(pt.getX()+(d.width)/2),(int)(pt.getY()+(d.height)/2)), r );

		current_agent = u;
		points.add( u);

		u.setRange(r);
		u.setName( &quot;access&quot;+points.indexOf(u) );


		updateAgentPosition( u );

		fireControllerChange();


	} //method: addAccessPoint





    /* ===================================================================== */
    /** This method is for removing a access point from the simulator. The actual
     *  access point is given to the method, and the remove from the simulation, and
     *  all controller listeners are notified of the new removal.
     *
     *  @param  u   The Access Point object to remove from the points list.
     *
     */
	public void removeAccessPoint(AccessPoint u){

		points.remove(u);

		chart.repaint();
		fireControllerChange();

	} //method: removeAccessPoint





    /* ===================================================================== */
    /** This method updating an Agent's gps Position.  It will get the pixel
     *  location, and then turn this into a gps Position.
     *
     *  @param  u   The agent whose Position needs to be set by this method.
     *
     */
	public void updateAgentPosition(Agent u){

		// Get the location Point
		chart.updatePos(u.getLocation(), users, points);

		Point poi = u.getLocation();

		// Turn pixel into gps coordinates.
		Position chart_pos = chart.getChartData().transPos(poi);
		Position old = u.getPosition();

		//Retain some old Position information.
		chart_pos.setHeading( old.getHeading() );
		chart_pos.setTime( old.getTime() );


		u.setPosition( chart_pos );

	} //method: updateAgentPosition




    /* ===================================================================== */
    /** This method returns the Vector of all the current users in the simulator.
     *
     *  @return  Vector   All users in the simulation.
     *
     */
	public Vector getUsers(){

		return users;

	} //method: getUsers




    /* ===================================================================== */
    /** This method returns the Vector of all current access points in the
     *  simulator.
     *
     *  @return  Vector   All Access Points in the simulation.
     *
     */
	public Vector getPoints(){

		return points;

	} //method: getPoints




    /* ===================================================================== */
    /** This method returns the current agent in focus or being moved around
     *  in the simulator map.
     *
     *  @return  Agent   The current Agent being moved or center on screen.
     *
     */
	public Agent getCurrentAgent(){

		return current_agent;

	} //method: getCurrentAgent




    /* ===================================================================== */
    /** This method sets who the current is.
     *
     *  @param  a   The Agent to make the current agent.
     *
     */
	public void setCurrentAgent(Agent a){

		current_agent = a;

	} //method: setCurrentAgent





    /* ===================================================================== */
    /** This method is for centering the given agent on the map.  Also returns
     *  the agent object in case it is needed once centered.
     *
     *  @param  name   The name of Agent to find and center on screen.
     *  @return  Agent   The Agent object that was centered.
     *
     */
	public Agent centerAgent(String name){

		Agent a = null;

		// Find the user or access point.
		for( int i=0; i <users.size(); i++ ){
			a = (Agent)users.elementAt(i);
			if( name.indexOf(""+a.getName()) > -1 ){
				current_agent = a;
				fireControllerChange();
				frame.centerScreen(a.getLocation());
			}
		}

		for( int i=0; i <points.size(); i++ ){
			a = (Agent)points.elementAt(i);
			if( name.indexOf(""+a.getName()) > -1 ){
				current_agent = a;
				fireControllerChange();
				frame.centerScreen(a.getLocation());
			}

		}

		return a;

	} //method: centerAgent





    /* ===================================================================== */
    /** This method is called when a User's location has changed.  A NMEA
     *  Message is created and then sent to the User's corresponding iPAQ via
     *  a port number.
     *
     *  @param  u   The user who has changed location.
     *
     */
	public void locationChange(User u){

		Position p = u.getPosition();
		Message msg = new NMEAMessage(u.getPort(), 2323, p.getLatitude(), p.getLongitude(), p.getHeading(), p.getTime());

		SocketSender ss = new SocketSender(msg);
		ss.start();

	} //method: locationChange



    /* ===================================================================== */
    /** This method is called to send a message to all agents.
     *
     *  @param  msg   Message to send
     *
     */
	public void sendToAll(Sim2PMessage msg){

		User a = null;

		// Find the user or access point.
		for( int i=0; i <users.size(); i++ ){
			a = (User)users.elementAt(i);
			msg.setTo(a.getPort());

			SocketSender ss = new SocketSender(msg);
			ss.start();
		}


	} //method: sendToAll



    /* ===================================================================== */
    /** This method simply tests to see if the tray is visible, if not visible
     *  then it is assumed to be dead.
     *
     *  @return  boolean   True if tray is alive and visible, false otherwise.
     *
     */
	public boolean isTrayAlive(){

		try{
			if( tray.isVisible()){
				return true;
			}
		}catch(Exception e){
			return false;
		}

		return false;

	} //method: isTrayAlive




    /* ===================================================================== */
    /** This method creates a new TrayDialog for performing various functions
     *  in the simulator, and viewing information.
     *
     */
	public void makeTray(){

		tray = new TrayDialog(this, frame);
		fireControllerChange();

	} //method: makeTray




    /* ===================================================================== */
    /** This method returns the main JFrame for the iSIM  application.
     *
     *  @return  JFrame   The main JFrame for the simulation.
     *
     */
	public JFrame getFrame(){

		return frame;

	} //method: getFrame




    /* ===================================================================== */
    /** This method returns the ChartComp which holds the simulation map, and
     *  draws all the users.
     *
     *  @return  CharComp   The JComponent that is the map.
     *
     */
	public ChartComp getChartComp(){

    	return(chart);

  	} //method: getChartComp




    /* ===================================================================== */
    /** This method sets whether the wireless ranges should be displayed or
     *  not.
     *
     *  @param  ranges  True to show the wireless range on map, false otherwise.
     *
     */
	public void setRanges(boolean ranges){

		chart.setRanges(ranges);

	} //method: setRanges





    /* ===================================================================== */
    /** This method changes the current map/chart used in the simulation.
     *  The GUI Frame and ChartComp handle the details behind this.
     *
     *  @param  chartName   The new chart file name.
     *  @param  scale   The scale for the map image.
     *  @param  precision   The drawing position for lines on map.
     *
     */
  	public void changeChart(String chartName){


    	frame.changeChart(chartName);

  	} //method: changeChart




    /* ===================================================================== */
    /** This method is for reseting the data insdie the controller.  It removes
     *  all agents (users and access points).
     *
     */
	public void reset(){

		users = new Vector();
		points = new Vector();
		current_agent = null;
		chart.repaint();
		fireControllerChange();

	} //method: reset




    /* ===================================================================== */
    /** This method returns the pixel distance between two points.
     *
     *  @param  p1   The first point.
     *  @param  p2   The second point.
     *
     */
	private double diff(Point p1, Point p2){

		double x = p1.getX() - p2.getX();
		if( x < 0 )
			x = x * -1;

		double y = p1.getY() - p2.getY();
		if( y < 0 )
			y = y * -1;


		return Math.sqrt( x*x + y*y );

	} //method: diff



    /* ===================================================================== */
    /** This method is required by SocketReceiverListener and is called by the
     *  SocketReceiver object.  It delivers a message to us from some other
     *  client application wanting to send a message.
     *
     *  This method uses a multicasting approach and sends the message to all
     *  agents in range and up to a certain number of hops designated in the
     *  message itself.  It calls a Thread now to do the message passing, so
     *  that bandwidth delays can be simulated.
     *
     *  @param  msg   The incoming Message that the simulator has received.
     *
     */
	public void incomingMessage(Message msg){

		if( msg instanceof MulticastMessage ){

			System.out.println("Received Multicast message from an iPAQ");

			MessageSender ms = new MessageSender( (MulticastMessage)msg );
			ms.start();

		} //if


	} //method: incomingMessage






	/* ===================================================================== */
    /** This methods adds a listener to the list of listeners this controller
     *  knows about.
     *
     *  @param  listener
     *
     */
    public void addControllerListener(ControllerListener listener){

		listeners.add(listener);

    } //method: addControllerListener




	/* ===================================================================== */
    /** This method removes a listener from the list of listeners that
     *  this controller knows about.
     *
     *  @param  listener
     *
     */
    public void removeControllerListener(ControllerListener listener){

		listeners.remove(listener);

    } //method: removeControllerListener




	/* ===================================================================== */
    /** This method notifies all subscribed listeners that they must
     *  refresh themselves.
     */
    public void fireControllerChange(){

		ControllerListener[] informMe = new ControllerListener[listeners.size()];
		informMe = (ControllerListener[])listeners.toArray(informMe);

		for(int i = 0; i < informMe.length; i++){

			informMe[i].refresh();
		}

    } //method: fireControllerChange




    /* ===================================================================== */
    /** This class is used for Multicasting a message to all users in wireless
     *  range.  It adds in timing delays based on ratio of max range/distance
     *  to simulate bandwidth loss and delay due to wireless signal
     *  degradation.
     *
     */
	class MessageSender extends Thread{

		/** The message to send in this thread. */
		private MulticastMessage msg;


		/* ================================================================ */
		/** This Costructor preps the Thread for the given message to send.
		 *
		 *  @param  msg   The message to send to other users in range.
		 *
		 */
		public MessageSender(MulticastMessage msg){

			this.msg = msg;
		} //Constructor


		/* ================================================================ */
		/** This method excutes the thread.  It sends the message to all
		 *  Users in range, adds a delay, and sends to Users that be reached
		 *  in a number of hops specified by the message.
		 *
		 */
		public void run(){

			System.out.println("Processing a Multicast message...");

			int hops = msg.getHops();
			int from = msg.getFrom();

			double diff = 0;
			Agent a = null;
			Agent temp = null;
			Vector tosendto = new Vector();

			// Find the agent that sent the message.
			for( int i=0; i < users.size(); i++ ){
				temp = (Agent)users.elementAt(i);
				if( from == ((User)temp).getPort() ){
					a = temp;
				}
			}

			tosendto.add(a);

			System.out.println("   Sent by "+ ((User)a).getPort());

			//Now send message to all agents except sender.
			if( a != null ){

				// Send to all agents in one hop.
				for( int i=0; i < users.size(); i++ ){

					temp = (Agent)users.elementAt(i);
					diff = diff(a.getLocation(),temp.getLocation());

					System.out.println( "diff: "+diff);

					if( diff < (a.getRange()+temp.getRange()) && !(tosendto.contains(temp)) ){

						// Impose a delay based on the distance and range.
						try{
							Thread.sleep( (int)(2000*((a.getRange()+temp.getRange())/diff)) );
						}catch(Exception e){}

						msg.setTo(((User)temp).getPort());
						SocketSender ss = new SocketSender(msg);
						ss.start();

						tosendto.add(temp);
					}
				}
				for( int i=0; i < points.size(); i++ ){
					temp = (Agent)points.elementAt(i);
					diff = diff(a.getLocation(),temp.getLocation());

					if( diff < (a.getRange()+temp.getRange()) && !(tosendto.contains(temp)) ){
						tosendto.add(temp);
					}
				}


				// Now find those within hops.
				int h = 1;
				while( h < hops ){
					h++;

					// Impose a delay inbetween hops
					try{
						Thread.sleep(2000);
					}catch(Exception e){}

					for( int j=0; j < tosendto.size(); j++ ){

						a = (Agent)tosendto.elementAt(j);

						// Find all agents in one hop.
						for( int i=0; i < users.size(); i++ ){

							temp = (Agent)users.elementAt(i);
							diff = diff(a.getLocation(),temp.getLocation());

							if( !(tosendto.contains(temp)) && diff < (a.getRange()+temp.getRange()) ){

								// Impose a delay based on the distance and range.
								try{
									Thread.sleep( (int)(2000*((a.getRange()+temp.getRange())/diff)) );
								}catch(Exception e){}

								msg.setTo(((User)temp).getPort());
								SocketSender ss = new SocketSender(msg);
								ss.start();

								tosendto.add(temp);
							}
						}
						for( int i=0; i < points.size(); i++ ){

							diff = diff(a.getLocation(),temp.getLocation());
							temp = (Agent)points.elementAt(i);

							if( !(tosendto.contains(temp)) && diff < (a.getRange()+temp.getRange()) ){
								tosendto.add(temp);
							}
						}

					}
				} //while
			}

		} //method: run

	} //class: MessageSender


} //class: Controller
</xmp>
</html>

