// Carnegie Mellon University
//   Information Networking Institute and
//   School of Computer Science
//
// Master Thesis: A Monitoring Tool for Overlay Network
// By: TungFai Chan and Annie Cheng
//
// File: GraphManager.java
// Path: userInterface/componentManager
// Description: Managerment class for drawing graph on panel and interact with
//              database


package userInterfaces.componentManagers;

import eventbase.*;
import eventbase.event.*;
import eventbase.routing.*;
import userInterfaces.*;
import userInterfaces.graph.*;
import userInterfaces.images.*;

import java.awt.*;
import java.util.*;
import javax.swing.*;

public class GraphManager {

    // DNS suffix location  (x,y) = (0,0) indicates the lower left corner
    final String[] geoLocNode = {
	// east coast
	".cmu.edu", "650", "500",
	".gatech.edu", "700", "200",
	".virginia.edu", "800", "400",
	".mit.edu", "800", "900",
	".udel.edu", "800", "600",
	".unc.edu", "750", "300",
	".columbia.edu", "820", "700", 
	".ocallahan.org", "830", "720",
	"pc-ma.research.att.com", "810", "680",
	".umass.edu", "700", "850",
	".umd.edu", "780", "500",
	".watson.ibm.com", "820", "725",

	// west coast
	".berkeley.edu", "200", "500",
	".stanford,edu", "180", "450",
	".ucsb.edu", "200", "400",
	".ualberta.ca", "200", "800",
	".ucla.edu", "200", "300", 
	".washington.edu", "200", "700",
	".ucsd.edu", "200", "200", 

	// central
	".wisc.edu", "500", "600",
	".uiuc.edu", "500", "500",
	".umn.edu", "450", "800", 
	".indiana.edu", "550", "450", 
	".uky.edu", "550", "350",

	// europe
	".uk", "900", "900",
	".it", "950", "500",
	".ch", "920", "700", 
	".de", "940", "750",

	// asia
	".sg", "10", "100",
	".tw", "50", "300",
	".jp", "100", "800",
	".kr", "80", "600", 
	".apan.net", "80", "600", 
	".cn", "30", "500" 
    };

    final int MIN_DISTANCE_X = 100;
    final int MIN_DISTANCE_Y = 50;
    final int GRAPH_MARGIN = 25;

    JPanel paintPanel = null;

    Vector nodes = null;
    Vector links = null;

    MainScreen mainScreen = null;
    EventBaseAgent dbAgent = null;

    VisualElement selectedElement = null;

    public GraphManager(JPanel panel, MainScreen mScreen, EventBaseAgent db) {
        paintPanel = panel;
        mainScreen = mScreen;
        dbAgent = db;

        links = new Vector();
        nodes = new Vector();
    }


    public void updateVisualElements(EventTableEntry tableEntry) {

        switch (tableEntry.getEvent().getEventId()) {
            // Various Event Type actions
            case EventType.JOIN_GROUP:
                {
                    JoinGroupEvent event = (JoinGroupEvent)tableEntry.getEvent();
                    OverlayHost host = event.getSourceHost();
                    if (searchNode(host.getID(), true) == null)
                        nodes.addElement(new Node(newCoordination(host.getName()), 
						  host.getID(),
						  host.getName(), 
						  Color.black));

                }
                break;

            case EventType.ADD_LINK:
                {
                    AddLinkEvent event = (AddLinkEvent)tableEntry.getEvent();
                    Node src = searchNode(event.getSourceHost().getID(), true);
                    Node dest = searchNode(event.getTargetHost().getID(), true);

                    if (src == null) {
                        OverlayHost host = event.getSourceHost();
                        src = new Node(newCoordination(host.getName()), 
				       host.getID(),
                                       host.getName(), 
				       Color.black);
                        nodes.addElement(src);
                    }
                    if (dest == null) {
                        OverlayHost host = event.getTargetHost();
                        dest = new Node(newCoordination(host.getName()), 
					host.getID(),
                                        host.getName(), 
					Color.black);
                        nodes.addElement(dest);
                    }

                    Link link = searchLink(src.getNodeID(), dest.getNodeID());
                    if (link == null)
                        links.addElement(new Link(src, dest, 
						  Color.gray, Color.black,
                                                  Color.blue, Color.green));
                    else
                        link.setMutualAgreement(true);

                }
            break;

            case EventType.DROP_LINK:
                {
                    DropLinkEvent event = (DropLinkEvent)tableEntry.getEvent();
                    Link target = searchLink(event.getSourceHost().getID(),
                                             event.getTargetHost().getID());

                    if (target != null) {
                        if (target.getMutualAgreement())
                            target.setMutualAgreement(false);
                        else
                            links.remove(target);
                    }
                }
                break;

            case EventType.LEAVE_GROUP:
                {
                    Node target = searchNode(tableEntry.getEvent().getSourceHost().getID(), false);

                    if (target != null) {
                        Enumeration allLinks = searchLinks(target.getNodeID());
                        while (allLinks.hasMoreElements())
                            links.remove((Link)allLinks.nextElement());

                        target.setActivated(false);
                    }
                }
                break;
            case EventType.DETECT_DEAD_HOST:
                {
                    Node target = searchNode(tableEntry.getEvent().getTargetHost().getID(), false);

                    if (target != null) {
                        Enumeration allLinks = searchLinks(target.getNodeID());
                        while (allLinks.hasMoreElements())
                            links.remove((Link)allLinks.nextElement());

                        target.setActivated(false);
                    }
                }
                break;

        }

    }

    public void updateRoutingInfo(double time) {
        if (selectedElement != null &&
            selectedElement.getElementType() == VisualElement.NODE) {

            int nodeID = ((Node)selectedElement).getNodeID();
            clearAllRoutingLinks();
            Vector pairs = dbAgent.getSpanningTreeForDisplay(time, nodeID);
            for (Enumeration e = pairs.elements(); e.hasMoreElements();) {
                SpanningTreeTableEntry entry = (SpanningTreeTableEntry)e.nextElement();
                Link link = searchLink(entry.getSourceID(), entry.getTargetID());
                if (link != null)
                    link.setRoutingLink(true);
                else
                    mainScreen.printConsoleMessage("GraphManager", "updateRoutingInfo",
                                                   "Add routing link that doesn't exist");
            }
        }
    }

    public void clearAll() {
        links = new Vector();
        for (Enumeration e = nodes.elements(); e.hasMoreElements();)
            ((Node)e.nextElement()).setActivated(false);
    }

    public void drawGraph(Graphics g, int graphType) {

        for (Enumeration e = nodes.elements(); e.hasMoreElements();)
            ((Node)e.nextElement()).draw(g, graphType);

        for (Enumeration e = links.elements(); e.hasMoreElements();)
            ((Link)e.nextElement()).draw(g, graphType);
    }

    public void relocateElements() {
        Vector tmp = nodes;
        nodes = new Vector();

        for (Enumeration e = tmp.elements(); e.hasMoreElements();) {
            Node tmpNode = (Node)e.nextElement();
            tmpNode.setCoordination(newCoordination());  // not sure if I should change this to reflect geoLoc of nodes -- yhchu
            nodes.addElement(tmpNode);
        }

        for (Enumeration e = links.elements(); e.hasMoreElements();)
            ((Link)e.nextElement()).calculateLocations();

    }

    public VisualElement checkClickCoordination(int x, int y, int graphType) {
        boolean found = false;
        selectedElement = null;


        // Only one of visual elements can be selected at one time
        for (Enumeration e = links.elements(); e.hasMoreElements();)
            if (!found) {
                Link tmpLink = (Link)e.nextElement();
                if ( tmpLink.isOnThisLink(new Coordination(x, y))) {
                    found = true;
                    selectedElement = tmpLink;
                }
            }
            else
                ((Link)e.nextElement()).setSelected(false);


        for (Enumeration e = nodes.elements(); e.hasMoreElements();)
            if (!found) {
                Node tmpNode = (Node)e.nextElement();
                if ( tmpNode.isThisNode(new Coordination(x, y))) {
                    found = true;
                    selectedElement = tmpNode;
                }
            }
            else
                ((Node)e.nextElement()).setSelected(false);

        return selectedElement;

    }

    public int checkPressedCoordination(int x, int y) {
        for (Enumeration e = nodes.elements(); e.hasMoreElements();) {
            Node tmpNode = (Node)e.nextElement();
            if ( tmpNode.isInRange(new Coordination(x, y)))
                return tmpNode.getNodeID();
        }

        return -1;
    }

    public void setNewNodeCoordination(int id, Coordination coord) {
        Node node = searchNode(id, false);
        if (node != null)
            node.setCoordination(coord);

    }

    public void setSelectedNode(int id) {
        if (id == -1)
            return;

	selectedElement = null;

        for (Enumeration e = nodes.elements(); e.hasMoreElements();) {
            Node node = (Node)e.nextElement();
            if (node.getNodeID() == id && node.isActivated()) {
                node.setSelected(true);
                selectedElement = node;
            }
            else
                node.setSelected(false);
        }

        for (Enumeration e = links.elements(); e.hasMoreElements();)
            ((Link)e.nextElement()).setSelected(false);
    }


    private Link searchLink(int srcID, int destID) {
        for (Enumeration e = links.elements(); e.hasMoreElements();) {
            Link curr = (Link)e.nextElement();
            if ((curr.getSrcNode().getNodeID() == srcID &&
                 curr.getDestNode().getNodeID() == destID) ||
                (curr.getSrcNode().getNodeID() == destID &&
                 curr.getDestNode().getNodeID() == srcID))
                 return curr;
        }

        return null;
    }

    private Enumeration searchLinks(int srcID) {
        Vector vec = new Vector();
        for (Enumeration e = links.elements(); e.hasMoreElements();) {
            Link curr = (Link)e.nextElement();
            if (curr.getSrcNode().getNodeID() == srcID ||
                curr.getDestNode().getNodeID() == srcID)
                vec.addElement(curr);
        }

        return vec.elements();
    }

    private Node searchNode(int id, boolean activatedIfExist) {
        for (Enumeration e = nodes.elements(); e.hasMoreElements();) {
            Node node = (Node)e.nextElement();
            if (node.getNodeID() == id) {
                if (activatedIfExist)
                    node.setActivated(true);
                return node;
            }
        }

        return null;
    }

    private void clearAllRoutingLinks() {
        for (Enumeration e = links.elements(); e.hasMoreElements();)
            ((Link)e.nextElement()).setRoutingLink(false);
    }

    // returns a new coordinate for a host, using hints from geoLoc
    // -- yhchu
    private Coordination newCoordination(String hostName) {
        for (int i=0; i < geoLocNode.length; i+=3) {
	    String suffix = geoLocNode[i];
	    int geoX = Integer.parseInt(geoLocNode[i+1]);
	    int geoY = Integer.parseInt(geoLocNode[i+2]);

	    if (hostName.indexOf(suffix) == -1) {
		continue;
	    }
	    
	    // found a match
	    //
	    // give some initial twist so that the hosts are not aligned
	    // to same axis
	    double jitter = 0.04;  
	    int x, y;
	    int widthX = paintPanel.getWidth() - GRAPH_MARGIN * 2;
	    int widthY = paintPanel.getHeight() - GRAPH_MARGIN * 2;
	    
	    // in Java windows coordinates, (0,0) is upper left corner
	    do {
		x = (int)(widthX * (geoX / 1000.0));
		x += (int)((Math.random()-0.5) * jitter * widthX);
		if (x < 0) { x = 0;}
		if (x > widthX) { x = widthX;}
		x += GRAPH_MARGIN;
		
		// flip y coordinates
		y = (int)(widthY * (1.0-(geoY / 1000.0)));
		y += (int)((Math.random()-0.5) * jitter * widthY);
		if (y < 0) { y = 0;}
		if (y > widthY) { y = widthY;}
		y += GRAPH_MARGIN;
		
		jitter += 0.02;
	    } while (conflictWithExistNodes(x, y));

	    System.out.println("GeoCoord: " + hostName 
			       + " X: " + x + "/" + geoX + "/" + widthX
			       + " Y: " + y + "/" + geoY + "/" + widthY
			       + " jitter: " + jitter);
	    return (new Coordination(x, y));
	}

	System.out.println("GeoCoord: WRN: cannot find coordinate for " 
			   + hostName);
	
	// no match found, use default random coordination
	return newCoordination();
    }

    private Coordination newCoordination() {
        int randX, randY;

        do {
            randX = (int)(Math.random() * (paintPanel.getWidth() - GRAPH_MARGIN * 2) +
                          GRAPH_MARGIN);
            randY = (int)(Math.random() * (paintPanel.getHeight() - GRAPH_MARGIN * 2) +
                          GRAPH_MARGIN);
        } while (conflictWithExistNodes(randX, randY));

        return (new Coordination(randX, randY));

    }

    private boolean conflictWithExistNodes(int x, int y) {
        for (int i = 0; i < nodes.size(); i++) {
            Node tmpNode = (Node)nodes.elementAt(i);
            int dx = Math.abs(tmpNode.getCoordination().x - x);
            int dy = Math.abs(tmpNode.getCoordination().y - y);
            if (dx < MIN_DISTANCE_X && dy < MIN_DISTANCE_Y)
                return true;
        }

        return false;
    }



}
