import java.awt.*;
import java.io.*;
import java.util.*;
import java.net.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.event.*;
import java.awt.font.*;

//Note Refactor Later. Pull the Application Code from the GUI code..
/**
 *  The view window is a canvas in which viewObjects are drawn upon.
 */
class ViewWindow extends JPanel implements MouseListener,MouseMotionListener {
	/**
	 * The current Line being drawn
	 * */
	Line currentLine = null;
	/**
	 * Constructor, makes the view Window 
	 * */
	public ViewWindow() {
		addMouseListener(this);
		addMouseMotionListener(this);
	}
	/**
	 * The list of viewObjects 
	 * */
	LinkedList viewObjects = new LinkedList();
	/**
	 * This method will add "vo" ViewObject to the Window. It will also place the object somewhere in the window.
	 * */
	void addViewObject(ViewObject vo) {
		viewObjects.add(vo);
	}
	/**
	 * This method will remove "vo" ViewObject from the Window.
	 * */
	void removeViewObject(ViewObject vo) {
		viewObjects.remove(vo);
	}
	/**
	 * The preferred Width of the Window
	 * */
	int prefWidth = 0;
	/**
	 * The preferred Height of the Window
	 * */
	int prefHeight = 0;
	/**
	 * Paint Components on a graphical context
	 * */
	protected void paintComponent(Graphics g) {
		g.setColor(Color.WHITE);
		g.fillRect(0,0,getSize().width,getSize().height);
		ListIterator iter = viewObjects.listIterator();
		prefWidth = 0;
		prefHeight = 0;
		while (iter.hasNext()) {
			ViewObject curr = (ViewObject)(iter.next());
			curr.paintOn(g);
			int x = curr.getMaxX();
			int y = curr.getMaxY();
			if (x > prefWidth) {
				prefWidth = x;
			}
			if (y > prefHeight) {
				prefHeight = y;
			}
		}
		if (currentLine!=null) {
			g.setColor(Color.BLUE);
			g.drawLine(currentLine.x1,currentLine.y1,currentLine.x2,currentLine.y2);
		}
	}
	/**
	 * The current View object clicked on
	 * */
	ViewObject currentViewObj = null;
	/**
	 * Are we dragging a patch
	 * */
	boolean patching = false;
	/**
	 * is it an input port?
	 * */
	boolean inputPort = false;
	/**
	 * The patch start Xcoord
	 * */
	int patchX  = 0;
	/**
	 * The patch start Ycoord
	 * */
	int patchY = 0;
	/**
	 * the last Update 
	 * */
	int lastUpdate = 0;
	/**
	 * last time the check update happened 
	 * */
	long lastCheck = System.currentTimeMillis();
	/**
	 * how often to update
	 * */
	int updateEvery = 15000;
	/**
	 * mouse clicked handler
	 * deals with patching and disconnecting
	 * */
	public void mouseClicked(MouseEvent e) { // Invoked when the mouse button has been clicked (pressed and released) on a component.  
		int button = e.getButton();
		if (button != MouseEvent.BUTTON1) {
			//de-patch or disconnect
			LinkedList objectsAt = viewObjectsAt(e.getX(),e.getY());
			if (!objectsAt.isEmpty()) {
				ViewObject obj = (ViewObject)objectsAt.removeFirst();
				if (obj instanceof ConnectionBox) {
					disconnect((ConnectionBox)obj);
				} else if (obj instanceof PatchLine) {
					depatch((PatchLine)obj);
				}
			}
		}
	}
	/**
	 * Invoked when the mouse enters a component.  
	 * */
	public void mouseEntered(MouseEvent e)  { // 
	}
	/**
	 * Invoked when the mouse exits a component.  
	 * */
	public void mouseExited(MouseEvent e)  { 
	}
	/**
	 * Invoked when a mouse button has been pressed on a component.  
	 * */
	public void mousePressed(MouseEvent e) { // 
		LinkedList objectsAt = viewObjectsAt(e.getX(),e.getY());
		if (objectsAt.size() > 0) {
			//System.err.println("Clicked on one!\n");
			currentViewObj = (ViewObject) objectsAt.removeFirst();
			if (currentViewObj instanceof ConnectionBox) {
				ConnectionBox b = (ConnectionBox)currentViewObj;
				if (b.hasClickedText(e.getX(),e.getY())) {
					
				} else {
					patching = true;
					inputPort = b.hasClickedInputKnob(e.getX(),e.getY());
					patchX = e.getX();
					patchY = e.getY();
				}
			}
			
		}
	}
	/**
	 * Invoked when a mouse button has been released on a component.
	 * */
	public void mouseReleased(MouseEvent e) { // 
		//execute action?
		if (patching && currentViewObj instanceof ConnectionBox) {
			ConnectionBox viewCurr = (ConnectionBox)currentViewObj;
			LinkedList objectsAt = viewObjectsAt(e.getX(),e.getY());
			if (objectsAt.size() > 0) {
				ViewObject obj = null;
				while(!objectsAt.isEmpty()) {
					obj = (ViewObject) objectsAt.removeFirst();
					if (obj instanceof ConnectionBox) {
						ConnectionBox c = (ConnectionBox)obj;
						if (inputPort) { //looking for an output port!
							if (c.hasOutputKnob() && c.hasClickedOutputKnob(e.getX(),e.getY())) {
								//addPatch(c,viewCurr);		
								addPatch(c,viewCurr);		
							}
						} else { //looking for an input port
							if (c.hasInputKnob() && c.hasClickedInputKnob(e.getX(),e.getY())) {
								addPatch(viewCurr,c);		
								//addPatch(c,viewCurr);		
							}
						}
						break;
					}
				}
			}
			//setup patchLine
		}
		currentViewObj = null;
		patching = false;
		currentLine = null;
		repaint();
	}
	/**
	 * adds a patch between two connectionBoxes
	 * */
	void addPatch(ConnectionBox in,ConnectionBox out) {
		try {
			viewObjects.add(new PatchLine(in,out));
			NetworkHandler.networkHandler(this).patch(in.getConnection(),out.getConnection());
		} catch (IOException e) {
			handleError(e);
		}
	}
	/**
	 * Invoked when a mouse button is pressed on a component and then dragged.
	 * */
	public void mouseDragged(MouseEvent e) { // 
		if (currentViewObj!=null) {
			if ( currentViewObj.draggable() && !patching) {
				currentViewObj.dragTo(e.getX(),e.getY());
				repaint();
			} else if (patching) {
				if (currentLine==null) {
					currentLine = new Line();
				}
				currentLine.x1 = patchX; currentLine.y1 = patchY;
				currentLine.x2 = e.getX(); currentLine.y2 = e.getY();
				repaint();
			}
		}
	}
	/**
	 * Invoked when the mouse button has been moved on a component (with no buttons down).
	 * */
	public void mouseMoved(MouseEvent e)  { // 
		checkUpdate();
	}
	/**
	 * check for an update
	 * */
	synchronized void checkUpdate() {
		if (lastCheck + updateEvery < System.currentTimeMillis()) {
			lastCheck = System.currentTimeMillis();
			try {
				NetworkHandler.networkHandler(this).getLastUpdateTime(lastUpdate);
			} catch (IOException ioe) {
				handleError(ioe);
			}
		}
	}
	/**
	 * CallBack for Network Handler when the update is receieved
	 * */
	void checkUpdate(int update) {
		if (update!=lastUpdate) {
			updateView();
			lastUpdate = update;
			lastCheck = System.currentTimeMillis();
		}
	}
	/**
	 * Returns a list of view objects at coord x,y
	 * @param x the x coord
	 * @param y the y coord
	 * */
	LinkedList viewObjectsAt(int x,int y) {
		LinkedList outList = new LinkedList();
		ListIterator iter = viewObjects.listIterator();
		while (iter.hasNext()) {
			ViewObject curr = (ViewObject)(iter.next());
			if (curr.wasClicked(x,y)) {
				outList.add(curr);
			}
		}
		return outList;
	}
	/**
	 * unpatch the patchLine obj
	 * @param obj remove a patch between two ConnectionBoxes
	 * */
	void depatch(PatchLine obj) {
		try {
			viewObjects.remove(obj);	
			NetworkHandler.networkHandler(this).unPatch(obj.getInput().getConnection(),obj.getOutput().getConnection());
			repaint();
		} catch (IOException e) {
			handleError(e);
		}
	}
	/**
	 * disconnect and connectioNBox
	 * */
	void disconnect(ConnectionBox cb) {
		viewObjects.remove(cb);	
		ListIterator iter = viewObjects.listIterator();
		while (iter.hasNext()) {
			ViewObject curr = (ViewObject)(iter.next());
			if (curr instanceof PatchLine) {
				PatchLine p = (PatchLine)curr;
				if (p.getInput() == cb || p.getOutput() == cb) {
					iter.remove();
				}
			}
		}
		try {
			NetworkHandler.networkHandler(this).disconnect(cb.getConnection());
		} catch (IOException e) {
			handleError(e);
		}
		repaint();
	}
	/**
	 * the index of the last view
	 * */
	private int lastView = 0;
	/**
	 * ask the network handler to send a update message
	 * */
	void updateView() {
		try {
			NetworkHandler.networkHandler(this).getView();
		} catch (IOException e) {
			handleError(e);
		}
	}
	/**
	 *  update the view with an array of objects (generally from the networkHandler)
	 * */
	void updateView(Object [] objs) {
		//remove connections that no longer exist
		//System.err.println("Update View");
			HashMap map = new HashMap();
			HashMap noLonger = new HashMap();
			Iterator iter = viewObjects.iterator();
			while (iter.hasNext()) {
				Object o = iter.next();
				if (o instanceof ConnectionBox) {
					ConnectionBox b = (ConnectionBox)o;
					map.put(new Integer(b.hashCode()),b);
					noLonger.put(new Integer(b.hashCode()),b);
					//noLonger.add(b);
				} else if (o instanceof PatchLine) {
					PatchLine p = (PatchLine)o;
					map.put(new Integer(p.hashCode()),p);
					//noLonger.add(p);
					noLonger.put(new Integer(p.hashCode()),p);
				}
			}
			int width = getWidth()-20;
			int height = getHeight()-20;
			for (int i = 0 ; i < objs.length; i++) {
				if (objs[i] instanceof ConnectionBox) { //AWFUL
					ConnectionBox box = (ConnectionBox)objs[i];
					Connection conn = box.getConnection();
					if (noLonger.remove(new Integer(box.hashCode()))==null) {
						int y = (lastView*20)%height;
						int row = (int)(lastView*20/height);
						int x = 50*row;
						if (x > width) {
							x = x%width;
						}
						box.dragTo((int)((getWidth()-20)*Math.random()),(int)((getHeight()-20)*Math.random()));
						box.dragTo(x,y);
						lastView++;
						map.put(new Integer(box.hashCode()),box);
					}
				} 
			}
			for (int i = 0 ; i < objs.length; i++) {
				if (objs[i] instanceof PatchLine) {
					PatchLine patch = (PatchLine)objs[i];
					if (noLonger.remove(new Integer(patch.hashCode()))==null) {
						ConnectionBox from = (ConnectionBox)map.get(new Integer(patch.getInput().hashCode()));
						ConnectionBox to = (ConnectionBox)map.get(new Integer(patch.getOutput().hashCode()));
						if (!(from == null && to == null)) {
							patch = new PatchLine(from,to);
							map.put(new Integer(patch.hashCode()),patch);
						}
					}
				}
			}
			iter = map.values().iterator();
			LinkedList newObjs = new LinkedList();
			while (iter.hasNext()) {
				Object o = iter.next();
				if (noLonger.containsKey(new Integer(o.hashCode()))) {
					//it was removed
				} else {
					newObjs.add(o);
				}
			}
			viewObjects = newObjs;
			repaint();
	}
	/**
	 *  the menubar initialized by getMenuBar
	 * */
	JMenuBar menuBar = null;
	/**
	 * gets the menubar for the ViewWindow
	 * */
	JMenuBar getMenuBar() {
		if (menuBar != null) {
			return menuBar;
		}
		menuBar = new JMenuBar();
		JMenu menu = new JMenu("File");
		JMenuItem item = new JMenuItem("Connect & Update");
		JMenuItem sitem = new JMenuItem("Shutdown Server");
		JMenuItem exitItem = new JMenuItem("Exit");
		menu.add(item);
		menu.add(sitem);
		menu.add(exitItem);
		menuBar.add(menu);
		final ViewWindow window = this;
		item.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
					updateView();
			}
		});
		sitem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					NetworkHandler.networkHandler(window).shutDown();
				} catch (IOException ioe) {
					handleError(ioe);
				}
			}
		});
		exitItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		});
		return menuBar;
	}
	/**
	 * show an error dialog with message in exception e
	 * @param e the exception containing the message
	 * */
	void handleError(Exception e) {
		JOptionPane.showMessageDialog(new JFrame(), e.getMessage(),"Error", JOptionPane.ERROR_MESSAGE);
	}
	/**
	 * show an error dialog
	 * @param e the  message
	 * */
	void handleError(String e) {
		JOptionPane.showMessageDialog(new JFrame(), e,"Error", JOptionPane.ERROR_MESSAGE);
	}
	/**
	 * main program, accepts hostname and port, if none are available connect to default port 
	 * and default host.
	 * */
	static public void main(String [] args) {
		try {
		JFrame mainFrame = new JFrame("ViewWindow Demo");
		if (args.length > 0 && args[0].equals("-h")) {
			throw new Exception("Help");
		}
		if (args.length >= 2) {
			NetworkHandler.setCurrentHost(InetAddress.getByName(args[0]));
			NetworkHandler.setCurrentPort(Integer.parseInt(args[1]));
		}
		ViewWindow p = new ViewWindow();
		JScrollPane scroll = new JScrollPane(p);
		mainFrame.getContentPane().add(scroll);
		java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize(); 

		screenSize.setSize((int)(screenSize.getWidth()*.4),(int)(screenSize.getHeight()*.7));
		mainFrame.setJMenuBar(p.getMenuBar());
		mainFrame.setSize(screenSize);
		mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		mainFrame.setVisible(true);
		p.updateView();
		} catch (Exception e) {
			System.err.println(e);
			System.err.println("AUSS Configurator");
			System.err.println("Usage: java ViewWindow <host> <port>");
		}
	}
	/**
	 *  get the preffered size of the View Window (the largest size of all the viewObjects)
	 * */
	public Dimension getPreferredSize() {
		return new Dimension(prefWidth,prefHeight);
	}
}

