package http.server;

import http.gen.HTTPReq;
import http.gen.HTTPResp;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import edu.neu.ccs.demeterf.demfgen.lib.Map;
import edu.neu.ccs.demeterf.demfgen.lib.List;

/** Represents the 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); }

    private ServerSocket socket;
    private Map<String, Method> dispatch;
    private Object target;
    private List<Thread> servants = List.create();
    private boolean single = false;
    private boolean done = false;

    /** Create a ServerThread... */
    protected ServerThread(Server serv, boolean sing, Map<String, Method> disp, Object hand) throws IOException {
        socket = new ServerSocket(serv.value());
        dispatch = disp; target = hand; single = sing; start();
    }

    /** Run!! */
    public void run(){
        while (!done) {
            try {
                p("Waiting for connection...");
                Socket req = socket.accept();
                p("Got One from: " + req.getInetAddress() + ":" + req.getPort());
                DispatchThread t = new DispatchThread(req, dispatch, target, this);
                addServant(t);
                t.start();
                try{
                    if(single)t.join();
                }catch(InterruptedException ie){ }
            } 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();
    }
    /** 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{
        done = true;
        socket.close();
        waitServants();
    }

    /** Handles the dispatch of a Request to a Server Method */
    private static class DispatchThread extends Thread {
        Socket sock;
        Map<String, Method> dispatch;
        Object target;
        ServerThread parent;

        DispatchThread(Socket s, Map<String, Method> disp, Object targ, ServerThread p) {
            sock = s; dispatch = disp; target = targ; parent = p;
        }

        public void run(){
            p("In Dispatch Thread...");
            try {
                HTTPReq req = HTTPReq.fromSocket(sock);
                String path = req.trimmedUrl();
                p("HTTP Parsed: " + path);
                HTTPResp res;
                if (!dispatch.containsKey(path)){
                    p("Unbound Path '"+path+"' Trying Default");
                    path = Path.EMPTY;
                }
                
                if (!dispatch.containsKey(path)){
                    p("No Default Path");
                    res = HTTPResp.error();
                } else {
                    Method m = dispatch.get(path);
                    if (m.getParameterTypes().length == 0)
                        res = (HTTPResp) m.invoke(target, new Object[] {});
                    else
                        res = (HTTPResp) m.invoke(target, new Object[] { req });
                }
                res.toSocket(sock);
                sock.close();
                parent.removeServant(this);
            } catch (Exception e){ throw new RuntimeException(e); }
        }
    }
}