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

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

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

/**
 * @author Lompa
 */
public class JaCopConsole {
	
	private static ProtocolManager protocolMan = null;
	private static InetAddress currentAddress = null;
	private static int port = 3337;
	
	public static void main(String[] args){
		try {
			/* controllo degli argomenit */
			if (args.length > 1){
				System.out.println("usage: A3Console [port]");
				System.exit(0);
			}
			
			/* porta */
			int port = 3337;
			if (args.length == 1){
				port = Integer.parseInt(args[0]);
			}
			
			currentAddress = InetAddress.getLocalHost();
			JaCopConsole.port = port;
			
			/* caricamento del servizio jaCOP */
			Loader.ConfigurationSet conf = Loader.newConfigurationSet();
			conf.setPort(port);
			conf.setConversationTimeout(30000);
			conf.setHeartbeatTimeout(15000);
			conf.setReTransmissionTimeout(3000);
			protocolMan = Loader.loadFramework(conf);
			
			/* creazione e registrazione ascoltatore */
			PrintListener listener = new PrintListener();
			protocolMan.addCommunityJoinedListener(listener);
			protocolMan.addCommunityLeftListener(listener);
			protocolMan.addJoinRequestRefusedListener(listener);
			protocolMan.addNeighborLostListener(listener);
			protocolMan.addNewNeighborListener(listener);
			protocolMan.addNewJunctureListener(listener);
			
			/* campi per l'interfaccia console */
			boolean end = false;
			BufferedReader cmdReader = new BufferedReader(new InputStreamReader(System.in));
			
			/* welcome message */
			System.out.println("***** jaCOP console - v0.1 *****");
			System.out.println("service running on port: "+conf.getPort());
			System.out.println("");
			
			while(!end){
				try {
					/* prompt */
					System.out.print("> ");
					String cmd = cmdReader.readLine();
					String[] cmdTokens = parseCommand(cmd);
					/* esecuzione */
					if (cmdTokens.length > 0)
						if (cmdTokens[0].equals("exit")){
							cmdExit(cmdTokens);
						}
						else if (cmdTokens[0].equals("echo")){
							cmdEcho(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");
						}
					
					/* chiusura */
					System.out.println("");
				}
				catch(Exception e){
					System.out.println(e.getMessage());
					e.printStackTrace();
				}
			}
		}
		catch(Exception e){
			System.out.println(e.getMessage());
			e.printStackTrace();
		}
		
		
	}
	
	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 static void cmdEcho(String[] cmd){
		for(int i = 1; i < cmd.length; i++)
			System.out.print(cmd[i] + " ");
		System.out.println("");
	}
	
	private static void cmdExit(String[] cmd){
		System.exit(0);
	}
	
	private static void cmdJoin(String[] cmd) throws Exception{
		if (cmd.length != 5){
			System.out.println("usage: join <community> <ip> <port> <node capacity>");
		}
		else if (!protocolMan.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 = 3;
			if (cmd.length == 6)
				maxDelegations = Integer.parseInt(cmd[5]);
			
			protocolMan.joinCommunity(community, server, new WeightedLoadFactorCalculator(capacity), maxDelegations);
		}
		else {
			System.out.println("Community already joined");
		}
	}
	
	private static void cmdLeave(String[] cmd) throws Exception{
		if (cmd.length != 2){
			System.out.println("usage: leave <community>");
		}
		else if (protocolMan.existsCommunity(cmd[1])){
			String community = cmd[1];
			
			protocolMan.leaveCommunity(community);
		}
		else {
			System.out.println("unexisting community");
		}
	}
	
	private static void cmdOpen(String[] cmd) throws Exception{
		if (cmd.length != 4){
			System.out.println("usage: open <community> <capacity> <connectionRank>");
		}
		else if (!protocolMan.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]);
			
			protocolMan.openCommunity(community, new WeightedLoadFactorCalculator(capacity), connectionRank);
			
			System.out.println("community open");
		}
		else {
			System.out.println("community already open");
		}
	}
	
	private static void cmdList(String[] cmd) throws Exception{
		if (cmd.length == 2 && cmd[1].equals("communities")){
			System.out.println("- communities list -");
			String[] communities = protocolMan.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[4].equals("-d")) useDetails = true;
			
			if (!protocolMan.existsCommunity(cmd[2])){
				System.out.println("unexisting community");
			}
			else {
				System.out.println("- neighbor list for community "+cmd[2]+" -");
				COPNode[] neighbors = protocolMan.getNeighbors(cmd[2]);
				for(int i = 0; i < neighbors.length; i++){
					System.out.print(printNode(neighbors[i]));
					if (protocolMan.getJuncturePoint(cmd[2]) != null && neighbors[i].isSame(protocolMan.getJuncturePoint(cmd[2])))
						System.out.print(" *");
					if (protocolMan.isJoined(cmd[2], neighbors[i]))
						System.out.print(" +");
					System.out.println("");
					if (useDetails){
						System.out.print("\tstructure:");
						COPNode[] structure = protocolMan.getStructureNeighbors(cmd[1], neighbors[i]);
						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 static void cmdListNeighbors(String[] cmd) throws Exception{
		if (cmd.length != 2){
			System.out.println("usage: ln <community>");
		}
		else {
			String[] cmdTokens = { "list", "neighbors", cmd[1] };
			cmdList(cmdTokens);
		}
	}
	
	private static 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 static String printNode(COPNode node){
		return node.getAddress().getHostAddress()+":"+node.getPort()+" load factor = "+node.getLoadFactor() + " " + (node.isStructure() ? "S |" : "  |");
	}
	
	private static String printCommunity(String community){
		return community +" - load factor = " + protocolMan.getCurrentNode(community).getLoadFactor();
	}
	
	/* classe per l'ascolto di eventi *****************************************************************/
	
	public static class PrintListener implements ICommunityJoinedListener, ICommunityLeftListener,
						INewNeighborListener, INeighborLostListener, IJoinRequestRefusedListener,
						INewJunctureListener {
		
		
		/* (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("> ");
		}
		
		private String printNode(COPNode node){
			return node.getAddress().getHostAddress()+":"+node.getPort()+" load factor = "+node.getLoadFactor() + " " + (node.isStructure() ? "S" : "  ");
		}
		/* (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("> ");
		}
	}
	
}
