package edu.neu.ccs.demeterf.http.server;

import edu.neu.ccs.demeterf.http.classes.HTTPReq;
import edu.neu.ccs.demeterf.http.classes.HTTPResp;
import edu.neu.ccs.demeterf.util.Util;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import edu.neu.ccs.demeterf.lib.List;
import java.lang.reflect.Method;

/** Represents the Listening/Dispatch portion of an HTTP Server.  Given a Map of Paths
 *    to Methods, it is responsible for calling methods when given a specific request.
 */
public class ServerThread extends Thread {
    static void p(String s){ Factory.p(s); }
    /** Set to true in order to get Output for Server/Dispatch descisions. */
    public static void setVerbose(boolean v){ Factory.setVerbose(v); }

    static final int DEFAULT_BACKLOG = 20;
    
    private ServerSocket socket;
    private ServerDispatch dispatch;
    private List<Thread> servants = List.create();
    private boolean single;
    private long maxRequest;
    private int backlog;
    private boolean done = false;
    private Method exHandler;
    
    /** Create a ServerThread... */
    protected ServerThread(int port, boolean sing, ServerDispatch disp, long max) throws IOException {
        this(port, sing, disp, max, null, DEFAULT_BACKLOG);
    }
    /** Create a ServerThread... */
    protected ServerThread(int port, boolean sing, ServerDispatch disp, long max, Method eh, int back) throws IOException {
        socket = new ServerSocket(port,back);
        dispatch = disp; single = sing;
        maxRequest = max; backlog = back; exHandler = eh;
        start();
    }

    /** Run!! */
    public void run(){
        while (!done) {
            try {
                p("Waiting for connection...");
                Socket req = socket.accept();
                p("Got One from: " + req.getInetAddress() + ":" + req.getPort());
                if(numServants() < backlog){
                    DispatchThread t = new DispatchThread(req, dispatch, this);
                    addServant(t);
                    t.start();
                    try{
                        if(single)t.join();
                    }catch(InterruptedException ie){ }
                }else{
                    // I would give an error response, but that takes too long
                    req.close();
                }
                Thread.yield();
            } catch (IOException e) {
                if(!done) {
                    System.err.println(" ServerThread Exception: " + e.getMessage());
                    done = true;
                }
            }
        }
    }
    /** Add a Servant Thread to the List */
    public synchronized void addServant(Thread t){ servants = servants.push(t); }
    /** Remove a Servant Thread from the List */
    public synchronized void removeServant(Thread t){
        servants = servants.remove(t);
        this.notify();
    }
    /** Remove a Servant Thread from the List */
    public synchronized int numServants(){ return servants.length(); }
    /** Await the completion of all Servant Threads */
    public synchronized void waitServants(){
        p("Waiting for Servants...");
        while(!servants.isEmpty()){
            try{ this.wait(1000);
            }catch(InterruptedException ie){}
            p("Still Waiting...");
        }
        p("Done");
    }
    /** Kill the Server listening thread, though workers will continue/complete */
    public void shutdown() throws IOException{ shutdown(true); }
    /** Kill the Server listening thread, maybe wait for workers */
    public void shutdown(boolean wait) throws IOException{
        done = true;
        socket.close();
        if(wait)
            waitServants();
    }

    /** Handles the dispatch of a Request to a Server Method */
    private static class DispatchThread extends Thread {
        Socket sock;
        ServerDispatch dispatch;
        ServerThread parent;

        DispatchThread(Socket s, ServerDispatch disp, ServerThread p) {
            sock = s; dispatch = disp; parent = p;
        }

        public void run(){
            p("In Dispatch Thread...");
            HTTPReq req = null;
            try {
                req = HTTPReq.fromSocket(sock,2000,parent.maxRequest);
            }catch(Exception e){
                if(parent.exHandler != null){
                    try{
                        HTTPResp res = Util.applyMethod(parent.exHandler, dispatch.getTarget(), new Object[]{e,sock});
                        res.toSocket(sock);
                        parent.removeServant(this);
                        sock.close();
                        return;
                    }catch (Exception ee){ e = ee; }
                    return;
                }
                this.parent.removeServant(this);
                try{ sock.close(); }catch(Exception ee){}
                return;
            }
            try{
                HTTPResp res = dispatch.handle(req, sock);
                res.toSocket(sock);
                sock.close();
            }catch (Exception e){
                this.parent.removeServant(this);
                throw new ResponseException(e);
            }
            parent.removeServant(this);
        }
    }
}