/*
 * Created on 8-mag-2004
 */
package a3.userInterface;

import java.io.*;
import java.util.*;
import java.net.*;

import a3.protocol.*;
import a3.protocol.Loader;
import a3.event.*;
import a3.domain.*;

import jaCop.domain.*;
import jaCop.event.*;
import jaCop.protocol.*;

/**
 * @author Lompa
 */
public class A3Console {
	
	private A3Manager protocolMan = null;
	private jaCop.protocol.ProtocolManager jaCOPMan = null;
	private A3Node currentNode = null;
	
	/**
	 * @param protocolMan
	 * @param currentAddress
	 * @param port
	 */
	public A3Console(A3Manager protocolMan, jaCop.protocol.ProtocolManager jaCOPMan) {
		this.protocolMan = protocolMan;
		this.jaCOPMan = jaCOPMan;
		this.currentNode = protocolMan.getCurrentNode();

		/* creazione e registrazione ascoltatore */
		PrintListener listener = new PrintListener();
		protocolMan.addStructureSoundedListener(listener);
		jaCOPMan.addCommunityJoinedListener(listener);
		jaCOPMan.addCommunityLeftListener(listener);
		jaCOPMan.addJoinRequestRefusedListener(listener);
		jaCOPMan.addNeighborLostListener(listener);
		jaCOPMan.addNewNeighborListener(listener);
		jaCOPMan.addNewJunctureListener(listener);
	}
	
	public void runConsole(String firstCommand){
		/* campi per l'interfaccia console */
		String communitySounded = null;
		boolean end = false;
		BufferedReader cmdReader = new BufferedReader(new InputStreamReader(System.in));
		
		/* welcome message */
		System.out.println("***** JaCOP-A3 console - v0.5 *****");
		System.out.println("COP service running on port: "+(currentNode.getPort()+1));
		System.out.println("A3 service running on port: "+currentNode.getPort());
		System.out.println("");
		
		String cmd0 = firstCommand;
		
		while(!end){
			try {
				/* prompt */
				System.out.print("> ");
				
				String cmd = null;
				if (cmd0 == null)
					cmd = cmdReader.readLine();
				else {
					System.out.println(cmd0);
					cmd = cmd0;
					cmd0 = null;
				}
				
				String[] cmdTokens = parseCommand(cmd);
				/* esecuzione */
				if (cmdTokens.length > 0)
					chooseCommand(cmdTokens);
				
				/* chiusura */
				System.out.println("");
			}
			catch(Exception e){
				System.out.println(e.getMessage());
				e.printStackTrace();
			}
		}		
	}
	
	protected void chooseCommand(String[] cmdTokens) throws Exception{
		if (cmdTokens[0].equals("exit")){
			cmdExit(cmdTokens);
		}
		else if (cmdTokens[0].equals("echo")){
			cmdEcho(cmdTokens);
		}
		else if (cmdTokens[0].equals("probe")){
			cmdSound(cmdTokens);
		}
		else if (cmdTokens[0].equals("stop")){
			cmdStop(cmdTokens);
		}
		else if (cmdTokens[0].equals("join")){
			cmdJoin(cmdTokens);
		}
		else if (cmdTokens[0].equals("leave")){
			cmdLeave(cmdTokens);
		}
		else if (cmdTokens[0].equals("open")){
			cmdOpen(cmdTokens);
		}
		else if (cmdTokens[0].equals("list")){
			cmdList(cmdTokens);
		}
		else if (cmdTokens[0].equals("ln")){
			cmdListNeighbors(cmdTokens);
		}
		else if (cmdTokens[0].equals("lc")){
			cmdListCommunities(cmdTokens);
		}
		else {
			System.out.println("command unknown");
		}		
	}
	
	/* parsing dei comandi **********************************************************************************/
	
	private static String[] parseCommand(String command) throws IOException{
		Vector buffer = new Vector();
		StreamTokenizer input = new StreamTokenizer(new StringReader(command));
		
		input.resetSyntax();
		
		input.whitespaceChars(' ', ' ');
		input.whitespaceChars('\t', '\t');
		input.whitespaceChars('\r', '\r');
		input.whitespaceChars('\n', '\n');
		
		input.wordChars('a', 'z');
		input.wordChars('A', 'Z');
		input.wordChars('.', '.');
		input.wordChars('0', '9');
		input.wordChars('-', '-');
		input.wordChars('_', '_');
		
		while(input.nextToken() != StreamTokenizer.TT_EOF){
			if (input.ttype == StreamTokenizer.TT_WORD)
				buffer.add(input.sval);
			if (input.ttype == StreamTokenizer.TT_NUMBER)
				buffer.add(Long.toString(Math.round(input.nval)));
		}
		
		return (String[]) buffer.toArray(new String[buffer.size()]);
	}
	
	/* metodi per l'esecuzione di comandi ************************************************************/
	
	private void cmdEcho(String[] cmd){
		for(int i = 1; i < cmd.length; i++)
			System.out.print(cmd[i] + " ");
		System.out.println("");
	}
	
	private void cmdExit(String[] cmd){
		System.exit(0);
	}
	
	
	private void cmdSound(String[] cmd) throws Exception{
		if (cmd.length != 2){
			System.out.println("usage: probe <community>");
		}
		else if (jaCOPMan.existsCommunity(cmd[1])){
			String community = cmd[1];
			
			protocolMan.soundCommunity(community);
		}
		else {
			System.out.println("unexisting community");
		}
	}
	
	private void cmdStop(String[] cmd) throws Exception{
		if (cmd.length != 1){
			System.out.println("usage: stop");
		}
		else {
			protocolMan.stopSoundCommunity();
		}
	}
	
	private void cmdJoin(String[] cmd) throws Exception{
		if (cmd.length != 5 && cmd.length != 6){
			System.out.println("usage: join <community> <ip> <port> <node capacity> [<maxDelegations>]");
		}
		else if (!jaCOPMan.existsCommunity(cmd[1])){
			String community = cmd[1];
			InetAddress address = InetAddress.getByName(cmd[2]);
			int port = Integer.parseInt(cmd[3]);
			int capacity = Integer.parseInt(cmd[4]);
			COPNode server = new COPNode(address, port, 0, false);
			
			int maxDelegations = JoinRequestClient.FORCE;
			if (cmd.length == 6)
				maxDelegations = Integer.parseInt(cmd[5]);
			
			jaCOPMan.joinCommunity(community, server, new jaCop.nodeStatus.WeightedLoadFactorCalculator(capacity), maxDelegations);
		}
		else {
			System.out.println("Community already joined");
		}
	}
	
	private void cmdLeave(String[] cmd) throws Exception{
		if (cmd.length != 2){
			System.out.println("usage: leave <community>");
		}
		else if (jaCOPMan.existsCommunity(cmd[1])){
			String community = cmd[1];
			
			jaCOPMan.leaveCommunity(community);
		}
		else {
			System.out.println("unexisting community");
		}
	}
	
	private void cmdOpen(String[] cmd) throws Exception{
		if (cmd.length != 4){
			System.out.println("usage: open <community> <capacity> <connectionRank>");
		}
		else if (!jaCOPMan.existsCommunity(cmd[1])){
			//String community = currentAddress.getHostAddress()+":"+port+"/"+cmd[1];
			String community = cmd[1];
			int capacity = Integer.parseInt(cmd[2]);
			int connectionRank = Integer.parseInt(cmd[3]);
			
			jaCOPMan.openCommunity(community, new jaCop.nodeStatus.WeightedLoadFactorCalculator(capacity), connectionRank);
			
			//System.out.println("community open");
		}
		else {
			System.out.println("community already open");
		}
	}
	
	private void cmdList(String[] cmd) throws Exception{
		if (cmd.length == 2 && cmd[1].equals("communities")){
			System.out.println("- communities list -");
			String[] communities = jaCOPMan.getCommunities();
			for(int i = 0; i < communities.length; i++){
				System.out.print(printCommunity(communities[i]));
				System.out.println("");
			}			
		}
		else if (cmd.length == 3 || cmd.length == 4 && cmd[1].equals("neighbors")){
			boolean useDetails = false;
			if (cmd.length == 4 && cmd[3].equals("-d")) useDetails = true;
			
			if (!jaCOPMan.existsCommunity(cmd[2])){
				System.out.println("unexisting community");
			}
			else {
				System.out.println("- neighbor list for community "+cmd[2]+" -");
				COPNode[] neighbors = jaCOPMan.getNeighbors(cmd[2]);
				for(int i = 0; i < neighbors.length; i++){
					System.out.print(printNode(neighbors[i]));
					if (jaCOPMan.getJuncturePoint(cmd[2]) != null && neighbors[i].isSame(jaCOPMan.getJuncturePoint(cmd[2])))
						System.out.print(" *");
					if (jaCOPMan.isJoined(cmd[2], neighbors[i]))
						System.out.print(" +");
					System.out.println("");
					if (useDetails){
						System.out.print("\tstructure:");
						COPNode[] structure = jaCOPMan.getStructureNeighbors(cmd[2], neighbors[i]);
						if (structure.length == 0 )
							System.out.print("\tnone\n");
						else
							System.out.print("\n");
						for(int k = 0; k < structure.length; k++){
							System.out.print("\t");
							System.out.println(printNode(structure[k]));
						}
					}
				}					
			}
		}
		else
			System.out.println("usage: list communities | neigbors <community> [-d]");

	}
	
	private void cmdListNeighbors(String[] cmd) throws Exception{
		if ((cmd.length != 2 && cmd.length != 3) || (cmd.length == 3 && !cmd[2].equals("-d"))){
			System.out.println("usage: ln <community> [-d]");
		}
		else {
			String[] cmdTokens = new String[cmd.length + 1];
			cmdTokens[0] = "list";
			cmdTokens[1] = "neighbors";
			cmdTokens[2] = cmd[1];
			if (cmd.length == 3) cmdTokens[3] = "-d";

			cmdList(cmdTokens);
		}
	}
	
	private void cmdListCommunities(String[] cmd) throws Exception{
		if (cmd.length != 1){
			System.out.println("usage: lc");
		}
		else {
			String[] cmdTokens = { "list", "communities"};
			cmdList(cmdTokens);
		}
	}
	
	/* metodi di utilità ******************************************************************************/
	
	private String printNode(COPNode node){
		return node.getAddress().getHostAddress()+":"+node.getPort()+ " " + (node.isStructure() ? "S |" : "  |"+" load factor = "+node.getLoadFactor());
	}
	
	private String printCommunity(String community){
		String ans = community + " ";
		ans += "- load factor = " + jaCOPMan.getCurrentNode(community).getLoadFactor() + " ";
		ans += "- connectionRank = " + jaCOPMan.getConnectionRank(community);
		return ans;
	}
	
	/* main per l'esecuzione in stand alone ***********************************************************/
	public static void main(String[] args){
		try {
			/* oggetti da inizializzare */
			A3Manager protocolMan = null;
			jaCop.protocol.ProtocolManager jaCOPMan = null;
			
			/* controllo degli argomenit */
			if (args.length > 2){
				System.out.println("usage: A3Console [<port>] [<first command>]");
				System.exit(0);
			}
			
			/* porta */
			int port = 3334;
			if (args.length >= 1){
				port = Integer.parseInt(args[0]);
			}
			
			InetAddress currentAddress = InetAddress.getLocalHost();
			
			/* caricamento del servizio A3 e JaCOP */
			/* configurazione */
			Loader.ConfigurationSet conf = Loader.newConfigurationSet();
			conf.setPort(port);
			conf.setConversationTimeout(60000);
			conf.setHeartbeatTimeout(10000);
			conf.setReTransmissionTimeout(2000);
			/* caricamento */
			Loader.ProtocolHandlersCouple couple = Loader.loadFramework(conf);
			/* inizializzazione oggetti */
			protocolMan = couple.getA3Manager();
			jaCOPMan = couple.getJaCOPManager();
			
			/* creazione di una istanza della console */
			A3Console console = new A3Console(protocolMan, jaCOPMan);
			
			/* inizializzazione primo comando */
			String firstCommand = null;
			if (args.length == 2){
				firstCommand = args[1];
			}
			
			/* esecuzione */
			console.runConsole(firstCommand);
		
		}
		catch(Exception e){
			System.out.println(e.getMessage());
			e.printStackTrace();
		}	
	}
	
	/* classe per l'ascolto di eventi *****************************************************************/
	
	public static class PrintListener implements 	IStructureSoundedListener, ICommunityJoinedListener, ICommunityLeftListener,
													INewNeighborListener, INeighborLostListener, IJoinRequestRefusedListener,
													INewJunctureListener {
		
		
		/* (non-Javadoc)
		 * @see jaCop.event.ICommunityJoinedListener#communityJoined(jaCop.event.CommunityJoinedEvent)
		 */
		public void structureSounded(StructureSoundedEvent event) {
			System.out.print("\n:: structure probed in community: "+event.getCommunity());
			System.out.print("\n:: node: "+printNode(event.getSenderCOPNode()));
			System.out.print("\n:: structure:");
			
			if (event.getStructure().length == 0) System.out.print("\n:: none");
			for(int i = 0; i < event.getStructure().length; i++)
				System.out.print("\n:: "+printNode(event.getStructure()[i]));
			
			System.out.println("");
			System.out.print("> ");
		}

		/* metodi di utilità ***************************************************************************/
		
		private String printNode(COPNode node){
			return node.getAddress().getHostAddress()+":"+node.getPort()+ " " + (node.isStructure() ? "S" : "  "+" load factor = "+node.getLoadFactor() );
		}
		
		/* (non-Javadoc)
		 * @see jaCop.event.ICommunityJoinedListener#communityJoined(jaCop.event.CommunityJoinedEvent)
		 */
		public void communityJoined(CommunityJoinedEvent event) {
			System.out.println("\n:: community joined: "+event.getCommunity());
			System.out.print("> ");
		}
		/* (non-Javadoc)
		 * @see jaCop.event.ICommunityLeftListener#communityLeft(jaCop.event.CommunityLeftEvent)
		 */
		public void communityLeft(CommunityLeftEvent event) {
			System.out.println("\n:: community left: "+event.getCommunity());
			System.out.print("> ");
		}
		/* (non-Javadoc)
		 * @see jaCop.event.INewNeighborListener#newNeighbors(jaCop.event.NewNeighborEvent)
		 */
		public void newNeighbor(NewNeighborEvent event) {
			System.out.println("\n:: new neighbors on community "+event.getCommunity()+":");
			System.out.println(":: "+printNode(event.getNeighbor()));
			System.out.print("> ");
		}
		/* (non-Javadoc)
		 * @see jaCop.event.INeighborLostListener#neighborLost(jaCop.event.NeighborLostEvent)
		 */
		public void neighborLost(NeighborLostEvent event) {
			System.out.println("\n:: neighbor lost on community "+event.getCommunity()+": ");
			COPNode node = event.getNeighbor();
			System.out.println(":: "+printNode(node));
			System.out.print("> ");
		}
		/* (non-Javadoc)
		 * @see jaCop.event.IStructureSoundedListener#joinRequestRefused(jaCop.event.StructureSoundedEvent)
		 */
		public void joinRequestRefused(JoinRequestRefusedEvent event) {
			System.out.println("\n:: joinrequest refused for community "+event.getCommunity()+"; refuser: ");
			COPNode node = event.getRefuser();
			System.out.println(":: "+printNode(node));
			System.out.println(":: message: "+event.getMessage());
			System.out.print("> ");
		}
		
		/* (non-Javadoc)
		 * @see jaCop.event.INewJunctureListener#junctureChanged(jaCop.event.NewJunctureEvent)
		 */
		public void newJuncture(NewJunctureEvent event) {
			System.out.println("\n:: juncture changed on community "+event.getCommunity()+", new node is: ");
			COPNode node = event.getJuncture();
			System.out.println(":: "+printNode(node));
			System.out.print("> ");
		}

	}
}
