import java.io.PrintStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class Node {
	
	protected List<Message> messageQueue;
	private int guid, domguid;
	protected static int staticGuid = 9; //start at ten so guid = 0 means no dom
	private boolean inCore = false;
	private Thread thread;
	
	Set<NodeGhost> neighbors;
	
	public int degree() {
		return neighbors.size();
	}
	public Set<NodeGhost> getNeighbors() {
		return neighbors;
	}
	
	public int effectiveDegree() {
		int count = 0;
		
		for (NodeGhost n : neighbors) {
			if (n.domguid == guid) {
				count++;
			}
		}
		
		return count;
	}
	
	public boolean inCore() {
		return inCore;
	}
	public int getGuid() {
		return guid;
	}
	public int getDomguid() {
		return domguid;
	}
	
	public Node() {
		messageQueue = Collections.synchronizedList(new LinkedList<Message>());
		guid = ++staticGuid;
		neighbors = new HashSet<NodeGhost>();
	}
	
	public void start() {
		thread = new Thread(new Runnable() {
			public void run() {
				processLoop();
			}
		});
		thread.start();
	}
	
	public void receiveMessage(Message msg) {
			messageQueue.add(msg);
	}
		
	protected void processLoop() {
		try {
			for (;;) {
				processMessage();
				sendUpdate();
				if (effectiveDegree() > 0) {
					if (inCore == false) {
						Network.getNetwork().changeOccured();
					}
					inCore = true;
				} else {
					if (inCore == true) {
						Network.getNetwork().changeOccured();
					}
					inCore = false;
				}
				Thread.sleep(100);
			}
		} catch (InterruptedException ie) {
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private void processMessage() {
		Message msg = null;
		
		if (messageQueue.size() != 0) {
			msg = messageQueue.get(0);
			messageQueue.remove(0);
		}
		
		if (msg == null) {
			return; //nothing to do
		}
		
		if (msg.getType() == Message.BEACON) {
			BeaconMessage m = (BeaconMessage) msg;
			NodeGhost ghost = m.getGhost();
			
			//update neighbor ghost based on new information he sent me
			NodeGhost target = getGhostForGuid(msg.getSourceNode());
			if (target != null) {
				if (target.inCore != ghost.inCore) {
					Network.getNetwork().changeOccured();
				}
				target.updateFrom(ghost);
			}
			
		} else if (msg.getType() == Message.UPDATE) {
			//the src just chose me as its dominator and has some new info for me
			NodeGhost target = getGhostForGuid(msg.getSourceNode());
			if (target != null) {
				target.domguid = guid;
			}
			
			//now, need to update my neighbor info based on the set in the message
			for (Pair p : ((UpdateMessage)msg).getSet()) {
				int n = p.getFirst();
				int d = p.getSecond();
				NodeGhost t = getGhostForGuid(n);
				if (t != null) {
					t.domguid = d;
				}
			}
		}
	}
	
	private NodeGhost getGhostForGuid(int guid) {
		for (NodeGhost ng : neighbors) {
			if (ng.getGuid() == guid) {
				return ng;
			}
		}
		return null;
	}
	
	private NodeGhost getLargest() {
		if (neighbors.size() == 0) {
			return null;
		}
		Iterator i = neighbors.iterator();
		NodeGhost candidate = (NodeGhost) i.next();
	
		for (NodeGhost g : neighbors) {
			if (g.ed > candidate.ed) { 
				candidate = g;
			} else if (g.ed == candidate.ed) {
				if (g.d > candidate.d) {
					candidate = g;
				}
			}
		}
		
		return candidate;
	}
	
	private void sendUpdate() {
		//this has to happen continuosuly i think
		if (domguid == 0) {
			NodeGhost ng = getLargest();
			if (ng == null) {
				domguid = guid; //no neighbors, so i dominate myself...
			} else {
				//so there was a largest, and now I have to see if I'm bigger
				domguid = ng.guid; //assume the otherguy is the dom and change if im bigger
				if (effectiveDegree() > ng.ed) {
					domguid = guid;
				} else if (effectiveDegree() == ng.ed) { 
					if (degree() > ng.d) {
						domguid = guid;
					}
				}
			}
			Set<Pair> s = new HashSet<Pair>();
			for (NodeGhost n : neighbors) {
				s.add(new Pair(n.getGuid(), n.getDomguid()));
			}
			Network.getNetwork().broadcast(this, new UpdateMessage(guid, s));
		}
	
		
		Network.getNetwork().broadcast(this, new BeaconMessage(guid, new NodeGhost(this)));
	}
	
	public boolean isNeighborOf(int n) {
		if (getGhostForGuid(n) != null) {
			return true;
		}
		return false;
	}
	
	public void addNeighbor(Node neighbor) {
		neighbors.add(new NodeGhost(neighbor));
	}
	
	
	
	public void print(PrintStream o) {
		o.println("----------------------");
		o.println("Node " + guid);
		o.println("In core: " + inCore);
		o.println("Dominator: " + domguid);
		o.println("Effective Degree: " + effectiveDegree());
		o.println("Degree: " + degree());
		o.println("Contents of neighbor table: ");
		Iterator it = neighbors.iterator();
		while (it.hasNext()) {
			NodeGhost ng = (NodeGhost) it.next();
			ng.printLocal(o);
		}
		o.println("----------------------");
	}
	public void stop() {
		thread.interrupt();
	}
	

}
