import java.io.PrintStream;
import java.util.LinkedList;
import java.util.List;


public class Network {
	
	private boolean AGENT_ON = true;
	
	private List<Node> nodes;
	private Node agent;
	
	private List<Triplet> messageLog;
	
	private static Network instance;
	private long lastChange;
	
	public Network() {
		nodes = new LinkedList<Node>();
		messageLog = new LinkedList<Triplet>();
		if (AGENT_ON) {
			agent = new Agent();
			agent.start();
		}
	}
	
	public boolean checkAllNodes(int guid, boolean in) {
		for (Node n : nodes) {
			for (NodeGhost g : n.getNeighbors()) {
				if (g.guid == guid) {
					if (g.inCore != in) {
						System.out.println("Node " + n.getGuid() + " GN " + g.guid);
						return false;
					}
				}
			}
		}
		
		return true;
	}
	
	public boolean consistent() {
		for (Node n : nodes) {
			if (!nodeConsistent(n.getGuid())) {
				System.out.println("Not consistent: " + n.getGuid());
				return false;
			}
		}
		return true;
	}
	
	private boolean nodeConsistent(int guid) {
		Node node = Network.getNetwork().getNode(guid);
		boolean in = node.inCore();
		
		return Network.getNetwork().checkAllNodes(guid, in);
	}
		
	public static Network getNetwork() {
		if (instance == null) {
			instance = new Network();
		}
		return instance;
	}
	
	private Node getNodeForGuid(int guid) {
		for (Node n : nodes) {
			if (n.getGuid() == guid) {
				return n;
			}
		}
		return null;
	}
	
	//need to limit to nearby nodes only
	public synchronized void broadcast(Node src, Message msg) {
		for (Node n : nodes) {
			if (n.isNeighborOf(msg.getSourceNode())) {
				messageLog.add(new Triplet(src.getGuid(), n.getGuid(), msg));
				n.receiveMessage(msg);
				if (AGENT_ON) {
					agent.receiveMessage(msg);
				}
			}
		}
	}
	
	public synchronized void unicast(Node src, int dest, Message msg) {
		for (Node n : nodes) {
			if (n.getGuid() == dest) {
				messageLog.add(new Triplet(src.getGuid(), n.getGuid(), msg));
				n.receiveMessage(msg);
				if (AGENT_ON) {
					agent.receiveMessage(msg);
				}
			}
		}
	}
		
	public void addNode(Node n) {
		nodes.add(n);
	}
	
	public Node getNode(int guid) {
		for (Node n : nodes) {
			if (n.getGuid() == guid) {
				return n;
			}
		}
		return null;
	}

	public void startNodes() {
		for (Node n : nodes) {
			n.start();
		}
	}

	public void stopAllNodes() {
		for (Node n : nodes) {
			n.stop();
		}
		if (agent != null) {
			agent.stop();
		}
	}

	public void printAllNodes() {
		for (Node n : nodes) {
			n.print(System.out);
		}
	}

	public long getLastChange() {
		return lastChange;
	}
	
	public void changeOccured() {
		lastChange = System.currentTimeMillis();
	}

	public int getSize() {
		return nodes.size();
	}

	//the agent may send to anyone
	public synchronized void agentSend(Message m, int dst) {
		if (!AGENT_ON) {
			return;
		}
		messageLog.add(new Triplet(666, dst, m)); //the agent's id is 66
		Node n = getNodeForGuid(dst);
		n.receiveMessage(m);
	}
	
	public void printMessages(PrintStream o) {
		for (Triplet t : messageLog) {
			o.println("From " + t.s + " to " + t.d + ": ");
			t.m.print(o);
			o.println("/////////////////////////");
		}
	}
	
}
