package processing.app;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.zip.*;

//import javax.swing.SwingUtilities;

/**
 * This code is placed here in anticipation of running the reference from an
 * internal web server that reads the docs from a zip file, instead of using
 * thousands of .html files on the disk, which is really inefficient.
 * <p/>
 * This is a very simple, multi-threaded HTTP server, originally based on 
 * <a href="http://j.mp/6BQwpI">this</a> article on java.sun.com.
 */
public class WebServer implements HttpConstants {

    /* Where worker threads stand idle */
    static Vector threads = new Vector();
 
    /* the web server's virtual root */
    //static File root;

    /* timeout on client connections */
    static int timeout = 10000;

    /* max # worker threads */
    static int workers = 5;

//    static PrintStream log = System.out; 


    /*
    static void loadProps() throws IOException {
        File f = new File
                (System.getProperty("java.home")+File.separator+
                    "lib"+File.separator+"www-server.properties");
        if (f.exists()) {
            InputStream is =new BufferedInputStream(new
                           FileInputStream(f));
            props.load(is);
            is.close();
            String r = props.getProperty("root");
            if (r != null) {
                root = new File(r);
                if (!root.exists()) {
                    throw new Error(root + " doesn't exist as server root");
                }
            }
            r = props.getProperty("timeout");
            if (r != null) {
                timeout = Integer.parseInt(r);
            }
            r = props.getProperty("workers");
            if (r != null) {
                workers = Integer.parseInt(r);
            }
            r = props.getProperty("log");
            if (r != null) {
                p("opening log file: " + r);
                log = new PrintStream(new BufferedOutputStream(
                                      new FileOutputStream(r)));
            }
        }

        // if no properties were specified, choose defaults
        if (root == null) {
            root = new File(System.getProperty("user.dir"));
        }
        if (timeout <= 1000) {
            timeout = 5000;
        }
        if (workers < 25) {
            workers = 5;
        }
        if (log == null) {
            p("logging to stdout");
            log = System.out;
        }
    }

    static void printProps() {
        p("root="+root);
        p("timeout="+timeout);
        p("workers="+workers);
    }
    */

    
    /* print to stdout */
//    protected static void p(String s) {
//        System.out.println(s);
//    }

    /* print to the log file */
    protected static void log(String s) {
      if (false) {
        System.out.println(s);
      }
//        synchronized (log) {
//            log.println(s);
//            log.flush();
//        }
    }

    
    //public static void main(String[] a) throws Exception {
    static public int launch(String zipPath) throws IOException {
      final ZipFile zip = new ZipFile(zipPath);
      final HashMap<String, ZipEntry> entries = new HashMap();
      Enumeration en = zip.entries();
      while (en.hasMoreElements()) {
        ZipEntry entry = (ZipEntry) en.nextElement();
        entries.put(entry.getName(), entry);
      }
      
//        if (a.length > 0) {
//            port = Integer.parseInt(a[0]);
//        }
//        loadProps();
//        printProps();
        // start worker threads
        for (int i = 0; i < workers; ++i) {
            WebServerWorker w = new WebServerWorker(zip, entries);
            Thread t = new Thread(w, "Web Server Worker #" + i);
            t.start();
            threads.addElement(w);
        }

        final int port = 8080;

        //SwingUtilities.invokeLater(new Runnable() {
        Runnable r = new Runnable() {
          public void run() {
            try {
              ServerSocket ss = new ServerSocket(port);
              while (true) {
                Socket s = ss.accept();
                WebServerWorker w = null;
                synchronized (threads) {
                  if (threads.isEmpty()) {
                    WebServerWorker ws = new WebServerWorker(zip, entries);
                    ws.setSocket(s);
                    (new Thread(ws, "additional worker")).start();
                  } else {
                    w = (WebServerWorker) threads.elementAt(0);
                    threads.removeElementAt(0);
                    w.setSocket(s);
                  }
                }
              }
            } catch (IOException e) {
              e.printStackTrace();
            }
          }
        };
        new Thread(r).start();
//        });
        return port;
    }
}


class WebServerWorker /*extends WebServer*/ implements HttpConstants, Runnable {
  ZipFile zip;
  HashMap<String, ZipEntry> entries;
  
    final static int BUF_SIZE = 2048;

    static final byte[] EOL = { (byte)'\r', (byte)'\n' };

    /* buffer to use for requests */
    byte[] buf;
    /* Socket to client we're handling */
    private Socket s;

    WebServerWorker(ZipFile zip, HashMap entries) {
      this.entries = entries;
      this.zip = zip;
      
      buf = new byte[BUF_SIZE];
      s = null;
  }

//    Worker() {
//        buf = new byte[BUF_SIZE];
//        s = null;
//    }
//
    synchronized void setSocket(Socket s) {
        this.s = s;
        notify();
    }

    public synchronized void run() {
        while(true) {
            if (s == null) {
                /* nothing to do */
                try {
                    wait();
                } catch (InterruptedException e) {
                    /* should not happen */
                    continue;
                }
            }
            try {
                handleClient();
            } catch (Exception e) {
                e.printStackTrace();
            }
            /* go back in wait queue if there's fewer
             * than numHandler connections.
             */
            s = null;
            Vector pool = WebServer.threads;
            synchronized (pool) {
                if (pool.size() >= WebServer.workers) {
                    /* too many threads, exit this one */
                    return;
                } else {
                    pool.addElement(this);
                }
            }
        }
    }

    
    void handleClient() throws IOException {
        InputStream is = new BufferedInputStream(s.getInputStream());
        PrintStream ps = new PrintStream(s.getOutputStream());
        // we will only block in read for this many milliseconds
        // before we fail with java.io.InterruptedIOException,
        // at which point we will abandon the connection.
        s.setSoTimeout(WebServer.timeout);
        s.setTcpNoDelay(true);
        // zero out the buffer from last time
        for (int i = 0; i < BUF_SIZE; i++) {
            buf[i] = 0;
        }
        try {
          // We only support HTTP GET/HEAD, and don't support any fancy HTTP 
          // options, so we're only interested really in the first line.
            int nread = 0, r = 0;

outerloop:
            while (nread < BUF_SIZE) {
                r = is.read(buf, nread, BUF_SIZE - nread);
                if (r == -1) {
                    return;  // EOF
                }
                int i = nread;
                nread += r;
                for (; i < nread; i++) {
                    if (buf[i] == (byte)'\n' || buf[i] == (byte)'\r') {
                        break outerloop;  // read one line 
                    }
                }
            }

            /* are we doing a GET or just a HEAD */
            boolean doingGet;
            /* beginning of file name */
            int index;
            if (buf[0] == (byte)'G' &&
                buf[1] == (byte)'E' &&
                buf[2] == (byte)'T' &&
                buf[3] == (byte)' ') {
                doingGet = true;
                index = 4;
            } else if (buf[0] == (byte)'H' &&
                       buf[1] == (byte)'E' &&
                       buf[2] == (byte)'A' &&
                       buf[3] == (byte)'D' &&
                       buf[4] == (byte)' ') {
                doingGet = false;
                index = 5;
            } else {
                /* we don't support this method */
                ps.print("HTTP/1.0 " + HTTP_BAD_METHOD +
                           " unsupported method type: ");
                ps.write(buf, 0, 5);
                ps.write(EOL);
                ps.flush();
                s.close();
                return;
            }

            int i = 0;
            /* find the file name, from:
             * GET /foo/bar.html HTTP/1.0
             * extract "/foo/bar.html"
             */
            for (i = index; i < nread; i++) {
                if (buf[i] == (byte)' ') {
                    break;
                }
            }

            String fname = new String(buf, index, i-index);
            // get the zip entry, remove the front slash
            ZipEntry entry = entries.get(fname.substring(1));
            //System.out.println(fname + " " + entry);
            boolean ok = printHeaders(entry, ps);
            if (entry != null) {
              InputStream stream = zip.getInputStream(entry);
              if (doingGet && ok) {
                sendFile(stream, ps);
              }
            } else {
              send404(ps);
            }
            /*
            String fname = 
              (new String(buf, 0, index, i-index)).replace('/', File.separatorChar);
            if (fname.startsWith(File.separator)) {
                fname = fname.substring(1);
            }
            File targ = new File(WebServer.root, fname);
            if (targ.isDirectory()) {
                File ind = new File(targ, "index.html");
                if (ind.exists()) {
                    targ = ind;
                }
            }
            boolean OK = printHeaders(targ, ps);
            if (doingGet) {
                if (OK) {
                    sendFile(targ, ps);
                } else {
                    send404(targ, ps);
                }
            }
            */
        } finally {
            s.close();
        }
    }

    
    boolean printHeaders(ZipEntry targ, PrintStream ps) throws IOException {
      boolean ret = false;
      int rCode = 0;
      if (targ == null) {
          rCode = HTTP_NOT_FOUND;
          ps.print("HTTP/1.0 " + HTTP_NOT_FOUND + " Not Found");
          ps.write(EOL);
          ret = false;
      }  else {
          rCode = HTTP_OK;
          ps.print("HTTP/1.0 " + HTTP_OK + " OK");
          ps.write(EOL);
          ret = true;
      }
      if (targ != null) {
        WebServer.log("From " +s.getInetAddress().getHostAddress()+": GET " + targ.getName()+" --> "+rCode);
      }
      ps.print("Server: Processing Documentation Server");
      ps.write(EOL);
      ps.print("Date: " + (new Date()));
      ps.write(EOL);
      if (ret) {
          if (!targ.isDirectory()) {
              ps.print("Content-length: " + targ.getSize());
              ps.write(EOL);
              ps.print("Last Modified: " + new Date(targ.getTime()));
              ps.write(EOL);
              String name = targ.getName();
              int ind = name.lastIndexOf('.');
              String ct = null;
              if (ind > 0) {
                  ct = (String) map.get(name.substring(ind));
              }
              if (ct == null) {
                //System.err.println("unknown content type " + name.substring(ind));
                  ct = "application/x-unknown-content-type";
              }
              ps.print("Content-type: " + ct);
              ps.write(EOL);
          } else {
              ps.print("Content-type: text/html");
              ps.write(EOL);
          }
      }
      ps.write(EOL);  // adding another newline here [fry]
      return ret;
  }
    
    
    boolean printHeaders(File targ, PrintStream ps) throws IOException {
        boolean ret = false;
        int rCode = 0;
        if (!targ.exists()) {
            rCode = HTTP_NOT_FOUND;
            ps.print("HTTP/1.0 " + HTTP_NOT_FOUND + " Not Found");
            ps.write(EOL);
            ret = false;
        }  else {
            rCode = HTTP_OK;
            ps.print("HTTP/1.0 " + HTTP_OK+" OK");
            ps.write(EOL);
            ret = true;
        }
        WebServer.log("From " +s.getInetAddress().getHostAddress()+": GET " + targ.getAbsolutePath()+"-->"+rCode);
        ps.print("Server: Simple java");
        ps.write(EOL);
        ps.print("Date: " + (new Date()));
        ps.write(EOL);
        if (ret) {
            if (!targ.isDirectory()) {
                ps.print("Content-length: " + targ.length());
                ps.write(EOL);
                ps.print("Last Modified: " + new Date(targ.lastModified()));
                ps.write(EOL);
                String name = targ.getName();
                int ind = name.lastIndexOf('.');
                String ct = null;
                if (ind > 0) {
                    ct = (String) map.get(name.substring(ind));
                }
                if (ct == null) {
                    ct = "unknown/unknown";
                }
                ps.print("Content-type: " + ct);
                ps.write(EOL);
            } else {
                ps.print("Content-type: text/html");
                ps.write(EOL);
            }
        }
        return ret;
    }

    
    void send404(PrintStream ps) throws IOException {
        ps.write(EOL);
        ps.write(EOL);
        ps.print("<html><body><h1>404 Not Found</h1>"+
                   "The requested resource was not found.</body></html>");
        ps.write(EOL);
        ps.write(EOL);
    }

    
    void sendFile(File targ, PrintStream ps) throws IOException {
        InputStream is = null;
        ps.write(EOL);
        if (targ.isDirectory()) {
            listDirectory(targ, ps);
            return;
        } else {
            is = new FileInputStream(targ.getAbsolutePath());
        }
        sendFile(is, ps);
    }
    
    
    void sendFile(InputStream is, PrintStream ps) throws IOException {
        try {
            int n;
            while ((n = is.read(buf)) > 0) {
                ps.write(buf, 0, n);
            }
        } finally {
            is.close();
        }
    }

    /* mapping of file extensions to content-types */
    static java.util.Hashtable map = new java.util.Hashtable();

    static {
        fillMap();
    }
    static void setSuffix(String k, String v) {
        map.put(k, v);
    }

    static void fillMap() {
        setSuffix("", "content/unknown");

        setSuffix(".uu", "application/octet-stream");
        setSuffix(".exe", "application/octet-stream");
        setSuffix(".ps", "application/postscript");
        setSuffix(".zip", "application/zip");
        setSuffix(".sh", "application/x-shar");
        setSuffix(".tar", "application/x-tar");
        setSuffix(".snd", "audio/basic");
        setSuffix(".au", "audio/basic");
        setSuffix(".wav", "audio/x-wav");
        
        setSuffix(".gif", "image/gif");
        setSuffix(".jpg", "image/jpeg");
        setSuffix(".jpeg", "image/jpeg");
        
        setSuffix(".htm", "text/html");
        setSuffix(".html", "text/html");
        setSuffix(".css", "text/css"); 
        setSuffix(".java", "text/javascript");
        
        setSuffix(".txt", "text/plain");
        setSuffix(".java", "text/plain");
        
        setSuffix(".c", "text/plain");
        setSuffix(".cc", "text/plain");
        setSuffix(".c++", "text/plain");
        setSuffix(".h", "text/plain");
        setSuffix(".pl", "text/plain");
    }

    void listDirectory(File dir, PrintStream ps) throws IOException {
        ps.println("<TITLE>Directory listing</TITLE><P>\n");
        ps.println("<A HREF=\"..\">Parent Directory</A><BR>\n");
        String[] list = dir.list();
        for (int i = 0; list != null && i < list.length; i++) {
            File f = new File(dir, list[i]);
            if (f.isDirectory()) {
                ps.println("<A HREF=\""+list[i]+"/\">"+list[i]+"/</A><BR>");
            } else {
                ps.println("<A HREF=\""+list[i]+"\">"+list[i]+"</A><BR");
            }
        }
        ps.println("<P><HR><BR><I>" + (new Date()) + "</I>");
    }

}


interface HttpConstants {
    /** 2XX: generally "OK" */
    public static final int HTTP_OK = 200;
    public static final int HTTP_CREATED = 201;
    public static final int HTTP_ACCEPTED = 202;
    public static final int HTTP_NOT_AUTHORITATIVE = 203;
    public static final int HTTP_NO_CONTENT = 204;
    public static final int HTTP_RESET = 205;
    public static final int HTTP_PARTIAL = 206;

    /** 3XX: relocation/redirect */
    public static final int HTTP_MULT_CHOICE = 300;
    public static final int HTTP_MOVED_PERM = 301;
    public static final int HTTP_MOVED_TEMP = 302;
    public static final int HTTP_SEE_OTHER = 303;
    public static final int HTTP_NOT_MODIFIED = 304;
    public static final int HTTP_USE_PROXY = 305;

    /** 4XX: client error */
    public static final int HTTP_BAD_REQUEST = 400;
    public static final int HTTP_UNAUTHORIZED = 401;
    public static final int HTTP_PAYMENT_REQUIRED = 402;
    public static final int HTTP_FORBIDDEN = 403;
    public static final int HTTP_NOT_FOUND = 404;
    public static final int HTTP_BAD_METHOD = 405;
    public static final int HTTP_NOT_ACCEPTABLE = 406;
    public static final int HTTP_PROXY_AUTH = 407;
    public static final int HTTP_CLIENT_TIMEOUT = 408;
    public static final int HTTP_CONFLICT = 409;
    public static final int HTTP_GONE = 410;
    public static final int HTTP_LENGTH_REQUIRED = 411;
    public static final int HTTP_PRECON_FAILED = 412;
    public static final int HTTP_ENTITY_TOO_LARGE = 413;
    public static final int HTTP_REQ_TOO_LONG = 414;
    public static final int HTTP_UNSUPPORTED_TYPE = 415;

    /** 5XX: server error */
    public static final int HTTP_SERVER_ERROR = 500;
    public static final int HTTP_INTERNAL_ERROR = 501;
    public static final int HTTP_BAD_GATEWAY = 502;
    public static final int HTTP_UNAVAILABLE = 503;
    public static final int HTTP_GATEWAY_TIMEOUT = 504;
    public static final int HTTP_VERSION = 505;
}
