// ClientConnection.java

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

public class ClientConnection extends Thread 
{
    private Socket socket = null;
    private HTTPHeader header = null;
    private InputStream in = null;
    private OutputStream out = null;

    //Still getting information from the client
    //so keep the connection alive
    private boolean  keepalive = true;
    private boolean  maycache    = true;

    // time used for testing
    long start, stop, inteval;
    long start2, stop2, inteval2;

    // Create a new WebConnection.
    public ClientConnection () {
    }

    // Create a new WebConnection for a given socket.
    public ClientConnection (Socket socket) {
	setSocket (socket);
    }
    
    /** Give this connection a socket to handle
     * @param socket the Socket that is requesting service.
     */
    public void setSocket (Socket socket) {
  	this.socket = socket;
	keepalive = true;
	start ();
    }

    /** Handle the incomming requests.
     */
    public void run () {
        try {
            HTTPHeader badresponse = null;          
            do { 
		// first read the header
                try {

                    //Create a inputStream Object to the socket
		    in = socket.getInputStream();
		    out = socket.getOutputStream();
		    header = new HTTPHeader (in);
		    
		    String c1 = header.getHeader ("Content-Length");
		    String con = header.getHeader ("Connection");
		    String pcon = header.getHeader ("Proxy-Connection");
		    
		    if (con != null && con.equalsIgnoreCase ("close"))
			keepalive = false;
		    if (keepalive && pcon != null && pcon.equalsIgnoreCase ("close"))
			keepalive = false;
		    
		} 
		catch (InterruptedIOException e) {
		    break;
		} 
		catch (IOException e) {
		    break;
		}

		//Look up the request in the cache and if the request is 
		//in the cache return it to the client.  Otherwise
		//connect to the server and get the data
		start = System.currentTimeMillis();
		handleRequest (header);
		stop = System.currentTimeMillis();
		inteval = stop - start;
		Proxy.getRuntime().addElement(new Long(inteval));
		//test System.out.println("In ClientConnection.java, size of runtime   "+Proxy.getRuntime().size());
		//test System.out.println("In ClientConnection.java, time for a request    "+inteval+ "   ms");
	    } while (keepalive);
	} catch (Throwable t) {
	}
	closeDown ();
    }
    
    //Close the connection
    protected void closeDown () {
  	try {
  	    socket.close ();
  	} catch (IOException e) {
  	}
  	socket = null; 
  	in = null;
  	out = null;
    }
    
    //Handle a request by getting the datastream (from the cache or the web).
    public void handleRequest (HTTPHeader header) {
	OutputStream outStream = null;
	InputStream contentStream = null;
	HTTPHeader webheader = null;
	HTTPHeader resheader = new HTTPHeader();
	HTTPHeader pxyheader = new HTTPHeader();
	HTTPHeader reqheader = new HTTPHeader();
	WebConnection wc = null;
	NCacheEntry entry = null;
	long size = -1;

        //Check if the request is in the cache
	    entry = Proxy.getCache ().getEntry (header);
	    if (entry != null) {
		setKeepalive (true);
		
		try {
	        pxyheader = (HTTPHeader) entry.getKey();
		resheader = (HTTPHeader) entry.getDataHook();
		reqheader = pxyheader.ifModifiedHeader(pxyheader, resheader);

		WebConnection h = Proxy.getWebConnection (reqheader);
		OutputStream oStream = h.getOutputStream ();
		InputStream headerStream = h.getInputStream();
		oStream.write(reqheader.toString().getBytes());
		webheader = new HTTPHeader(headerStream);
		oStream.close();
		headerStream.close();
		h.close();
		String headStatus = webheader.getStatusLine().trim();
		int len = headStatus.length();
		if ( headStatus.equalsIgnoreCase("HTTP/1.1 304 Not Modified")) {
		    setMayCache (false);	
		    contentStream = new FileInputStream (entry.getFileName());
		    webheader = (HTTPHeader) entry.getDataHook();
		} else {
		    Proxy.getCache().remove(pxyheader);
		    setMayCache (true);
		    h = Proxy.getWebConnection (reqheader);
		    oStream = h.getOutputStream ();
		    oStream.write(reqheader.toString().getBytes());
		    contentStream = h.getInputStream();
		}
		} catch (FileNotFoundException e) {
		    contentStream = null;         // ignore sorta..
		} catch (IOException e) {
		    contentStream = null;
		}
	} //End of case - found in cache
	
	
	//If there is not entry in the cache then go to the web server
	//and get a copy of the desired page
	else {
	    try {
		//Get the header of the desired page
		WebConnection h = Proxy.getWebConnection (header);
		OutputStream oStream = h.getOutputStream ();
		InputStream headerStream = h.getInputStream();
		oStream.write(header.toString().getBytes());
		webheader = new HTTPHeader(headerStream);
		oStream.close();
		headerStream.close();
		h.close();
		
		do {
		    //Get a connection to the web server
		    wc = Proxy.getWebConnection (header);
		    
		    try {
			//Write the requested header to the web server
			outStream = wc.getOutputStream ();
			outStream.write (header.toString ().getBytes ());
		    }
		    catch (IOException e) {
			throw e;
		    }		
		    catch (Exception e) {
		    }
		    
		    try {
			//Get the header returned by the web server
			contentStream = wc.getInputStream ();
		    }
		    catch (Exception e) {
		    }
		} while (wc == null);
	    }
	    catch (IOException e) {
		return;
	    }
	    
	} // End of the not in cache option
	    
	    //Handle sending the response to the client
	    BaseHandler cacheHandler = new BaseHandler (header, webheader,contentStream,out,getMayCache(),true);
	    try {
		if (cacheHandler != null) {
		    cacheHandler.handle();
		    out.close();
		}
	    }
	    catch (IOException e) {
		return;
	    }
    }
    
    /** Set keepalive to a new value. Note that keepalive can only be
     *	promoted down. 
     * @param keepalive the new keepalive value.
     */
    public void setKeepalive (boolean keepalive) {
	this.keepalive = (this.keepalive && keepalive);
    }
    
    /** Set the state of this request. This can only be promoted down.
     * @param cacheAllowed true if we may cache the response, false otherwise.
     */
    public void setMayCache (boolean cacheAllowed) {
	maycache = cacheAllowed && maycache;
    }
  
    /** Get the state of this request.
     * @return true if we may cache the response, false otherwise.
     */
    public boolean getMayCache () {
	return maycache;
    }    
}
