// Proxy.java

import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.reflect.*;
import java.text.*;

public class Proxy extends Thread 
{
    /** The serversocket the proxy is using. */
    private static ServerSocket ss = null;
    
    /** Port of the connected proxy. */
    private static int proxyport = 9666;

    /** the current running proxy */
    private static Proxy instance;

    /** The local adress of the proxy. */
    private static InetAddress localhost;
  
    /** the WebConnection */
    private static WebConnection web  = new WebConnection ();

    /** the cache-handler */
    private static NCache cache = new NCache ();

    // vector to store the inteval for each request (download file)
    private static Vector runtime = new Vector();
    private static Vector overhead = new Vector();
    
    // Start the proxy. 
    public static void main (String[] args) {
	instance = new Proxy ();	
    }

    // Constructor    
    private Proxy () {
	try {

	    //Get the address of the computer the proxy is running on
	    localhost = InetAddress.getLocalHost ();
	    cache.setup();
	} catch (UnknownHostException e) {
	    System.out.println ("I couldnt find the hostname of this machine, exiting.");
	    System.exit (-1);
	}

	//Open a socket for the proxy to communicate with Netscape
	openSocket();
	
        //Start the proxy
        start();
    }

    // Get a WebConnection.
    public static WebConnection getWebConnection (HTTPHeader header) throws IOException 
    {
        return web.getConnection (header);
    }
 
    // get connections made to the proxy server
    public void run () {
	BufferedReader shell = new BufferedReader(new InputStreamReader(System.in));
	while (true) {
	    Socket client = null;
	    try {
		if (shell.ready()) {
		    String input = shell.readLine();
		    if (input.equalsIgnoreCase("shutdown")) {
			Viewer(getOverhead());
			System.out.println("For Runtime ********************");
			Viewer(getRuntime());
			kill();		    
		    }
		}
		
		ClientConnection con = new ClientConnection();
		client = ss.accept ();
	        con.setSocket (client);
		
	    } catch (SocketException e) {
		System.out.println("Socketoptions failed?: " + e);
		try {
		    if (client != null)
			client.close();
		} catch (IOException ee) {}
  	    } 
	    catch (IOException e) {
		System.out.println ("Failed to accept, trying to restart serversocket.");
		closeSocket ();
		openSocket ();
	     }
	    catch (Exception e) {
		System.out.println ("Unknown error: " + e);
	    }	
	}
    }
    
    // Close a socket
    protected static void closeSocket () {
  	try {
  	    if (ss != null) {
  		ss.close ();
  		ss = null;
  	    }
  	} catch (IOException e) {
  	    System.out.println ("Failed to close serversocket on port " + proxyport);
  	    System.exit (-1);
  	}
    }

    // Open a socket on the specified port
    protected static void openSocket () {
	try {
	    closeSocket ();
	    ss = new ServerSocket (proxyport);
	}
	catch (IOException e) {
	    System.out.println ("Failed to open serversocket on port " + proxyport);
	    System.exit (-1);
	}
    }

    // Get the InetAddress to connect to.
    public static InetAddress getInetAddress (URL url) throws UnknownHostException {
	return InetAddress.getByName(url.getHost());	
    }

    // Get the port to connect to.
    public static int getConnectPort (int port) {
	return port;
    }

    /** Get the cache the proxy are using.
     * @return the NCache the proxy currently are using.
     */
    public static NCache getCache () {
	return cache;
    }

    /** shutdown
     */
    public static void kill () {
	closeSocket ();
	cache.flush ();
	System.exit (0);
    }

    /** Vector stores the runtime for each downloaded pate.
     */
    public static Vector getRuntime() {
	return runtime;
    }    
    /** Vector stores the overhead for each downloaded pate.
     */
    public static Vector getOverhead() {
	return overhead;
    }    

    /** View the runtime
     */
    public void Viewer(Vector v) {
	int size = v.size();
	double[] time = new double[size];
	for (int i=0; i<size; i++) {
	    time[i] = ((Long) v.elementAt(i)).doubleValue();
	}
	Arrays.sort(time);
	for (int i=0; i<size; i++) {
	    //test System.out.print("  "+time[i]);
	}
	
	int min = (int)time[0]*10/10, max = (int)time[size-1]*10/10, inter = (int)(max-min)*10/100;
	//test System.out.println("In proxy:  "+min +"  "+ max+"  "+inter);
	//test System.out.println("In proxy:  "+"Expect  :"+doubleexpect(time) +"  "+ doublevar(time)+"  ");

//test 
//  	int[][] prob = new int[size][2];
//  	int count = 0; // count times of each interval
//  	int m = 0; // count no. of inter

//  	for (int i=0; i<size; i++) {
//  	    if ((time[i] >= min + m*inter) && (time[i] < min + (m+1)*inter)) {
//  		count ++;			 
//  	    }
//  	    else {
//  		prob[m][1] = count;
//  		prob[m][0] = min + m*inter;
//  		count = 1;
//  		m++;
//  	    }	
//  	    if (time[i] >= max) {
//  		prob[m][1] = count;
//  		prob[m][0] = max;
//  	    }
//  	}
//  	int l = 12;
//  	if (size<12) l = size;
	
//  	for (int i=0; i<l; i++) {
//  	    System.out.println("In proxy:    "+prob[i][0] +"   "+ prob[i][1]);
//  	}
    }

    static private double doubleexpect (double[] number) {
	int n = number.length;
	double sum = 0;
	for (int i=0; i<n; i++) 
	    sum += number[i];

	return sum/(double) n;
    }// method doubleexpect()

    static private double doublevar (double[] number) {
	int n = number.length;
	double sum = 0;
	for (int i=0; i<n; i++) 
	    sum += Math.pow(number[i] - doubleexpect(number), 2.0);
	return sum/(double) (n-1);
    }// method doublevar() 

}




