package org.jfox.framework.management.http;

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

import org.jfox.framework.Registry;
import org.jfox.framework.logger.Logger;

/**
 * @author <a href="mailto:yangyong@ufsoft.com.cn">Young Yang</a>
 */


public class ManagementHttpServer 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 Registry registry = null;

    private static Logger logger = Logger.getLogger(ManagementHttpServer.class.getName());

    void setRegistry(Registry registry) {
        this.registry = registry;
    }

    /**
     * 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.debug("web server 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.debug("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" : "IndexCommand";

                String commClassName = this.getClass().getPackage().getName() + "." + className;
                String page = ((Command) loader.loadClass(commClassName).newInstance()).doGet(registry, 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.shutdownOutput();
//        socket.shutdownInput();
                socket.close();
            }
            catch(Exception e) {
                e.printStackTrace();
            }
            finally {
                socket = null;
            }
        }
    }

    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 ManagementHttpServer().start();
    }
}

