package context.arch.comm.protocol;

import java.net.Socket;
import java.io.BufferedReader;
import java.io.StringReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
import java.util.Date;
import java.io.IOException;

import context.arch.comm.CommunicationsServer;
import context.arch.comm.CommunicationsObject;
import context.arch.comm.DataObject;

/**
 * This class subclasses TCPServerSocket, listening for and handling HTTP requests.
 * It implements the CommunicationsServer interface
 *
 * @see context.arch.comm.protocol.TCPServerSocket
 * @see context.arch.comm.CommunicationsServer
 */
public class HTTPServerSocket extends TCPServerSocket implements CommunicationsServer {

  /**
   * Debug flag. Set to true to see debug messages.
   */
  private static final boolean DEBUG = false;

  /**
   * Default port for HTTP communications is 80
   */
  public static final int DEFAULT_PORT = 80;

  /**
   * The protocol being used is HTTP 
   */
  public static final String PROTOCOL = "HTTP";

  /**
   * HTTP GET request type 
   */
  public static final String GET = "GET";
  
  /**
   * HTTP POST request type
   */
  public static final String POST = "POST";

  private int port;
  private CommunicationsObject commObject;

  /** 
   * Basic constructor for HTTPServerSocket that calls TCPServerSocket
   * 
   * @param object Handle of the generic instantiating communications object
   * @see #DEFAULT_PORT
   * @see context.arch.comm.protocol.TCPServerSocket
   */
  public HTTPServerSocket(CommunicationsObject object) { 
    super(DEFAULT_PORT);
    commObject = object;
    port = DEFAULT_PORT;
  }

  /** 
   * Constructor for HTTPServerSocket that calls TCPServerSocket with the
   * given port
   *
   * @param object Handle of the generic instantiating communications object
   * @param port Port to use to receive communications on
   * @see context.arch.comm.protocol.TCPServerSocket
   */
  public HTTPServerSocket(CommunicationsObject object, Integer port) { 
    super(port.intValue());
    commObject = object;
    this.port = port.intValue();
  }

  /** 
   * Stub method that handles incoming HTTP requests.  It calls the equivalent
   * method in the CommunicationsObject and then closes the socket.
   *
   * @param dataSocket Socket to receive HTTP data from
   * @see context.arch.comm.CommunicationsObject#handleIncomingRequest(java.net.Socket)
   */
  public void handleIncomingRequest(Socket dataSocket) {
    commObject.handleIncomingRequest(dataSocket);
    try {
      dataSocket.close();
    } catch (IOException ioe) {
      System.out.println("Couldn't close socket: "+ioe);
    }
  }

  /** 
   * Method that takes a reply message and adds the necessary HTTP protocol
   *
   * @param data Reply to a received request
   * @return the reply with the added HTTP protocol
   * @exception context.arch.comm.protocol.ProtocolException if the protocol
   *		can not be added
   */
  public String addReplyProtocol(String data) throws ProtocolException {
    StringBuffer sb = new StringBuffer();
    sb.append("HTTP/1.0 200 OK\r\n");
    Date now = new Date();
    sb.append("Date: "+now+"\r\n");
    sb.append("Server: context/1.0\r\n");
    sb.append("Content-type: text/xml\r\n");
    sb.append("Content-length: "+data.length()+"\r\n\r\n"); // AKD added
    sb.append(data);
    return (sb.toString());
  }

  /** 
   * Method that strips the HTTP protocol from a request message.  This only
   * deals with GET and POST headers.  If any other header is received, it will
   * throw a ProtocolException
   *
   * @param data Socket the request is coming from
   * @return the request with the HTTP protocol stripped away
   * @exception context.arch.comm.protocol.ProtocolException if the protocol
   *		can not be stripped away
   */
  public RequestData stripRequestProtocol(Socket data) throws ProtocolException {
    String method;
    String version = "";
    BufferedReader bufferedReader = null;
    try {
      bufferedReader = new BufferedReader(new InputStreamReader(data.getInputStream()), 2048);
      String get = bufferedReader.readLine();
      StringTokenizer tokenizer = new StringTokenizer(get);
      int bytesRead = 0;
      int bytesReadThisTime = 0;
      char [] tempdata;

      method = tokenizer.nextToken();
      if (method.equals("GET")) { 
        String file= tokenizer.nextToken();
        if (tokenizer.hasMoreTokens()) {
          version = tokenizer.nextToken();
        }
    
        while ((get = bufferedReader.readLine()) != null) {
          if (get.trim().equals("")) {
            break;
          }
        }
        return new RequestData(GET,file,null);
      }
      else if (method.equals(POST)) {
        String file= tokenizer.nextToken();
        if (tokenizer.hasMoreTokens()) {
          version = tokenizer.nextToken();
        }
        // DS, 10/27/1998: I found out that some clients don't capitalize Content-Length correctly.
        // Thus, I've modified the following line from:
        // while (!(get.startsWith("Content-Length:"))) { to:
        while (!(get.toLowerCase().startsWith("content-length:"))) {
          get = bufferedReader.readLine();
        }
        int length = 0;
        try {
          length = new Integer(get.substring(15).trim()).intValue();
        } catch (NumberFormatException nfe) {
          System.out.println("RequestServerSocket run error: "+nfe);
          throw new ProtocolException();
        }

        if (DEBUG) {
          System.out.println ("Content-Length is: " + length);
        }
					
        while (!(get.trim().equals(""))) {
          get = bufferedReader.readLine();
        }

        char[] postdata = new char[length];
        
        tempdata = new char [length];

        while (bytesRead < length) {
          if (bufferedReader.ready()){
            int ix = bytesRead;	// index to current end of tempdata

            bytesReadThisTime = bufferedReader.read(postdata, 0, length);  // DS: check we've read what we should
            bytesRead += bytesReadThisTime;
        		
            if (DEBUG) {
              System.out.println ("read " + bytesReadThisTime + " more bytes, "+bytesRead);
            }
		
            for (int i = 0; i < bytesReadThisTime; i++) {
              tempdata[ix + i] = postdata[i];
            }

            postdata = new char[length];

//          Not sure why this is commented out // Daniel, 9/30/1998
//            if (bytesRead < length) {
//              System.out.println ("HTTPServerSocket stripRequestProtocol: could read only " + bytesRead + " bytes instead of " + length);
//              throw new ProtocolException ();
//            }
          }
        }

        String readerData = new String(tempdata);
// System.out.println("data: "+readerData);
	  StringReader reader = new StringReader(readerData);
        return new RequestData(RequestData.DECODE,file,reader);
      }
      else {
        System.out.println("HTTPServerSocket stripRequestProtocol: invalid protocol use");
        throw new ProtocolException();
      }
    } catch (IOException ioe) {
        System.out.println("HTTPServerSocket stripRequestProtocol IOException: "+ioe);
        throw new ProtocolException();
    }
  }

  /** 
   * This method generates an error message if a request can't
   * be handled properly, to the point where a contextual error message 
   * can still be sent as the reply.  NOT IMPLEMENTED YET, CURRENTLY RETURNS
   * EMPTY DATAOBJECT - AKD
   * @return error message in the form of a DataObject
   * @see #getFatalMessage()
   */
  public DataObject getErrorMessage() {
    return new DataObject();
  }

  /** 
   * This method generates an fatal message if a request can't
   * be handled properly, to the point where no contextual error message 
   * can be sent as the reply.  NOT IMPLEMENTED YET, CURRENTLY RETURNS
   * EMPTY STRING - AKD
   *
   * @return fatal error message
   * @see #getErrorMessage()
   */
  public String getFatalMessage() {
    return new String("");
  }

  /**
   * This method stops the server from receiving more data
   *
   * @see context.arch.comm.protocol.TCPServerSocket#stopServer()
   */
  public void quit() {
    super.stopServer();
  }

  /** 
   * Method to get the communications protocol being used
   *
   * @return communications protocol being used
   * @see #PROTOCOL
   */
  public String getProtocol() {
    return PROTOCOL;
  }

}
