import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Scanner;


public class DirectoryTestSecond {

	public static final int TEST_CLIENTS = 10;
	public static final int SERVER_PORT = 54321;
	public static final String HOST = "localhost";
	
	public static int tests = 0;
	public static int passed = 0;

    public static boolean showEx = false;
	
    public static void showException(Exception e){
    	if(showEx){
    		System.out.println("\t" + e);
    		e.printStackTrace();
    	}
    }

	public static void showResults(){
		System.out.println();
		System.out.println("-----------------------TEST SUMMARY-------------------");
		System.out.printf("TOTAL TESTS   %d\n", tests);
		System.out.printf("      PASSED  %d\n", passed);
		System.out.printf("      FAILED  %d\n", tests-passed);
		System.out.printf("      PERCENT %.2f\n\n", (100.0*passed)/tests);
	}
	
	public static void test(boolean condition, String msg){
		tests++;
		System.out.printf("%-50s", msg);
		if(condition){
			passed++;
			System.out.println("\t PASSED");
		}
		else {
			System.out.println("\t FAILED");
		}
	}
	
	public static String sendString(String msg){
		//open a socket		
		try {
			Socket s = new Socket("localhost",  54321);

			//send the message
			PrintWriter pw = new PrintWriter(s.getOutputStream());
			pw.println(msg);
			pw.flush();
			
			Scanner inFromNet = 
					new Scanner(s.getInputStream());

			//get the response (one line)
			String firstLine = inFromNet.nextLine();

			//return the line
			return firstLine;

		}
		catch(IOException e){
			System.err.println("Exception: " + e.getMessage());
			showException(e);
		}
		return "";
	}

	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		System.out.println("java DirectoryTestSecond [-v] [-s]");
		System.out.println("  -v: verbose, show exception output");
		//System.out.println("  -s: simple, run simple tests");
		//boolean simple = false;
	    if(args.length > 0) {
	    	for(int i = 0; i < args.length; i++) {
	    		if(args[i].equals("-v")) 
	    			showEx = true;
	    			
	    	}
	    }
	    
	    
		DirectoryClientConnector[] clients = new DirectoryClientConnector[TEST_CLIENTS];
		
		//create clients
		for(int i = 0; i < TEST_CLIENTS; i++){
			try {
				clients[i] = new DirectoryClientConnector(HOST, i, "c" + i);
				clients[i].logon();
				test(true, "Created and logged client " + i);
			}
			catch(Exception e){
				test(false, "Created and logged client " + i);
				showException(e);
			}
		}
		
		//list testing
		try {
			ArrayList<String> list = clients[0].getList();
			test(list != null, "getList produces an ArrayList");

			//size varies depending on if the client itself is in the list
			test(list.size() == TEST_CLIENTS || list.size() == TEST_CLIENTS - 1, 
					"getList returns the correct number of clients");
			
			//make sure all the clients that should be there, are
			testList(clients, list, 1, TEST_CLIENTS-1, true);
			
			//log some off
			int top = TEST_CLIENTS/2;
			for(int i = top; i < TEST_CLIENTS; i++){
				clients[i].logoff();
			}
			
			//check again
			list = clients[0].getList();
			testList(clients, list, 1, top-1, true);
			testList(clients, list, top, TEST_CLIENTS-1, false);
			
		}
		catch(Exception e){
			test(false, "getList produces an ArrayList");
			showException(e);
		}

		boolean testVar = true;
		
		//clean up
		for(int i = 0; i < TEST_CLIENTS/2; i++){
			try{
				clients[i].logoff();
				
			}
			catch(Exception e){
				testVar = false;
				showException(e);
			}
		}
		test(testVar, "Other logoffs");
		
		//showResults();
		
		//Evil client!
		DirectoryClientConnector evil = new DirectoryClientConnector(HOST, 10000, "evil");
		
		/*
		//try to call methods without logging on
		testVar = true;
		try {
			evil.ping();
			testVar = false;
		}
		catch(Exception e){ 
			showException(e);
		}
		test(testVar, "Ping called before login.");
		*/
		
		testVar = true;
		try {
			evil.getList();
			testVar = false;
		}
		catch(Exception e){
			showException(e);
		}
		test(testVar, "List called before login.");
		
		
		testVar = true;
		try {
			evil.logoff();
			testVar = false;
		}
		catch(Exception e){
			showException(e);
		}
		test(testVar, "Logoff called before login.");
		
		//fine, fine login
		testVar = true;
		try {
			evil.logon();
		}
		catch(Exception e){
			testVar = false;
			showException(e);
		}
		test(testVar, "Evil client login.");
		
		//while we are here, lets make sure we are alone
		try {
		    ArrayList<String> evilList = evil.getList();
		    test(evilList.size() <= 2, "Evil client should be alone.");
		}
		catch(Exception e){
			showException(e);
		}

		
		//But now, we do it again! HA HA HA HA (evil laughter)
		testVar = true;
		try {
			evil.logon();
			testVar = false;
		}
		catch(Exception e){
			showException(e);
		}
		test(testVar, "Evil client login twice.");
		
		testVar = true;
		try {
			evil.logoff();
		}
		catch(Exception e){
			testVar = false;
			showException(e);
		}
		test(testVar, "Evil client logoff.");
		
		
		//random wrong messages from no one
		testMessage("LOGON 54321");
		testMessage("LOGON One Two");
		testMessage("LOGON");
		testMessage("LIFT");
		testMessage("LIST");
		testMessage("LIST 12345");
		testMessage("PING 12345");
		
		//test ping
		
		//create two clients
		try {
			DirectoryClientConnector pingTest = new DirectoryClientConnector(HOST, 11111, "ping1");
			pingTest.logon();
			String result = sendString("LOGON 10101 ping2");
			String[] pieces = result.split(":");
			int id = Integer.parseInt(pieces[1]);
			//int ttl = Integer.parseInt(pieces[2]);
			int ttl = (int)Double.parseDouble(pieces[2]);
			
			//wait for ttl to expire
			System.out.printf("WAITING %d SECONDS FOR TIMEOUT.\n", ttl + 2);
			try {
				Thread.sleep((ttl + 2)*1000);
			}
			catch(InterruptedException ie){
			    showException(ie);
				
			}
			DirectoryClientConnector pingChecker = new DirectoryClientConnector(HOST, 12123, "checker");
			pingChecker.logon();
			ArrayList<String> pingList = pingChecker.getList();
			
			//ping1 should be there, ping2 should not
			boolean pingWorks = false;
			boolean removeWorks = true;
			for(String s: pingList){
				System.out.println("\t" + s);
				String[] pcs = s.split(":");
				if(pcs[1].equals("ping1")){
					pingWorks = true;
				}
				if(pcs[1].equals("ping2")){
					removeWorks = false;
				}
			}
			test(pingWorks, "Ping keeps the client alive.");
			test(removeWorks, "Server removes non-pinging client.");
		}
		catch(Exception e){
			test(false, "Ping tests.");
			showException(e);
		}
		showResults();
	}

	
	private static void testMessage(String msg){
		try {
			String result = sendString(msg);
			test(result.startsWith("ERROR"), "Message " + msg + " causes an error.");
		}
		catch (Exception e) 
		{
			test(false, "Message " + msg + " causes an error.");
			showException(e);
		}
		
	}
	
	//check if every item from index from to index to is in the list
	private static void testList(DirectoryClientConnector[] clients, ArrayList<String> list, int from, int to, boolean testIfIn){
		
		boolean[] inList = new boolean[TEST_CLIENTS];
		for(String s: list){
			if(s.startsWith("LIST")){
				//ignore this line if there is one
			}
			else {
				String[] pieces = s.split(":");
				test(pieces.length == 4, "List string has four elements.");
				int port = -1;
				try {
					port = Integer.parseInt(pieces[3]);
					test(true, "Port is a number.");	
					
					inList[port] = true;
				}
				catch(NumberFormatException nfe){
					test(false, "Port is a number.");
					showException(nfe);
				}
			}
		}
		
		boolean all = true;
		boolean none = true;
		for(int i = from; i <= to; i++){
			all = all && inList[i];
			none = none && !inList[i];
		}
		
		if(testIfIn){
			test(all, "All clients from " + from + " to " + to + " are in the list.");
		}
		else {
			test(none, "No clients from " + from + " to " + to + " are in the list.");
		}
	}
	
}
