import java.lang.Math;
import java.util.Map;
import java.util.TreeMap;

// indicate whether the result is computed from REMOTE, LOCAL, CACHE and UNHANDLED
enum CompTag {
	REMOTE, LOCAL, CACHE, UNHANDLED, REJECTED
}


// class representing computation result
class CompResult {
	
	CompTag tag;
	int value;
	
	CompResult(int arg1, CompTag arg2) 
	{
		value = arg1;
		tag = arg2;
	}
	
	String to_string()
	{
		if( tag == CompTag.REMOTE )
			return new String("[REMOTE] " + Integer.toString(value));
		else if( tag == CompTag.LOCAL )
			return new String("[LOCAL] " + Integer.toString(value));
		else if( tag == CompTag.CACHE )
			return new String("[CACHE] " + Integer.toString(value));
		else if( tag == CompTag.REJECTED )
			return new String("[REJECTED] " + Integer.toString(value));
		else
			return new String("Unexpected Error!\n");
	}
}


// Server face
interface ServerFace {
	CompResult mash_number(int number);
	void dispose();
}


//
// Even Servers
// always add 1
//
class EvenLocalServer implements ServerFace {

	public CompResult mash_number(int number) {
		assert(number % 2 == 0);
		// compute absolute value plus one
		return new CompResult(Math.abs(number)+100, CompTag.LOCAL);
	}
	public void dispose(){
		
	}
}

//
// Even Proxy: if 6, return -1, otherwise call evenLocalServer
// 
class EvenServerProxy implements ServerFace {
	
	ServerFace serv = new EvenLocalServer();
	
	public CompResult mash_number(int number) {
		assert(number % 2 == 0);
		if( number % 6 == 0)
			return new CompResult(-1, CompTag.REJECTED);
		else {
			return serv.mash_number(number);
		}
	}
	
	public void dispose() {
		serv.dispose();
	}
	
}


//
// Odd Servers
//

class OddNetServer implements ServerFace {
	
	ClientNetProxy net_proxy;
	
	OddNetServer( String host, int port ) 
	{
		net_proxy = new ClientNetProxy(host, port);
	}
	
	public CompResult mash_number(int number) {
		assert(number % 2 == 1);
//      if (number > 125) // OH send the message when the number is larger than 125
		   net_proxy.sendMessage(Integer.toString(number));
		return new CompResult(Integer.parseInt(net_proxy.getMessage()), CompTag.REMOTE);
	}
	
	public void dispose() {
		net_proxy.dispose();
	}
}

//
// compute a non-deterministic function
// 
class OddLocalServer implements ServerFace {

	public CompResult mash_number(int number) {
		// return random result from OP
		return new CompResult((int)(Math.random() * 1000) + number, CompTag.LOCAL);
		
	}
	
	public void dispose() {}
}


//
// cache entity
//
class CachedResult {
	
	CachedResult(int arg1, long arg2) {
		value = arg1;
		time = arg2;
	}
	
	public int value;
	public long time;
	
}

//
// Odd Proxy: use cache to reduce network comminucation
//
class OddNetServerProxy implements ServerFace {
	
	Map<Integer, CachedResult> cache = new TreeMap<Integer, CachedResult>();
	OddNetServer serv;
	long maxInterval;
	
	OddNetServerProxy( String host, int port, long max_interv )
	{
		serv = new OddNetServer(host, port);
		maxInterval = max_interv*1000;
	}

	
	public CompResult mash_number(int number) {
		assert(number % 2 == 1);
		
		long current_time = System.currentTimeMillis();
		
		if( cache.containsKey(number) ) 
		{
			System.out.println( "The key " + number + " is cached!");
			CachedResult res = cache.get(number);
			long gap =current_time - res.time;
			System.out.println("Time gap is " + gap);
			if( gap <= maxInterval )
			{
				return new CompResult(res.value, CompTag.CACHE);
			}
			System.out.println( "But it's outdated, update!");
		}
		else
			System.out.println("Does not contain the key " + number);
		CompResult res = serv.mash_number(number);
		CachedResult obj = new CachedResult(res.value, current_time);
		cache.put(number, obj);
		
		return res;
	}
	
	public void dispose() {
		serv.dispose();
	}
}



//
// Prime Servers
//

class PrimeNetServer implements ServerFace {
	
	ClientNetProxy net_proxy;
	
	PrimeNetServer( String host, int port ) 
	{
		net_proxy = new ClientNetProxy(host, port);
	}
	
	public CompResult mash_number(int number) {		
		net_proxy.sendMessage(Integer.toString(number));
      return new CompResult(Integer.parseInt(net_proxy.getMessage()), CompTag.REMOTE);
	}
	
	public void dispose() {
		net_proxy.dispose();
	}

}

// always double prime
class PrimeLocalServer implements ServerFace {
	
	public CompResult mash_number(int number) {
		
		return new CompResult(number * 2, CompTag.LOCAL);
		
	}	
	
	public void dispose() {}
}


//
// network listener
//

class NetListener {
	
	ServerNetProxy net;
	ServerFace serv;
	
	public void start( String name, int port, ServerFace arg_serv ){
		
		System.out.println(" (" + name + " net) listening at port " + Integer.toString(port));
		
		serv = arg_serv;
		
		net = new ServerNetProxy(port);
		net.accept();

		while (true) {
			System.out.println("Waiting for msg!");
		    String str = net.getMessage();
			
			if( str != null && !str.isEmpty() ) {
				
				if( str.equals("close") ) {
					
					System.out.println(" (" + name + " net) client has closed the connection. ");
//					System.out.println(" (" + name + " net) listening at port " + Integer.toString(port));
//					net.accept();
					break;
				}
				
				int num = Integer.parseInt(str);
				
				assert(num % 2 == 1);
				
				System.out.println(" (" + name + " net) server asked to handle " + Integer.toString(num));
				CompResult res = serv.mash_number(num);
				System.out.println(" (" + name + " net) server mashed and returned " + Integer.toString(res.value));
				
				net.sendMessage(Integer.toString(res.value));				
			}
			else{
//				System.sleep(1000);
				try {
					System.out.println("No msg got,sleep 1s");
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("Connection from client is lost! Re-establish");
				net.dispose();
				net.accept();
				System.out.println("Done!");
			}
		}
		
	}
}

