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

import java.io.IOException;
import java.net.Socket;
import java.lang.reflect.Field;

import edu.neu.ccs.demeterf.lib.List;
import java.lang.reflect.Method;

/** Web Server Factory class */
public class Factory {

    private Factory() {}

    static boolean verbose = false;

    /** Set to true in order to get Output for Server/Dispatch descisions. */
    public static void setVerbose(boolean v){
        verbose = v;
    }

    static void p(String s){
        if (verbose) {
            System.err.println(" ** " + s);
        }
    }

    /**
     * Create a new Server using the given Handler. The {@link edu.neu.ccs.demeterf.http.server.Server Server}
     * annotation is used to tag a class as an HTTP Server/Handler.  Within the class,
     * {@link edu.neu.ccs.demeterf.http.server.Port Port} Annotation is used to mark the port (an instance variable)
     * the Server will listen on, which is bound (read) at Server creation time.  Handler methods are annotated with
     * {@link edu.neu.ccs.demeterf.http.server.Path Path} to describe responses to various Path
     * requests. See {@link edu.neu.ccs.demeterf.http.Test Test} for an Example.
     */
    public static ServerThread create(Object handler) throws IOException{
        return create(handler, false);
    }

    /** Create a Single/Multi-threaded Server */
    public static ServerThread create(Object handler, boolean single) throws IOException{
        return create(handler, single, ServerThread.DEFAULT_BACKLOG);
    }
    /** Create a Single/Multi-threaded Server */
    public static ServerThread create(Object handler, boolean single, int backlog) throws IOException{
            Class<?> c = handler.getClass();
        if (!c.isAnnotationPresent(Server.class)) {
            throw error("Cannot Create Server for unannotated class '" + c.getCanonicalName() + "'");
        }
        int port = getPort(c, handler);
        long max = getMaxMsgSize(c, handler);        
        p("Server Port = " + port);
        p("Max MsgSize = " + max);
        List<Method> except = List.create(c.getDeclaredMethods()).filter(new Except());
        if(!except.isEmpty())
            ServerDispatch.check(except.top(), new Class<?>[]{Exception.class, Socket.class});
        return new ServerThread(port, single, ServerDispatch.create(handler), max,
                except.isEmpty()?null:except.top(), backlog);
    }

    private static class Except extends List.Pred<Method>{
        public boolean huh(Method m)
        { return m.isAnnotationPresent(ExceptionHandler.class); }
    } 
    
    /** Create a ServerDispatch for direct testing */
    public ServerDispatch localDispatcher(Object handler){
        return ServerDispatch.create(handler, ServerDispatch.MinimalFormals);
    }
    
    /** Get the Port number of the server handler instance */
    private static int getPort(Class c, Object server){
        for(Field ff:c.getDeclaredFields()){
            if(ff.isAnnotationPresent(Port.class))
                try{
                    ff.setAccessible(true);
                    if(ff.getType().equals(int.class) || ff.getType().equals(Integer.class))
                        return (Integer)ff.get(server);
                }catch(IllegalAccessException e){
                    throw error("Port Field '"+ff.getName()+"' is not accessible in " + c.getCanonicalName());
                }
        }
        throw error("No Port Field found in " + c.getCanonicalName());
    }
    /** Get the MaxBytes (request size) of the server handler instance */
    private static long getMaxMsgSize(Class c, Object server){
        for(Field ff:c.getDeclaredFields()){
            if(ff.isAnnotationPresent(MaxMessageSize.class))
                try{
                    ff.setAccessible(true);
                    if(ff.getType().equals(int.class) || ff.getType().equals(Integer.class))
                        return (Integer)ff.get(server);
                    if(ff.getType().equals(long.class) || ff.getType().equals(Long.class))
                        return (Long)ff.get(server);
                    throw error("Incorrect Type for MaxRequestSize ("+ff.getType().getSimpleName()+")");
                }catch(IllegalAccessException e){
                    throw error("MaxRequestSize Field '"+ff.getName()+"' is not accessible in " + c.getCanonicalName());
                }
        }
        return 0;
    }
    /** Create a RuntimeException to throw with the given Message */
    public static RuntimeException error(String s){ return new RuntimeException(s); }
    /** Create a RuntimeException to throw with the given Cause */
    public static RuntimeException error(Throwable t){ return new RuntimeException(t); }
}