///////////////////////////////////////////////////////////////////////
//
//  Arvutiv6rkude kodut66 nr. 1
//  IP paketi liikumine l2bi lyyside
//
//  Risto Erik
//  020452
//  IAPB-21
//  dh@bsd.ee
// 
///////////////////////////////////////////////////////////////////////


import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.util.*;


public class Av extends Applet
{
	public static Font font, smFont;	// 10 pt, 8 pt
	public static Color gray, darkGray;	// 0xe0e0e0, 0xc0c0c0
	public static String statusMsg[];	// 3 rida teksti
	public static int statusId;			// rea number
	public static int W, H;				// appleti laius ja k6rgus
	public static int maxNodes;			// max v6rgu objektide arv (k.a. src ja dst)
	public static int fpos;				// XXX: kasutu vist
	public int last_x, last_y;			// viimane kursori asukoht lohistamisel
	public NetNode tmpNode;				// objekt, kust lohistama hakati
	public Button restartBtn, initBtn, nextBtn;	// nupud
	public static Av avObj;				// viit (vms) staatilistes meetodites mittestaatikale ligi p22semiseks
	public boolean theEnd, order_done;	// a.s.d.f.
	public boolean theEndError;
	public boolean kammkala;

	public NetNode hopNodes[][];
	public int hopNodeCount[];
	public int hopNodePtr[];
	public int hopCount;
	public int hopPtr;
	public Packet thePacket;
	public int packets;

	public int activeLinks[][][];
	public int activeLinkCount; 

	public void printf(String s) { System.out.print(s); }
	public void init()
	{
		Av.avObj = this;
		
		Av.font = new Font("Verdana", Font.PLAIN, 10);
		Av.smFont = new Font("Verdana", Font.PLAIN, 8);
		Av.gray = new Color(0xe0e0e0);
		Av.darkGray = new Color(0xc0c0c0);

		Av.statusMsg = new String[3];
		Av.statusMsg[0] = null;
		Av.statusMsg[1] = null;
		Av.statusMsg[2] = null;
		Av.statusId = 0;

		Av.W = bounds().width;		// deprecated
		Av.H = bounds().height;		// ...

		theEnd = false;
		theEndError = false;
		order_done = false;
		tmpNode = null;
		last_x = last_y = 0;

		hopCount = 0;
		hopPtr = -1;
		Av.fpos = 0;
		thePacket = null;
		packets = 0;
		kammkala = false;

		restartBtn = new Button("Restart");
		initBtn = new Button("Init Network");
		nextBtn = new Button("Next");
		this.add(restartBtn);
		this.add(initBtn);
		this.add(nextBtn);

		String arg = getParameter("maxnodes");
		if (arg != null) {
			try { Av.maxNodes = Integer.parseInt(arg); }
			catch (NumberFormatException e)	{ Av.maxNodes = 12; }
		} else Av.maxNodes = 12;
		
		activeLinks = new int[Av.maxNodes][2][2];
		activeLinkCount = 0;
		
		NetNode.init(this);
	}

	// aDraw* meetodid olid enne clear() meetodis
	public void paint(Graphics g) 
	{
		this.setBackground(Av.gray);
		aDrawGrid(g);
		aDrawLinks(g);
		aDrawNodes(g);
		aDrawStatus(g);
  	}


	public void aDrawStatus(Graphics g)
	{
		int s = 0;

		g.setColor(getBackground());
		g.fillRect(2, Av.H - 38, Av.W - 4, 36);
		g.setFont(Av.smFont);
		if (Av.statusMsg[0] != null) {
			g.setColor(Color.blue);
			g.drawString(Av.statusMsg[0], 4, Av.H - (++s * 10) + 4);
			if (Av.statusMsg[1] != null) {
				g.setColor(Color.black);
				g.drawString(Av.statusMsg[1], 4, Av.H - (++s * 10) + 4);
				if (Av.statusMsg[2] != null)
					g.drawString(Av.statusMsg[2], 4, Av.H - (++s * 10) + 4);
			}
		}
	}

	public void aDrawGrid(Graphics g)
	{
		g.setColor(getBackground());
		g.fillRect(0, 0, Av.W, Av.H);
		g.setColor(Av.darkGray);
		for (int y = 40; y < Av.H; y += 40) {
			for (int x = 40; x < Av.W; x += 40)
				g.drawLine(x, 0, x, Av.H);
			g.drawLine(0, y, Av.W, y);
		}
	}
	
	public void aDrawLinks(Graphics g)
	{
		for (int i = 0; i < NetNode.nodeCount; i++)
			(NetNode.nodes[i]).drawLinks(g);
		g.setColor(Color.yellow);
		for (int i = 0; i < activeLinkCount; i++) {
			g.drawLine(activeLinks[i][0][0], activeLinks[i][0][1], activeLinks[i][1][0], activeLinks[i][1][1]);
			g.drawLine(activeLinks[i][0][0]-1, activeLinks[i][0][1], activeLinks[i][1][0]-1, activeLinks[i][1][1]);
		}
	}

	public void aDrawNodes(Graphics g)
	{
		g.setFont(Av.font);
		for (int i = 0; i < NetNode.nodeCount; i++)
			(NetNode.nodes[i]).drawNode(g);
	}

	public void showShortestPath()
	{
		NetNode n = NetNode.findNode("DST");
		int i = 0;
		NetNode src, dst;
		Graphics g = getGraphics();
		String str = null;

		if (kammkala) return;
		kammkala = true;

		activeLinkCount = 0;
		if (n.packet.nodeCount < 1) {
			Av.status("[BUG] MINGI ILGELT VASTIK BUG ON V6RGUS!");
			return;
		}
		// printf("Shortest path:\n");

		g.setColor(Color.yellow);
		src = n.packet.nodes[0];
		str = "Lyhim tee: SRC -> ";
		for (i = 1; i < n.packet.nodeCount; i++) {
			dst = n.packet.nodes[i];
			// printf("> "+ (i+1) +": "+ src.getName()+" -> "+ dst.getName() +"\n");
			str += dst.getName() +" -> ";
			activeLinks[i-1][0][0] = src.getRealX();
			activeLinks[i-1][0][1] = src.getRealY();
			activeLinks[i-1][1][0] = dst.getRealX();
			activeLinks[i-1][1][1] = dst.getRealY();
			// g.drawLine(src.getRealX(), src.getRealY(), dst.getRealX(), dst.getRealY());
			// g.drawLine(src.getRealX(), src.getRealY()-1, dst.getRealX(), dst.getRealY()-1);
			src = n.packet.nodes[i];
		}
		dst = n;
		str += " DST  ("+ (i - 1);
		if ((i - 1) > 1) str += " hypet)";
		else str += " hype)";
		Av.status(str);
		activeLinks[i-1][0][0] = src.getRealX();
		activeLinks[i-1][0][1] = src.getRealY();
		activeLinks[i-1][1][0] = dst.getRealX();
		activeLinks[i-1][1][1] = dst.getRealY();
		activeLinkCount = i;
		// printf("> "+ (i+1) +": "+ src.getName()+" -> "+ dst.getName() +"\n");
		// g.drawLine(src.getRealX(), src.getRealY(), dst.getRealX(), dst.getRealY());
		// g.drawLine(src.getRealX(), src.getRealY()-1, dst.getRealX(), dst.getRealY()-1);

		// printf("Total packets sent: "+ packets +"+, useful packets: "+ i +", useless: "+ (packets - i) +"+\n");
		str = "Saadeti vahemalt "+ packets +" paketti, neist kasulikke oli "+ i +" ja kasutuid";
		if ((packets - i - 1) > 1) str += " v2hemalt "+ (packets - i - 1);
		else str += " 0";
		Av.status(str);
		// printf("\n");
		paint(g);
	}

	public static void status(String msg)
	{
		if (msg == null) return;
		if (Av.statusMsg[0] != null) {
			if (Av.statusMsg[1] != null)
				Av.statusMsg[2] = Av.statusMsg[1];
			Av.statusMsg[1] = Av.statusMsg[0];
		}
		Av.statusMsg[0] = "("+ Av.statusId +") "+ msg;
		Av.statusId++;
		Av.avObj.paint(Av.avObj.getGraphics());
	}

	public void progress(String msg)
	{
//		Graphics g = getGraphics();
//		FontMetrics fm;
//		int h, w;
//		
//		if (msg == null) return;
//		g.setColor(Color.black);
//		g.setFont(Av.smFont);
//		fm = g.getFontMetrics();
//		w = fm.stringWidth(msg);
//		h = fm.getHeight();
//		g.setColor(Av.gray);
//		g.fillRect(2, ((Av.fpos) * h) + 2, w + 2, h);
//		g.setColor(Color.black);
//		g.drawString(msg, 2, ((Av.fpos + 1) * h) + 2);
//		Av.fpos++; 
//		paint(getGraphics());
	}

	public boolean action(Event e, Object obj)
	{
		if (e.target == restartBtn) doRestart(); 
		else if (e.target == nextBtn) doNext();
		else if (e.target == initBtn) doInit();
		else return false;
		return true;
	}

	public boolean mouseDrag(Event e, int x, int y)
	{
		Graphics g = getGraphics();

		if (order_done) return false;

		g.setColor(Av.darkGray);
		g.drawLine(x, y, last_x, last_y);
		last_x = x;
		last_y = y;

		return true;
	}

	public boolean mouseDown(Event e, int x, int y)
	{
		Graphics g = getGraphics();
		int xc, yc;
		NetNode n;

		if (order_done) return false;

		last_x = x;
		last_y = y;
		xc = x / 40;
		yc = y / 40;
		if ((x % 40) > 20) xc++;
		if ((y % 40) > 20) yc++;

		n = NetNode.findNode(xc, yc);
		if (n == null) {
			n = new NetNode(NetNode.NetNodeId++, NetNode.GW, xc, yc);
			NetNode.addNode(n);
			tmpNode = null;
			paint(g);
		} else if (tmpNode == null) tmpNode = n;
		
		return true;
	}

	public boolean mouseUp(Event e, int x, int y)
	{
		Graphics g = getGraphics();
		int xc, yc;
		NetNode n;

		if (order_done) return false;
		last_x = x;
		last_y = y;
		xc = x / 40;
		yc = y / 40;
		if ((x % 40) > 20) xc++;
		if ((y % 40) > 20) yc++;

		n = NetNode.findNode(xc, yc);
		if (n != null && tmpNode != null) {
			if (NetNode.connectNodes(tmpNode, n))
				status("Connected node "+ tmpNode.getName() +" with "+ n.getName());
			tmpNode = null;
			paint(g);
			return true;
		}
		
		paint(g);
		return false;
	}

	//
	//
	//
	//
	//

	public void doRestart()
	{
		NetNode.clean();
		NetNode.init(this);
		for (int i = 0; i < 3; i++)
			Av.statusMsg[i] = null;
		Av.statusId = 0;
		tmpNode = null;
		Av.fpos = 0;
		last_x = last_y = 0;
		theEnd = false;
		theEndError = false;
		order_done = false;
		activeLinkCount = 0;
		thePacket = null;
		packets = 0;
		kammkala = false;

		paint(getGraphics());
		Av.status("[restarted]");
	}

	public void doNext()
	{
		Graphics g = getGraphics();
		NetNode nodes[] = new NetNode[NetNode.nodeCount];
		NetNode n;
		int count = 0, i;
		
		while (true) {
			if (theEnd) {
				if (!theEndError)
					showShortestPath();
				return;
			}

			if (hopNodePtr[hopPtr] >= hopNodeCount[hopPtr])
				hopPtr++;
		
			if (hopPtr >= hopCount) {
				Av.status("V6rk on vigane.. SRC pole DST'ga yhendatud!");
				theEnd = true;
				theEndError = true;
				return;
			}

			paint(g);
			n = hopNodes[hopPtr][(hopNodePtr[hopPtr])];
			if (n.startFlood(g, n.packet)) {
				(hopNodePtr[hopPtr])++;
				return;
			}
			(hopNodePtr[hopPtr])++;
		}
	}

	public void doInit() 
	{
		NetNode n;

		if (!order_done) {
			travelOrder();
			order_done = true;
			for (int i = 0; i < hopCount; i++)
				hopNodePtr[i] = 0;
			hopPtr = 0;
			n = NetNode.findNode("SRC");
			Av.status("Created packet for SRC");
			n.packet = new Packet(this);
		}
	}

	public void travelOrder()
	{
		NetNode n;
		int i, count;

		n = NetNode.findNode("SRC");

		n.hops = 0;
		for (i = 0; i < n.linkCount; i++) (n.links[i]).hops = 1;
		for (i = 0; i < n.linkCount; i++) order(n.links[i], 2);

		Av.fpos = 0;
		hopCount = 0;
		hopPtr = 0;

		hopNodes = new NetNode[maxNodes+1][maxNodes+1];
		hopNodeCount = new int[maxNodes+1];
		hopNodePtr = new int[maxNodes+1];
		
		while (true) {
			System.out.print("maxNodes = "+ maxNodes +" hopCount = "+ hopCount +"\n");
			hopNodeCount[hopCount] = 0;
			count = 0;
			for (i = 0; i < NetNode.nodeCount; i++) {
				n = NetNode.nodes[i];
				if (n.hops == hopCount) {
					hopNodes[hopCount][count++] = n;
					progress(n.getName() +" hops: "+ n.hops);
				}
			}
			hopNodeCount[hopCount] = count;
			if (count == 0) break;
			hopCount++;
		}
		Av.status("travelOrder() done. max hops = "+ hopCount);
		Av.fpos = 0;
		activeLinkCount = 0;
	}

	public void order(NetNode n, int hops)
	{
		NetNode nodes[] = new NetNode[maxNodes];
		int count = 0, i;
		
		for (i = 0; i < n.linkCount; i++) {
			if ((n.links[i]).hops < 0 || (n.links[i]).hops > hops) {
				(n.links[i]).hops = hops;
				nodes[count++] = (n.links[i]);
			}
		}

		for (i = 0; i < count; i++)
			order((nodes[i]), hops + 1);
	}
}



//
//
//
//
//

class NetNode 
{
	public static final int SRC = 0;
	public static final int GW = 1;
	public static final int DST = 2;
	public static int nodeCount;
	public static NetNode nodes[];
	public static Av avObj;
	public static int NetNodeId;
	//
	private int id;
	private int xCoord;
	private int yCoord;
	private int type;
	private Color color;
	private String name;
	//
	public NetNode links[];
	public int linkCount;
	public boolean flooded, dont_flood, ready;
	public int floodedBy[];
	public int floodedByCount;
	public int hops;
	public Av av;
	public Packet packet;

	public NetNode(int nId, int nType, int x, int y)
	{
		av = NetNode.avObj;
		id = nId;
		type = nType;
		xCoord = x;
		yCoord = y;
		hops = -1;
		switch (type) {
			case NetNode.SRC:
				name = new String("SRC");
				color = Color.blue;
				break;
			case NetNode.GW:
				name = new String("GW"+ id);
				color = Color.green;
				break;
			case NetNode.DST:
				name = new String("DST");
				color = Color.red;
				break;
			default: System.exit(1); break;
		}
		links = new NetNode[av.maxNodes];
		linkCount = 0;
		flooded = false;
		dont_flood = false;
		floodedBy = new int[av.maxNodes];
		floodedByCount = 0;
		av.status("Created node with id="+ id +" name="+ name);
	}
	
	public static void init(Av obj)
	{
		NetNode n;

		NetNode.avObj = obj;
		NetNode.nodeCount = 0;
		NetNode.nodes = new NetNode[obj.maxNodes+2];
		NetNode.NetNodeId = 0;
		
		n = new NetNode(NetNode.NetNodeId++, NetNode.SRC, 1, (obj.H / 40) / 2);
		n.flooded = true;
		NetNode.addNode(n);
		
		n = new NetNode(obj.maxNodes + 1, NetNode.DST, (obj.W / 40) - 1, (obj.H / 40) / 2);
		n.dont_flood = true;
		NetNode.addNode(n);
	}

	public static void clean()
	{
		for (int i = 0; i < NetNode.avObj.maxNodes; i++)
			NetNode.nodes[i] = null;
		NetNode.nodeCount = 0;
		NetNode.NetNodeId = 0;
	}

	public static void addNode(NetNode n)
	{
		if (NetNode.nodeCount >= NetNode.avObj.maxNodes) {
			NetNode.avObj.status("Max ("+ NetNode.avObj.maxNodes +") nodes already created");
			return;
		}
		for (int i = 0; i < NetNode.nodeCount; i++) {
			if ((NetNode.nodes[i]).getId() == n.getId()) {
				NetNode.avObj.status("Node with id "+ n.getId() +" already created.");
				return;
			}
		}
		NetNode.nodes[NetNode.nodeCount] = n;
		NetNode.nodeCount++;
	}

	public static NetNode findNode(int id)
	{
		for (int i = 0; i < NetNode.nodeCount; i++) {
			if ((NetNode.nodes[i]).getId() == id)
				return NetNode.nodes[i];
		}
		return null;
	}
	
	public static NetNode findNode(int xc, int yc)
	{
		NetNode n;

		for (int i = 0; i < NetNode.nodeCount; i++) {
			n = NetNode.nodes[i];
			if (n.getX() == xc && n.getY() == yc) return n;
		}
		
		return null;
	}

	public static NetNode findNode(String str)
	{
		for (int i = 0; i < NetNode.nodeCount; i++) {
			if ((NetNode.nodes[i]).getName().equals(str))
				return NetNode.nodes[i];
		}

		return null;
	}
	
	public static boolean connectNodes(String src, String dst)
	{
		NetNode srcNode, dstNode;

		srcNode = findNode(src);
		dstNode = findNode(dst);
		return NetNode.connectNodes(srcNode, dstNode);
	}

	public static boolean connectNodes(NetNode src, NetNode dst)
	{
		if (src == null || dst == null) {
			NetNode.avObj.status("[BUG] src == null || dst == null");
			return false;
		}
		if (src.getId() == dst.getId()) {
			NetNode.avObj.status("Can't connect node "+ src.getName() +" with itself");
			return false;
		}

		if (!src.connectTo(dst) || !dst.connectTo(src))
			return false;
		return true;
	}

    public boolean startFlood(Graphics g, Packet p)
	// public boolean startFlood(Graphics g)
	{
		NetNode n;
		int count = 0;
		String str = null, by = null;
		boolean asdf = false;

		av.printf("FLOODER: "+ name +" :: flooded="+ flooded +" floodedByCount="+ floodedByCount +"\n");

		if (name.equals("DST")) {
			av.progress(name +": THE END");
			// av.status("THE END");
			av.theEnd = true;
			return false;
		}
		
		if (dont_flood) {
			av.progress(name +" won't flood anymore");
			return false;
		}

		if (id == 0) {
			av.progress(name +": START");
		}
		       
		//		else if (linkCount <= 1) {
		//		    // dead end. purge packet
		//		    p.purge();
		//		    return false;
		//		}

		if (!flooded) {
			av.progress(name +": not ready to flood");
			return false;
		}

		str = "[aeg = "+ av.hopPtr +"] "+ getName() +" floodib: ";
		dont_flood = true;

		by = null;
		for (int i = 0; i < linkCount; i++) {
			n = links[i];
			asdf = false;
			for (int j = 0; j < floodedByCount; j++) {
				if (n.getId() == floodedBy[j]) {
					asdf = true;
					if (by == null) {
						NetNode node = NetNode.findNode(floodedBy[0]);
						by = node.getName();
						// av.progress(name +":X: I was flooded by "+ n.getName());
						// Av.status(name +" was already flooded by "+ by +" with this packet");
					}
					av.printf(name +" was already flooded by "+ by +" with this packet\n");			
					break;
				}
			}
			if (asdf) continue;
			av.packets++;

			// av.progress(name +": flooding "+ n.getName());
			g.setColor(Color.yellow);
			g.drawLine(getRealX(), getRealY(), n.getRealX(), n.getRealY());
			g.drawLine(getRealX()-1, getRealY(), n.getRealX()-1, n.getRealY());
			av.activeLinks[count][0][0] = getRealX();
			av.activeLinks[count][0][1] = getRealY();
			av.activeLinks[count][1][0] = n.getRealX();
			av.activeLinks[count][1][1] = n.getRealY();
			n.flooded = true;
			n.floodedBy[n.floodedByCount++] = id;
			if (n.packet == null || n.packet.nodeCount > p.nodeCount + 1) {
				n.packet = p.copy();
				n.packet.addNode(this);
				// av.printf("["+ n.getName() +"] Copying packet: count "+ (n.packet.nodeCount - 1) +" vs "+ p.nodeCount +"\n");
			} else {
				// av.printf("["+ n.getName() +"] NOT copying packet: count "+ (n.packet.nodeCount - 1) +" vs "+ p.nodeCount +"\n");
			}
			if (count == 0) str += "{ "+ n.getName();
			else str += " "+ n.getName();
			count++;
		}
		if (count > 0) str += " }";
		if (by != null) str += ", "+ name +" ise sai paketi "+ by +" poolt";
		if (count == 0) return false;
		av.activeLinkCount = count;
		av.paint(g);
		Av.status(str);
		return true;
	}

	public void drawNode(Graphics g)
	{
		int x = getRealX(), y = getRealY();

		g.setColor(color);
		g.fillOval(x - 4, y - 4, 8, 8);
		g.setColor(Color.black);
		g.drawString(name, x - 10, y - 6);
	}

	public void drawLinks(Graphics g)
	{
		int x = getRealX(), y = getRealY();

		g.setColor(Color.black);
		for (int i = 0; i < linkCount; i++)
			g.drawLine(x, y, (links[i]).getRealX(), (links[i]).getRealY());
	}

	public boolean connectTo(NetNode n)
	{
		if (n.getId() == id) {
			av.status("Can't connect node "+ name +" with itself");
			return false;
		}

		if (linkCount >= av.maxNodes) {
			av.status("Max ("+ av.maxNodes +") link count reached");
			return false;
		}

		for (int i = 0; i < linkCount; i++) {
			if ((links[i]).getId() == n.getId()) {
				av.status("Node "+ n.getName() +" already connected with "+ name);
				return false;
			}
		}
		links[linkCount] = n;
		linkCount++;
		return true;
	}

	public int getId() { return id; }
	public int getX() { return xCoord; }
	public int getY() { return yCoord; }
	public int getRealX() { return xCoord * 40; }
	public int getRealY() { return yCoord * 40; }
	public int getType() { return type; }
	public String getName() { return name; }
}



class Packet
{
    public NetNode nodes[];
    public int nodeCount;
    public Av av;

    // create new Packet 
    public Packet(Av obj) 
    {
		av = obj;
		nodes = new NetNode[obj.maxNodes];
		nodeCount = 0;
    }

    // duplicate packet (p.clone() ?)
    public Packet copy()
    {
		Packet p = new Packet(av);
	
		for (int i = 0; i < nodeCount; i++)
			p.nodes[i] = nodes[i];
		p.nodeCount = nodeCount;

		return p;
    }

    public boolean addNode(NetNode n)
    {
		if (nodeCount >= av.maxNodes) {
			Av.status("[BUG] too many nodes in path!");
			return false;
		}
		for (int i = 0; i < nodeCount; i++) {
			if (nodes[i].getId() == n.getId()) {
				Av.status("Node "+ n.getName() +" already in path!");
				return false;
			}
		}
		nodes[nodeCount++] = n;
		return true;
    }
}
