import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.imageio.*;
import java.awt.geom.*;
import javax.swing.*;
import java.awt.image.*;

class AnimCreator
{
	BufferedImage imgBuf;
	Graphics2D g2D;
    int width = 640;
    int height = 480;
	static Random r;
	
	AnimCreator(int width, int height)
	{
		this.width=width;
		this.height=height;
		r=new Random(10236);
        // Create a buffered image in which to draw
        imgBuf= new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		g2D=imgBuf.createGraphics();
		g2D.setBackground(Color.white);
		g2D.setColor(Color.black);
	}
	void saveImage(String fname)
	{
	    try { // fuck transparency
	        // Save as JPEG
	        File file = new File(fname);
	        ImageIO.write(imgBuf, "jpg", file);
	    } catch (IOException e) {
	    }
    }
	void saveImage(String fbase,int i)
	{
		String str=Integer.toString(i);
		String pad="";
		for (int j=0;j<5-str.length();j++)
			pad="0"+pad;
		saveImage(fbase+pad+str+".jpg");
	}
	void saveImage(Image img,String fbase, int i)
	{
		g2D.drawImage(img, 0, 0, null);
		saveImage(fbase,i);
	}
	void fillFrame()
	{
		g2D.clearRect(0,0,width,height);
	}
	void saveAndShow(int fidx)
	{
		fillFrame();
		saveImage("output",fidx);
	}
}

class ImageLoader
{
	static BufferedImage loadImage(String fname, double scale)
	{
	    // load image from INFILE
	    Image image = Toolkit.getDefaultToolkit().getImage(fname);
	    MediaTracker mediaTracker = new MediaTracker(new Container());
	    mediaTracker.addImage(image, 0);
	    try{
	    mediaTracker.waitForID(0);
	    }catch(InterruptedException e)
	    {
	    	return null;
	    }
	    // determine thumbnail size from WIDTH and HEIGHT
	    int imageWidth = image.getWidth(null);
	    int imageHeight = image.getHeight(null);
	    int thumbWidth = (int)(imageWidth*scale);
	    int thumbHeight = (int)(imageHeight*scale);
	    // draw original image to thumbnail image object and
	    // scale it to the new size on-the-fly
	    BufferedImage thumbImage = new BufferedImage(thumbWidth, 
	      thumbHeight, BufferedImage.TYPE_INT_ARGB);
	    Graphics2D graphics2D = thumbImage.createGraphics();
	    graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
	      RenderingHints.VALUE_INTERPOLATION_BILINEAR);
	    graphics2D.drawImage(image, 0, 0, thumbWidth, thumbHeight, null);
	    // save thumbnail image to OUTFILE
	    return thumbImage;
	}
}

//id,sequence number,type(0=start, 1=end detection),sensor_time (jiffies: 1 sec=56 jiffies),basestation_pctimestamp (milliseconds)
class EventRecord {
	public int id;

	public int sequenceNumber;

	public int type;

	public long sensor_time;

	public long pctimestamp;

	static EventRecord Parse(String str) {
		EventRecord e = new EventRecord();
		StringTokenizer strTok;
		int type = getEventRecordType(str);
		if (type == 0)
			strTok = new StringTokenizer(str, ",");
		else
			strTok = new StringTokenizer(str, " ");
		e.id = Integer.parseInt(strTok.nextToken());
		e.sequenceNumber = Integer.parseInt(strTok.nextToken());
		e.type = Integer.parseInt(strTok.nextToken());
		e.sensor_time = Long.parseLong(strTok.nextToken());
		if (type == 1)
			strTok.nextToken(); // in this type there is a redundant line
		e.pctimestamp = Long.parseLong(strTok.nextToken());
		return e;
	}

	static int getEventRecordType(String str) {
		int type = 0;
		if (str.indexOf(",") < 0)
			type = 1;
		return type;
	}

	public String toString() {
		StringBuffer b = new StringBuffer();
		b.append(id);
		b.append(",");
		b.append(sequenceNumber);
		b.append(",");
		b.append(type);
		b.append(",");
		b.append(sensor_time);
		b.append(",");
		b.append(pctimestamp);
		return b.toString();
	}
}

class SimPanel extends Panel 
{
	static final long serialVersionUID=63631; 
	BufferedImage salmonImage;
	Image img;
	Graphics g;
	AnimCreator anim;
	SimPanel()
	{
		salmonImage=ImageLoader.loadImage("salmon.png", 0.3);
		img=null;
		anim=null;
	}
	
	public void paint(Graphics graph) {
		if (img==null)
		{
			img=createImage(this.getWidth(),this.getHeight());
			g=img.getGraphics();
			anim=new AnimCreator(this.getWidth(),this.getHeight());
		}
		g.clearRect(0,0,this.getWidth(),this.getHeight());
		// just place all event data on the display with circles
		SimDisplay par = (SimDisplay) getParent();
		SimKernel krn = par.kernel;
		// 7 lines with 15 sensors in each, row major format
		for (int r = 0; r < krn.nRows; r++)
			for (int c = 0; c < SimKernel.RowSize; c++) {
				Color col;
				if (krn.nodeState[r][c] == 0)
					col = Color.red;
				else
					col = Color.green;
				g.setColor(col);
				g.fillOval(c * 50 + 25, r * 50 + 25, 40, 40);
				g.setColor(Color.black);
				g.drawOval(c * 50 + 25, r * 50 + 25, 40, 40);
			}
		paintTree(g, krn.treeRoot);
		paintRobot(g, krn.theFish);
		graph.drawImage(img, 0, 0,null);
		if (salmonSim.sd.ticker.running)
		{
			salmonSim.sd.sp.anim.saveImage(salmonSim.sd.sp.img, "solmonAnim", salmonSim.sd.ticker.cframe);
		}
	}

	void paintTree(Graphics g, SimTreeLeaf node) {
		int x = node.x;
		int y = node.y;
		for (int i = 0; i < node.children.length; i++) {
			int cx, cy;
			cx = node.children[i].x;
			cy = node.children[i].y;
			g.setColor(Color.black);
			g.drawLine(x, y, cx, cy);
		}
		for (int i = 0; i < node.children.length; i++) {
			paintTree(g, node.children[i]);
		}
		int level = node.level;
		g.setColor(Color.blue);
		g.fillOval(x - 10 + level / 2, y - 10 + level / 2, 20 - level,
				20 - level);
		g.setColor(Color.black);
		g.drawOval(x - 10 + level / 2, y - 10 + level / 2, 20 - level,
				20 - level);
	}

	void paintRobot(Graphics g, Salmon fish) {
		/*
		g.setColor(Color.yellow);
		g.fillRect((int) (fish.x - 10), (int) fish.y - 10, 20, 20);
		g.setColor(Color.black);
		g.drawRect((int) fish.x - 10, (int) fish.y - 10, 20, 20);*/
		g.drawImage(salmonImage,(int)fish.x-salmonImage.getWidth()/2,(int)fish.y-salmonImage.getHeight()/2,null);
	}
}

class DistanceMatrix {
	Vector nodes;

	int distance[][];

	DistanceMatrix() {
		nodes = new Vector();
		distance = null;
	}

	void fillList(SimTreeLeaf n) {
		nodes.add((Object)n);
		for (int i = 0; i < n.children.length; i++) {
			fillList(n.children[i]);
		}
	}

	int childlevel(SimTreeLeaf ancestor, SimTreeLeaf descendant) {
		if (ancestor == descendant)
			return 0;
		if (ancestor.children.length == 0)
			return -1; // not a children
		for (int i = 0; i < ancestor.children.length; i++) {
			int res = childlevel(ancestor.children[i], descendant);
			if (res >= 0)
				return res + 1;
		}
		return -1; // not a grand children
	}

	void calculateDistances() {
		distance = new int[nodes.size()][nodes.size()];
		for (int i = 0; i < nodes.size(); i++) {
			for (int j = 0; j <= i; j++) {
				int childij, childji;
				childij = childlevel((SimTreeLeaf) (nodes.get(i)),
						(SimTreeLeaf) nodes.get(j));
				childji = childlevel((SimTreeLeaf) (nodes.get(j)),
						(SimTreeLeaf) nodes.get(i));
				if (i == j)
					distance[i][j] = 0;
				else if (childij >= 0)
					distance[i][j] = childij;
				else if (childji >= 0)
					distance[i][j] = childji;
				else {
					int nparent = 0;
					SimTreeLeaf par = (SimTreeLeaf) nodes.get(i);
					while (childlevel(par, (SimTreeLeaf) nodes.get(j)) < 0) {
						par = par.parent;
						nparent++;
					}
					distance[i][j] = childlevel(par, (SimTreeLeaf) nodes.get(j))
							+ nparent;
				}
				distance[j][i] = distance[i][j] = distance[i][j]
						+ SimKernel.BStransmissionCost;
			}
		}
	}

	int getIdx(SimTreeLeaf l) {
		for (int i = 0; i < nodes.size(); i++)
			if (l == (SimTreeLeaf) nodes.get(i))
				return i;
		return -1; // should pull things down in case of error.
	}
}

class SimKernel {
	static SimKernel theKernel;

	static int BStransmissionCost = 0;

	public EventRecord eventList[];

	public int nodeState[][];

	DistanceMatrix D;

	public static int RowSize = 15;

	public int nRows;

	public long currentTime = 0; // current pctimestamp

	public int currentEvent = 0; // current event., this points to the first

	// unprocessed event

	public long initTime;

	public SimTreeLeaf treeRoot;

	public Salmon theFish;

	public Random rnd;

	public SimKernel(EventRecord[] eventList, double speed, long seed) {
		init(eventList, speed, 15, 4, seed);
	}

	public SimKernel(EventRecord[] eventList, double speed, int experimentType,
			long seed) {
		if (experimentType == 0) {
			init(eventList, speed, 15, 4, seed); // default uses 7 rows
		} else if (experimentType == 1) {
			init(eventList, speed, 13, 8, seed);
		}
	}

	void init(EventRecord[] eventList, double speed, int RowSize, int nRows,
			long seed) {
		if (seed > 0)
			rnd = new Random(seed);
		else
			rnd = new Random();
		theKernel = this;
		SimKernel.RowSize = RowSize;
		theFish = null; // reset stuff is complicated
		this.nRows = nRows;
		this.eventList = eventList;
		nodeState = new int[nRows][];
		for (int i = 0; i < nRows; i++) {
			nodeState[i] = new int[RowSize];
		}
		reset();
		constructTree();
		theFish = new Salmon(this);
		Salmon.v = speed;
		D = new DistanceMatrix();
		D.fillList(treeRoot);
		D.calculateDistances();
		theFish.reset();
		for (int i = 1; i < eventList.length; i++) {
			if (eventList[i].pctimestamp - eventList[i - 1].pctimestamp < 0) {
				System.err.println("out of order stamps");
			}
		}
		// printTree();
	}

	void constructTree() {
		Vector v = new Vector();
		SimTreeLeaf root = new SimTreeLeaf(nRows / 2, RowSize / 2);
		boolean[][] visited = new boolean[nRows][RowSize];
		for (int i = 0; i < nRows; i++)
			for (int j = 0; j < RowSize; j++)
				visited[i][j] = false;
		v.add((Object)root);
		while (!v.isEmpty()) {
			int idx = (int) (rnd.nextDouble() * rnd.nextDouble() * v.size());
			SimTreeLeaf l = (SimTreeLeaf) v.remove(idx);
			SimTreeLeaf child;
			if (!visited[l.r][l.c]) {
				visited[l.r][l.c] = true;
				if (l.r > 0 && !visited[l.r - 1][l.c]) {
					child = new SimTreeLeaf(l.r - 1, l.c);
					l.addChild(child);
					v.add((Object)child);
				}
				if (l.r < nRows - 1 && !visited[l.r + 1][l.c]) {
					child = new SimTreeLeaf(l.r + 1, l.c);
					l.addChild(child);
					v.add((Object)child);
				}
				if (l.c > 0 && !visited[l.r][l.c - 1]) {
					child = new SimTreeLeaf(l.r, l.c - 1);
					l.addChild(child);
					v.add((Object)child);
				}
				if (l.c < RowSize - 1 && !visited[l.r][l.c + 1]) {
					child = new SimTreeLeaf(l.r, l.c + 1);
					l.addChild(child);
					v.add((Object)child);
				}
			} else {
				l.parent.removeChild(l);
				// bad idea but i have to remove this child
			}
		}
		treeRoot = root;
	}

	SimTreeLeaf nodeAt(SimTreeLeaf root, int r, int c) {
		if (root.r == r && root.c == c)
			return root;
		for (int i = 0; i < root.children.length; i++) {
			SimTreeLeaf res = nodeAt(root.children[i], r, c);
			if (res != null)
				return res;
		}
		return null;
	}

	void printXYMatrix() {
		System.out.println("\\xymatrix@=0.05in{");
		for (int i = 0; i < nRows; i++) {
			for (int j = 0; j < RowSize; j++) {
				SimTreeLeaf l = nodeAt(treeRoot, i, j);
				System.out.print("{\\bullet}");
				if (l.parent != null) {
					// System.out.println(""+l.parent.r+","+l.parent.c+"
					// "+l.r+","+l.c);
					System.out.print("\\ar[" + (l.parent.r - l.r) + ","
							+ (l.parent.c - l.c) + "]");
				}
				if (j < RowSize - 1)
					System.out.print("&");
			}
			System.out.println("\\\\");
		}
		System.out.println("}");
	}

	void printCostTrace() {
		long tmax = (long) Math
				.ceil((eventList[eventList.length - 1].pctimestamp - initTime) / 1000.0) + 1; // in
		// terms
		// of
		// seconds
		reset();
		double summobile = 0, sumstatic = 0;
		for (long i = 0; i < tmax; i++) {
			stepTime(1);
			sumstatic += getCost(treeRoot);
			summobile += getCost(theFish.currentNode);
			System.out.println("" + sumstatic + "," + summobile);
		}
	}

	void printInstantenousCostTrace() {
		long tmax = (long) Math
				.ceil((eventList[eventList.length - 1].pctimestamp - initTime) / 1000.0) + 1; // in
		// terms
		// of
		// seconds
		reset();
		double summobile = 0, sumstatic = 0;
		for (long i = 0; i < tmax; i++) {
			stepTime(1);
			sumstatic = getCost(treeRoot);
			summobile = getCost(theFish.currentNode);
			System.out.println("" + sumstatic + "," + summobile);
		}
	}

	void step() {
		step(true);
	}

	void step(boolean disp) // step at least one event
	{
		int cEvent = currentEvent;
		while (cEvent == currentEvent) {
			stepTime(1);
		}
		if (disp)
			System.out.println("Current Event: " + eventList[currentEvent]);
	}

	void stepEvent() // low level event increment. don't call this one use
	// stepTime
	{
		if (currentEvent == eventList.length)
			return; // don't step over it
		int nodeId = eventList[currentEvent].id;
		int type = eventList[currentEvent].type;
		int r = nodeId / RowSize;
		int c = nodeId % RowSize;
		currentTime = eventList[currentEvent].pctimestamp;
		nodeState[r][c] = 1 - type; // 0 start 1 stop
		currentEvent++;
	}

	void stepTime(double seconds) // lowest level function for incementing
	// time
	{
		double deadline = (getSecondsPassed() + seconds) * 1000 + initTime;
		while (currentEvent < eventList.length
				&& eventList[currentEvent].pctimestamp < deadline) {
			stepEvent();
		} // do all the events
		currentTime = (long) deadline;
		theFish.step(seconds);
	}

	void goToTime(double seconds) {
		goToTime(seconds, true);
	}

	void goToTime(double seconds, boolean disp) // in seconds
	{
		double deadline = seconds * 1000 + initTime;
		while (currentEvent < eventList.length && deadline < currentTime - 1000) {
			stepTime(1);
		}
		stepTime((deadline - currentTime) / 1000);
	}

	void reset() {
		for (int i = 0; i < nRows; i++) {
			for (int j = 0; j < RowSize; j++)
				nodeState[i][j] = 0; // start with off
		}
		currentTime = eventList[0].pctimestamp;
		initTime = currentTime;
		currentEvent = 0;
		if (theFish != null)
			theFish.reset();
	}

	void goToEvent(int n) {
		goToEvent(n, true);
	}

	void goToEvent(int n, boolean disp) {
		reset();
		while (currentEvent < n) {
			step(disp);
		}
	}

	public double getSecondsPassed() {
		return ((double) currentTime - initTime) / 1000;
	}

	public double getDownStreamDataRate(SimTreeLeaf n) // data coming from the
	// children
	{
		double sum = nodeState[n.r][n.c];
		for (int i = 0; i < n.children.length; i++) {
			sum += getDownStreamDataRate(n.children[i]);
		}
		return sum;
	}

	public double getUpStreamDataRate(SimTreeLeaf n) // data coming from
	// parent
	{
		return getDownStreamDataRate(treeRoot) - getDownStreamDataRate(n);
	}

	public double getCost(SimTreeLeaf n) {
		int idx = D.getIdx(n);
		double sum = 0;
		for (int i = 0; i < D.nodes.size(); i++) {
			SimTreeLeaf l = (SimTreeLeaf) D.nodes.get(i);
			sum += D.distance[idx][i] * nodeState[l.r][l.c];
		}
		return sum;
	}

	public double getTotalCost() // for root only
	{
		reset();
		double sum = 0;
		double lasttime = currentTime;
		for (int i = 0; i < eventList.length; i++) {
			sum += (eventList[i].pctimestamp - lasttime) / 1000.0
					* getCost(treeRoot);
			lasttime = currentTime;
			step(false);
		}
		return sum;
	}

	public boolean isOptimal(SimTreeLeaf n) {
		double eps = getDownStreamDataRate(treeRoot);
		if (getUpStreamDataRate(n) > eps / 2)
			return false;
		for (int i = 0; i < n.children.length; i++) {
			if (getDownStreamDataRate(n.children[i]) > eps / 2)
				return false;
		}
		return true;
	}

	public SimTreeLeaf getOptimalNode() {
		return getOptimalNode(treeRoot);
	}

	public SimTreeLeaf getOptimalNode(SimTreeLeaf root) // it can return more
	// just find one in
	// rightmost
	{
		if (isOptimal(root))
			return root;
		for (int i = 0; i < root.children.length; i++) {
			SimTreeLeaf l = getOptimalNode(root.children[i]);
			if (l != null)
				return l;
		}
		return null;
	}

	public double getConstantTimeTotalCost() {
		long tmax = (long) Math
				.ceil((eventList[eventList.length - 1].pctimestamp - initTime) / 1000.0) + 1; // in
		// terms
		// of
		// seconds
		reset();
		double sum = 0;
		for (long i = 0; i < tmax; i++) {
			stepTime(1);
			sum += getCost(treeRoot);
		}
		return sum;
	}

	public double getConstantTimeTotalCostMobile() {
		long tmax = (long) Math
				.ceil((eventList[eventList.length - 1].pctimestamp - initTime) / 1000.0) + 1; // in
		// terms
		// of
		// seconds
		reset();
		double sum = 0;
		for (long i = 0; i < tmax; i++) {
			stepTime(1);
			sum += getCost(theFish.currentNode);
		}
		return sum;
	}

	public void printBurstyTrace() {
		long tmax = (long) Math
				.ceil((eventList[eventList.length - 1].pctimestamp - initTime) / 1000.0) + 1; // in
		// terms
		// of
		// seconds
		reset();
		double mobilecost = 0, staticcost = 0;
		SimTreeLeaf opt, oldopt = getOptimalNode();
		for (long i = 0; i < tmax; i++) {
			stepTime(1);
			opt = getOptimalNode();
			mobilecost += getCost(theFish.currentNode);
			staticcost += getCost(treeRoot);
			if (oldopt != opt) {
				mobilecost = 0;
				staticcost = 0;
			}
			oldopt = opt;
			System.out.println("" + staticcost + "," + mobilecost);
		}
	}
}

class Statistics {
	SimKernel krn;

	double averageBurstDuration;

	int nBursts;

	Statistics() {
		krn = SimKernel.theKernel;
	}

	public void fillStatistics() {
		int nRows = krn.nRows;
		int RowSize = SimKernel.RowSize;
		long burstStart[][] = new long[nRows][RowSize];
		for (int i = 0; i < nRows; i++)
			for (int j = 0; j < RowSize; j++)
				burstStart[i][j] = -1;
		double sum = 0;
		nBursts = 0;
		EventRecord[] eventList = krn.eventList;
		for (int i = 0; i < eventList.length; i++) {
			int nodeId = eventList[i].id;
			int type = eventList[i].type;
			int r = nodeId / SimKernel.RowSize;
			int c = nodeId % SimKernel.RowSize;
			if (type == 0 && burstStart[r][c] < 0)
				burstStart[r][c] = eventList[i].pctimestamp;
			else {
				nBursts++;
				sum += eventList[i].pctimestamp - burstStart[r][c];
				burstStart[r][c] = -1;
			}

		}
		if (nBursts > 0)
			averageBurstDuration = sum / nBursts;
		else
			averageBurstDuration = 0;
	}
}

class Salmon {
	double x, y; // position (in pixels)

	static double v = 50;

	SimKernel krn;

	SimTreeLeaf currentNode;

	SimTreeLeaf targetNode;

	Salmon(SimKernel krn) {
		this.krn = krn;
		reset();
	}

	void reset() {
		currentNode = krn.treeRoot;
		targetNode = krn.treeRoot;
		x = currentNode.x;
		y = currentNode.y;
	}

	double dist(double x1, double y1, double x2, double y2) {
		return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
	}

	void step(double t) {
		double dist = this.dist(x, y, targetNode.x, targetNode.y);
		if (dist < v * t) {
			x = targetNode.x;
			y = targetNode.y;
			currentNode = targetNode;
		} else {
			x = x + (targetNode.x - x) / dist * v * t;
			y = y + (targetNode.y - y) / dist * v * t;
		}
		if (currentNode == targetNode) {
			chooseTarget();
		}
		// System.out.println("stepping mbs: "+t+" ("+x+","+y+")" );
	}

	void chooseTarget() {
		double eps = krn.getDownStreamDataRate(krn.treeRoot);
		if (krn.getUpStreamDataRate(currentNode) > eps / 2)
			targetNode = currentNode.parent;
		for (int i = 0; i < currentNode.children.length; i++) {
			if (krn.getDownStreamDataRate(currentNode.children[i]) > eps / 2)
				targetNode = currentNode.children[i];
		}
	}
}

class SimTreeLeaf // tree node for the embedding
{
	static final SimTreeLeaf simNull = new SimTreeLeaf();

	static int currentNodeId = 0;

	int nodeId;

	SimTreeLeaf[] children;

	SimTreeLeaf parent;

	long currentDataRate;

	int x, y; // position in rendering surface

	int dataRate;

	int r, c;

	int level;

	public SimTreeLeaf(int r, int c) {
		this.r = r;
		this.c = c;
		x = c * 50 + 45;
		y = r * 50 + 45;

		init();
	}

	public SimTreeLeaf() {
		init();
	}

	void init() {
		children = new SimTreeLeaf[0];
		parent = null;
		level = 0;
		nodeId = currentNodeId++;
	}

	void addChild(SimTreeLeaf n) {
		int currentchild = 0;
		if (children == null) {
			children = new SimTreeLeaf[1];
		} else {
			SimTreeLeaf[] tmp = new SimTreeLeaf[children.length + 1];
			for (int i = 0; i < children.length; i++)
				tmp[i] = children[i];
			currentchild = children.length;
			children = tmp;
		}
		children[currentchild] = n;
		n.parent = this;
		n.level = level + 1;
	}

	void removeChild(SimTreeLeaf n) {
		SimTreeLeaf[] tmp = new SimTreeLeaf[children.length - 1];
		for (int i = 0, j = 0; i < children.length; i++)
			if (children[i] != n)
				tmp[j++] = children[i];
		children = tmp;
	}

	public String toString() {
		String str = "";
		str = str + nodeId + ": ";
		return str;
	}
}

class AnimationTicker extends TimerTask {
	SimDisplay disp;

	boolean running;
	int cframe=0;

	AnimationTicker(SimDisplay disp) {
		this.disp = disp;
		running = false;
	}

	public void run() {
		if (running) {
			disp.kernel.stepTime(1);
			disp.updateStatusText();
			disp.repaint();
			cframe++;
		}
		else
			cframe=0;
	}
}

class SimDisplay extends Frame implements ActionListener {
	static final long serialVersionUID=6363; 
	
	SimPanel sp;

	SimKernel kernel;

	Button btnStep;

	Button btnReset;

	Label lblStatus;

	TextField tfEventNo;

	Button btnGoToEvent;

	Button btnGoToTime;

	Button btnAnimate;

	java.util.Timer timer;

	AnimationTicker ticker;

	public SimDisplay(SimKernel kernel) {
		this.kernel = kernel;
		ticker = new AnimationTicker(this);
		timer = new java.util.Timer();
		timer.scheduleAtFixedRate(ticker, 0, 100);
		this.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent evt) {
				Frame frame = (Frame) evt.getSource();
				SimDisplay dsp = (SimDisplay) frame;
				dsp.timer.cancel();

				// Hide the frame
				frame.setVisible(false);

				// If the frame is no longer needed, call dispose
				frame.dispose();
			}
		});
		this.setBounds(0, 0, 800, 640);
		this.setLayout(null);
		sp = new SimPanel();
		sp.setBounds(0, 0, 800, 400);
		this.add(sp);

		btnStep = new Button("Step");
		btnStep.setBounds(10, 400, 75, 25);
		btnStep.addActionListener(this);
		this.add(btnStep);

		btnReset = new Button("Reset");
		btnReset.setBounds(90, 400, 75, 25);
		btnReset.addActionListener(this);
		this.add(btnReset);

		tfEventNo = new TextField();
		tfEventNo.setBounds(170, 400, 75, 25);
		this.add(tfEventNo);

		btnGoToEvent = new Button("Go to event");
		btnGoToEvent.setBounds(250, 400, 75, 25);
		btnGoToEvent.addActionListener(this);
		this.add(btnGoToEvent);

		btnGoToTime = new Button("Go to Time");
		btnGoToTime.setBounds(330, 400, 75, 25);
		btnGoToTime.addActionListener(this);
		this.add(btnGoToTime);

		btnAnimate = new Button("Animate");
		btnAnimate.setBounds(410, 400, 75, 25);
		btnAnimate.addActionListener(this);
		this.add(btnAnimate);

		lblStatus = new Label("Initializing the simulation...");
		lblStatus.setBounds(10, 615, 800, 25);
		this.add(lblStatus);
	}

	public void actionPerformed(ActionEvent evt) {
		if (evt.getSource() == btnStep) {
			kernel.step();
		} else if (evt.getSource() == btnReset) {
			kernel.reset();
		} else if (evt.getSource() == btnGoToEvent) {
			kernel.goToEvent(Integer.parseInt(tfEventNo.getText())); // try
			// catch?
		} else if (evt.getSource() == btnGoToTime) {
			kernel.goToTime(Double.parseDouble(tfEventNo.getText()));
		} else if (evt.getSource() == btnAnimate) {
			if (btnAnimate.getLabel() == "Animate") {
				ticker.running = true;
				btnAnimate.setLabel("Stop");
				btnAnimate.repaint();
			} else {
				ticker.running = false;
				btnAnimate.setLabel("Animate");
				btnAnimate.repaint();
			}
		}
		updateStatusText();
		repaint();
	}

	public void repaint() {
		super.repaint();
		Component[] cl = this.getComponents();
		for (int i = 0; i < cl.length; i++)
			cl[i].repaint();
	}

	public void updateStatusText() {
		lblStatus.setText("Current Event:" + kernel.currentEvent
				+ " Current Time:" + kernel.getSecondsPassed()
				+ " Current Total Data Rate:"
				+ kernel.getDownStreamDataRate(kernel.treeRoot)
				+ " Root hop cost:" + kernel.getCost(kernel.treeRoot));
	}

}

public class salmonSim {
	public static int lastFileType; // This is a really ugly hack but i am too
	static SimDisplay sd; 
	// lazy to rewrite the code.

	public static EventRecord[] loadFile(String fileName) {
		EventRecord records[] = new EventRecord[1000000]; // probably too big
		// but whatever
		int nrecords = 0;
		try {
			FileReader fr = new FileReader(fileName);
			BufferedReader input = new BufferedReader(fr);

			String s = input.readLine();
			lastFileType = EventRecord.getEventRecordType(s);

			while (s instanceof String) {
				// System.out.println(s);
				EventRecord e = EventRecord.Parse(s);
				records[nrecords++] = e;
				// System.out.println(e);
				// System.out.println("--------");
				s = input.readLine();
			}

		} catch (ArrayIndexOutOfBoundsException e) {
			System.err
					.println("No Input File\n\nFileIO usage: java FileIO <filename>");
			System.exit(1);
		} catch (IOException e) {
			System.err.println("FileNotFound");
			System.exit(1);
		}
		EventRecord result[] = new EventRecord[nrecords];
		for (int i = 0; i < nrecords; i++)
			result[i] = records[i];
		System.err.println("File :'" + fileName + "' is loaded with "
				+ nrecords + " event records...OK");
		return result;
	}

	public static void showHelp()
	{
		System.out.println("Salmon Emulator");
		System.out.println("USAGE:");
		System.out.println("salmonSim <filename> <options>");
		System.out.println("OPTIONS:");
		System.out.println("-speed <r>: Speed of base station in terms of pixels (need calibration)");
		System.out.println("-nodisplay: Don't show the gui");
		System.out.println("-printxymatrix: print the xy matrix");
		System.out.println("-printcostchange: print the change in cost");
		System.out.println("-printtotalcosts: print the total costs");
		System.out.println("-printinstantchange: print the instaneous costs in each simulation step");
		System.out.println("-seed: the random number seed");
		System.out.println("-printBurstyTrace: print trace but reset at bursts");
		System.out.println("-useBScost: use BS transmission cost (d[i][j]++)");
		System.exit(0);
	}

	public static void main(String[] args) {
		if (args.length < 1)
			showHelp();
		EventRecord[] eventList = loadFile(args[0]);
		double speed = 20;
		long seed = -1;
		boolean hideGUI = false;
		boolean printxymatrix = false;
		boolean printcostchange = false;
		boolean printtotalcosts = false;
		boolean printinstantchange = false;
		boolean printBurstyTrace = false;
		for (int i = 1; i < args.length; i++) {
			if (args[i].equals("-speed")) {
				speed = Double.parseDouble(args[++i]);
			} else if (args[i].equals("-seed")) {
				seed = Long.parseLong(args[++i]);
			} else if (args[i].equals("-nodisplay"))
				hideGUI = true;
			else if (args[i].equals("-printxymatrix"))
				printxymatrix = true;
			else if (args[i].equals("-printcostchange"))
				printcostchange = true;
			else if (args[i].equals("-printtotalcosts"))
				printtotalcosts = true;
			else if (args[i].equals("-printinstantchange"))
				printinstantchange = true;
			else if (args[i].equals("-printBurstyTrace"))
				printBurstyTrace = true;
			else if (args[i].equals("-useBScost"))
				SimKernel.BStransmissionCost = 1;
			else
				showHelp();
		}
		SimKernel krn = new SimKernel(eventList, speed, lastFileType, seed);
		Salmon.v = speed;
		if (printxymatrix) {
			krn.printXYMatrix();
			System.exit(0);
		}
		if (printcostchange) {
			krn.printCostTrace();
			System.exit(0);
		}
		if (printtotalcosts) {
			System.out.println("" + krn.getConstantTimeTotalCost() + ","
					+ krn.getConstantTimeTotalCostMobile());
			System.exit(0);
		}
		if (printinstantchange) {
			krn.printInstantenousCostTrace();
			System.exit(0);
		}
		if (printBurstyTrace) {
			krn.printBurstyTrace();
			System.exit(0);
		}
		if (!hideGUI) {
			sd = new SimDisplay(krn);
			sd.setVisible(true);
		}
		// System.exit(0);

	}

}