/* JFox, the OpenSource J2EE Application Server
 *
 * Copyright (C) 2002 huihoo.com
 * Distributable under GNU LGPL license
 * See the GNU Lesser General Public License for more details.
 */

package org.jfox.mx.http;

import java.io.IOException;
import java.io.DataOutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.net.Socket;
import java.util.Properties;
import java.util.StringTokenizer;

import org.jfox.mx.MxServer;

/**
 *
 * @author <a href="mailto:young_yy@hotmail.com">Young Yang</a>
 */
public class WebServer implements Runnable {
  private int port = 8082;

  /** The serverSocket listen queue depth */
  private int backlog = 50;

  /** The web server http listening socket */
  private ServerSocket ssocket = null;
  /** The thread pool used to manage listening threads */
  private final ThreadPool threadPool = new ThreadPool();

  private MxServer server = null;

  void setMxServer(MxServer server){
    this.server = server;
  }

  /** Set the http listening port
   */
  public void setPort(int p) {
    port = p;
  }

  /** Get the http listening port
   @return the http listening port
   */
  public int getPort() {
    return port;
  }

  public String getBindAddress() {
    String host = "localhost";
    try {
      host = InetAddress.getLocalHost().getHostAddress();
    }
    catch(UnknownHostException e){ }
    return host;
  }


  /** Get the server sockets listen queue depth
   @return the listen queue depth
   */
  public int getBacklog() {
    return backlog;
  }
  /** Set the server sockets listen queue depth
   */
  public void setBacklog(int backlog) {
    if( backlog <= 0 )
      backlog = 50;
    this.backlog = backlog;
  }

  /** Start the web server on port and begin listening for requests.
   */
  public void start() throws IOException {
    threadPool.enable();
    try {
      ssocket = new ServerSocket(port, backlog);
      Logger.log("started at " + port + " !");
      listen();
    }
    catch (IOException e) {
      throw e;
    }
  }

  /** Close the web server listening socket
   */
  public void stop() {
    try {
      ServerSocket srv = ssocket;
      ssocket = null;
      srv.close();
      Logger.log("stopped !");
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    threadPool.disable();
  }

  /** Listen threads entry point. Here we accept a client connection
   and located requested classes/resources using the class loader
   specified in the http request.
   */
  public void run() {
    // Return if the server has been stopped
    if (ssocket == null)
      return;

    // Accept a connection
    Socket socket = null;
    try {
      socket = ssocket.accept();
    }
    catch(Exception e) {
//      e.printStackTrace();
      return;
    }

    // Create a new thread to accept the next connection
    listen();

    try {
      // Get the request socket output stream
      DataOutputStream out = new DataOutputStream(socket.getOutputStream());
      try {
        String httpCode = "200 OK";
        // Get the requested item from the HTTP header
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        Properties queryMap = parseQueryString(getPath(in));
        ClassLoader loader = Thread.currentThread().getContextClassLoader();

        String className = queryMap.containsKey("action") ? queryMap.getProperty("action") + "Command" : "AgentCommand";

        String commClassName = this.getClass().getPackage().getName() + "." + className;
        String page = ((Command)loader.loadClass(commClassName).newInstance()).doGet(server, queryMap);

        try {
          // The HTTP 1.0 header
          out.writeBytes("HTTP/1.0 "+httpCode+"\r\n");
          out.writeBytes("Content-Length: " + page.length() + "\r\n");
          out.writeBytes("Content-Type: text/html");
          out.writeBytes("\r\n\r\n");
          // The response body
          out.write(page.toString().getBytes());
          out.flush();
        }
        catch (IOException ie) {
          return;
        }
      }
      catch(Throwable e) {
        e.printStackTrace();
        try {
          out.writeBytes("HTTP/1.0 400 " + e.getMessage() + "\r\n");
          out.writeBytes("Content-Type: text/html\r\n\r\n");
          out.flush();
        }
        catch (IOException ex) {
          // Ignore
        }
      }
    }
    catch (IOException ex) {
      ex.printStackTrace();
    }
    finally {
      // Close the client request socket
      try {
        socket.close();
      }
      catch (IOException e) {
      }
    }
  }

  private void listen() {
    threadPool.run(this);
  }

  /**
   @return the path portion of the HTTP request header.
   */
  private String getPath(BufferedReader in) throws IOException {
    String line = in.readLine();
//      log.trace("raw request="+line);
    // Find the request path by parsing the 'REQUEST_TYPE filePath HTTP_VERSION' string
    int start = line.indexOf(' ')+1;
    int end = line.indexOf(' ', start+1);
    // The file minus the leading '/'
    String filePath = line.substring(start+1, end);
    return decodeUrl(filePath);
  }

  /// decode url
  private String decodeUrl(String encodeURL)  {
    int length = encodeURL.length();
    char[] charArray = encodeURL.toCharArray();
    byte[] byteArray = new byte[length];
    int j = 0;
    boolean flag = true;
    for(int k = 0; k < length; k++) {
      char c = charArray[k];
      if(c < 0 || c > '\177')
        throw new IllegalArgumentException("Not encoded");
      byte byte0 = (byte)(0x7f & c);
      if(c == '+') {
        flag = false;
        byte0 = 32;
      }
      else
        if(c == '%' && k + 2 < length) {
          flag = false;
          byte0 = (byte)(0xff & Integer.parseInt(encodeURL.substring(k + 1, k + 3), 16));
          k += 2;
        }
      byteArray[j++] = byte0;
    }

    if(flag)
      return encodeURL;
    try {
      return new String(byteArray, 0, j, "ISO-8859-1");
    }
    catch(UnsupportedEncodingException unsupportedencodingexception) {
      return new String(byteArray, 0, j);
    }
  }

  private Properties parseQueryString(String qstring){
    Properties prop = new Properties();
    if(qstring == null || qstring.trim().length() == 0) return prop;
    if(qstring.startsWith("?")) qstring = qstring.substring(1);
    StringTokenizer st = new StringTokenizer(qstring,"&");
    while(st.hasMoreTokens()){
      String nvPair = st.nextToken();
      int index = nvPair.indexOf("=");
      if(index < 0){ // only key , not have value
        prop.setProperty(nvPair,"");
      }
      else{
        String name = nvPair.substring(0,index);
        String value = nvPair.substring(index+1);
        prop.setProperty(name,value);
      }
    }
    return prop;
  }

  public static void main(String[] args) throws Exception {
    new WebServer().start();
  }
}