namespace edu.neu.ccs.demeterf.http.server{

using System.Reflection;
using edu.neu.ccs.demeterf.lib;
using edu.neu.ccs.demeterf.util;
using edu.neu.ccs.demeterf.http.classes;
using System;
using System.Net.Sockets;

public class ServerDispatch{
    public static void p(String s){ Factory.p(s); }
    
    private Map<String, MethodInfo> dispatch;
    private Object target;
    
    private ServerDispatch(Map<String, MethodInfo> dis, Object targ){
        dispatch = dis; target = targ;
    }
    
    /** Create a dispatcher, selecting methods with the default argument types. */
    public static ServerDispatch create(Object handler){
        return create(handler,DefaultFormals);
    }
    /** Create a dispatcher, selecting methods with the given formal argument types. */
    public static ServerDispatch create(Object handler, Type[] formals){
        Type t = handler.GetType();
        if (!t.IsDefined(typeof(Server),true)) {
            throw Factory.error("Cannot Create Server for unannotated class '" + t.Name + "'");
        }
        return new ServerDispatch(List<MethodInfo>.create(t.GetMethods(Util.ReflectFlags))
                                  .fold(new Folder(formals),
                                        Map<String, MethodInfo>.create<String>()),
                                  handler);
    }

    public class Folder : List<MethodInfo>.Fold<Map<String, MethodInfo>>{
        public Folder(Type[] f){ formals = f; }
        Type[] formals;
        public override Map<String, MethodInfo> fold(MethodInfo m, Map<String, MethodInfo> res){
            if(!m.IsDefined(typeof(Path),true))
                return res;
            String path = check(m, formals);
            p("Mapping '" + path + "' to " + m.Name);
            return res.put(path, m);
        }
    }

    /** Handle a given HTTP request using this dispatcher (no socket) */
    public HTTPResp handle(HTTPReq req){ return handle(req,null); }
    
    /** Handle a given HTTP request using this dispatcher, passing the
     *    server's socket */
    public HTTPResp handle(HTTPReq req, Socket sock){
        try {
            String path = req.trimmedUrl();
            p("HTTP Path: " + path);
            HTTPResp res;
            if (!dispatch.containsKey(path)){
                p("Unbound Path '"+path+"' Trying Default");
                path = Path.EMPTY;
            }
        
            Object[] args;
            if (!dispatch.containsKey(path)){
                p("No Default Path");
                res = HTTPResp.error();
            } else {
                MethodInfo m = dispatch.get(path);
                switch(m.GetParameters().Length){
                case 0:args = new Object[] {};break;
                case 1:args = new Object[] { req };break;
                default:
                    if(sock == null)
                        throw Factory.error("Missing Expected Socket Argument");
                    args = new Object[] { req, sock };break;
                }
                res = (HTTPResp) m.Invoke(target, args);
            }
            return res;
        }catch(MethodAccessException e){
            return HTTPResp.error("Inaccessable Handler:"+e);
        }catch(Exception e){
            return HTTPResp.error(""+e);
        }
    }
    
    
    /** Minimal Parameters... */
    public static Type[] MinimalFormals = { typeof(HTTPReq) };
    /** Allowed Parameters... */
    public static Type[] DefaultFormals = { typeof(HTTPReq), typeof(Socket) };

    static Type[] parameterTypes(MethodInfo m) {
        ParameterInfo[] ps = m.GetParameters();
        Type[] ts = new Type[ps.Length];
        for(int i = 0; i < ps.Length; i++)
            ts[i] = ps[i].ParameterType;
        return ts;
    }

    /** Do a sanity check of the types of an annotated method */
    private static String check(MethodInfo m, Type[] formals){
        Path p = (Path)m.GetCustomAttributes(typeof(Path),true)[0];
        if (!typeof(HTTPResp).Equals(m.ReturnType)){
            throw Factory.error("Return Type is incorrect: '" + m.ReturnType + "'");
        }
        Type[] pars = parameterTypes(m);
        if (pars.Length > formals.Length) {
            throw Factory.error("Too many Parameters for Server Method '" + m.Name + "'");
        }

        checkParams(m.Name, 0, pars, formals);
        return p.value;
    }

    /** Check that the parameters of the method are fine... */
    private static void checkParams(String method, int i, Type[] ps, Type[] fmls){
        if (i >= ps.Length || i >= fmls.Length)return;
        
        if (!fmls[i].IsAssignableFrom(ps[i])) {
            throw Factory.error("Parameter #" + i + " of " + method + " is of an incorrect type. " + "Expecting '"
                    + fmls[i].Name + "'");
        }
        checkParams(method, i+1, ps, fmls);
    }
}

}