/*
  * Called to handle a given web request.  The system uses IHttpRequest to allow for different web server 
  * systems.  It checks to see if the url is for jquery,json or backbone, if none of those, it generates the required model 
  * javascript for the given url, as specified by an attribute or attributes in Models, that define the 
  * url that the model/view/collection javascript is available on.
  * If the call is not for javascript it processes the given action using the http method (GET = load,
  * DELETE = delete, POST = create, PUT = update, SELECT = SelectList, SMETHOD = StaticMethodCall, METHOD = MethodCall)
  */
 public static void HandleRequest(IHttpRequest request)
 {
     Logger.Debug("Handling backbone request for path " + request.URL.ToString());
     int status=-1;
     string message=null;
     bool allowNullResponse = false;
     if (request.URL.AbsolutePath.EndsWith(".js") && request.Method.ToUpper() == "GET")
     {
         if (request.IsJsURLAllowed(Uri.UnescapeDataString(request.URL.AbsolutePath), out status, out message))
         {
             message = null;
             request.SetResponseContentType("text/javascript");
             if (Uri.UnescapeDataString(request.URL.AbsolutePath) == (_jqueryURL == null ? "" : _jqueryURL))
             {
                 Logger.Trace("Sending jquery javascript response through backbone handler");
                 request.SetResponseStatus(200);
                 request.WriteContent(Utility.ReadEmbeddedResource("Org.Reddragonit.BackBoneDotNet.resources.jquery.min.js",false));
                 request.SendResponse();
             }
             else if (Uri.UnescapeDataString(request.URL.AbsolutePath) == (_jsonURL == null ? "" : _jsonURL))
             {
                 Logger.Trace("Sending json javascript response through backbone handler");
                 request.SetResponseStatus(200);
                 request.WriteContent(Utility.ReadEmbeddedResource("Org.Reddragonit.BackBoneDotNet.resources.json2.min.js",false));
                 request.SendResponse();
             }
             else if (Uri.UnescapeDataString(request.URL.AbsolutePath) == (_backboneURL == null ? "" : _backboneURL))
             {
                 Logger.Trace("Sending modified backbone javascript response through backbone handler");
                 request.SetResponseStatus(200);
                 request.WriteContent(Utility.ReadEmbeddedResource("Org.Reddragonit.BackBoneDotNet.resources.backbone_combined.min.js",false));
                 request.SendResponse();
             }
             else
             {
                 bool found = false;
                 lock (_CachedJScript)
                 {
                     if (_CachedJScript.ContainsKey(request.URL.Host + Uri.UnescapeDataString(request.URL.AbsolutePath)))
                     {
                         Logger.Trace("Sending cached javascript response for path " + request.URL.Host + request.URL.AbsolutePath);
                         found = true;
                         request.SetResponseStatus(200);
                         request.WriteContent((string)_CachedJScript[request.URL.Host + Uri.UnescapeDataString(request.URL.AbsolutePath)].Value);
                         request.SendResponse();
                     }
                 }
                 if (!found)
                 {
                     Logger.Trace("Buidling model javascript for path " + request.URL.Host + Uri.UnescapeDataString(request.URL.AbsolutePath));
                     StringBuilder sb = new StringBuilder();
                     foreach (Type t in Utility.LocateTypeInstances(typeof(IModel)))
                     {
                         foreach (ModelJSFilePath mj in t.GetCustomAttributes(typeof(ModelJSFilePath), false))
                         {
                             if ((mj.Host == "*" || mj.Host == request.URL.Host) && 
                                 (mj.Path == Uri.UnescapeDataString(request.URL.AbsolutePath)||mj.MinPath==Uri.UnescapeDataString(request.URL.AbsolutePath)))
                             {
                                 Logger.Trace("Appending model " + t.FullName + " to path " + request.URL.Host + Uri.UnescapeDataString(request.URL.AbsolutePath));
                                 sb.Append(_GenerateModelJSFile(t, request.URL.Host, mj.MinPath == Uri.UnescapeDataString(request.URL.AbsolutePath) || Settings.Default.CompressAllJS));
                             }
                         }
                     }
                     request.SetResponseStatus(200);
                     request.WriteContent(sb.ToString());
                     lock (_CachedJScript)
                     {
                         if (!_CachedJScript.ContainsKey(request.URL.Host + request.URL.AbsolutePath))
                             _CachedJScript.Add(request.URL.Host + Uri.UnescapeDataString(request.URL.AbsolutePath), new CachedItemContainer(sb.ToString()));
                     }
                     request.SendResponse();
                 }
             }
         }
         else
             _SetSecurityError(out status, out message);
     }
     else
     {
         Object ret = null;
         switch (request.Method.ToUpper())
         {
             case "GET":
                 Logger.Trace("Attempting to handle GET request");
                 if (_RPC_SELECT.IsMatch(request.Method.ToUpper(), request.URL.Host, request.URL.AbsolutePath + (request.URL.Query != "" ? request.URL.Query : "")))
                 {
                     Logger.Trace("Handling request as a model list method");
                     foreach (sModelListCall mlc in _ModelListCalls)
                     {
                         if (mlc.HandlesRequest(request))
                         {
                             if (request.IsListAllowed(mlc.ModelType, out status, out message))
                             {
                                 message = null;
                                 ret = mlc.HandleRequest(request);
                             }
                             else
                                 _SetSecurityError(out status, out message);
                             break;
                         }
                     }
                 }
                 else
                 {
                     if (_LoadAlls.ContainsKey(request.URL.Host + request.URL.AbsolutePath))
                     {
                         Logger.Trace("Handling Load All request");
                         if (request.IsLoadAllAllowed(_LoadAlls[request.URL.Host + request.URL.AbsolutePath].DeclaringType, out status, out message))
                         {
                             message = null;
                             ret = _LoadAlls[request.URL.Host + request.URL.AbsolutePath].Invoke(null, new object[0]);
                         }
                         else
                             _SetSecurityError(out status, out message);
                     }
                     else if (_LoadAlls.ContainsKey("*" + request.URL.AbsolutePath))
                     {
                         Logger.Trace("Handling Load All request");
                         if (request.IsLoadAllAllowed(_LoadAlls["*" + request.URL.AbsolutePath].DeclaringType, out status, out message))
                         {
                             message = null;
                             ret = _LoadAlls["*" + request.URL.AbsolutePath].Invoke(null, new object[0]);
                         }
                         else
                             _SetSecurityError(out status, out message);
                     }
                     else if (_Loads.ContainsKey(request.URL.Host + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))))
                     {
                         Logger.Trace("Handling Load request");
                         if (request.IsLoadAllowed(_Loads[request.URL.Host + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))].DeclaringType, Uri.UnescapeDataString(request.URL.AbsolutePath.Substring(request.URL.AbsolutePath.LastIndexOf("/") + 1)), out status, out message))
                         {
                             message = null;
                             ret = _Loads[request.URL.Host + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))].Invoke(null, new object[] { Uri.UnescapeDataString(request.URL.AbsolutePath.Substring(request.URL.AbsolutePath.LastIndexOf("/") + 1)) });
                         }
                         else
                             _SetSecurityError(out status, out message);
                     }
                     else if (_Loads.ContainsKey("*" + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))))
                     {
                         Logger.Trace("Handling Load request");
                         if (request.IsLoadAllowed(_Loads["*" + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))].DeclaringType, Uri.UnescapeDataString(request.URL.AbsolutePath.Substring(request.URL.AbsolutePath.LastIndexOf("/") + 1)), out status, out message))
                         {
                             message = null;
                             ret = _Loads["*" + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))].Invoke(null, new object[] { Uri.UnescapeDataString(request.URL.AbsolutePath.Substring(request.URL.AbsolutePath.LastIndexOf("/") + 1)) });
                         }
                         else
                             _SetSecurityError(out status, out message);
                     }
                 }
                 break;
             case "SELECT":
                 Logger.Trace("Handling Select List request");
                 if (_SelectLists.ContainsKey(request.URL.Host + request.URL.AbsolutePath))
                 {
                     foreach (sModelListCall mlc in _SelectLists[request.URL.Host + request.URL.AbsolutePath])
                     {
                         if (mlc.HandlesRequest(request))
                         {
                             if (request.IsSelectAllowed(mlc.ModelType, out status, out message))
                             {
                                 message = null;
                                 ret = mlc.HandlesRequest(request);
                             }
                             else
                                 _SetSecurityError(out status, out message);
                             break;
                         }
                     }
                 }
                 else if (_SelectLists.ContainsKey("*" + request.URL.AbsolutePath))
                 {
                     foreach (sModelListCall mlc in _SelectLists["*" + request.URL.AbsolutePath])
                     {
                         if (mlc.HandlesRequest(request))
                         {
                             if (request.IsSelectAllowed(mlc.ModelType, out status, out message))
                             {
                                 message = null;
                                 ret = mlc.HandleRequest(request);
                             }
                             else
                                 _SetSecurityError(out status, out message);
                             break;
                         }
                     }
                 }
                 break;
             case "PUT":
             case "PATCH":
                 Logger.Trace("Handling Put request");
                 if (_Loads.ContainsKey(request.URL.Host + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))))
                 {
                     if (request.IsLoadAllowed(_Loads[request.URL.Host + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))].DeclaringType, Uri.UnescapeDataString(request.URL.AbsolutePath.Substring(request.URL.AbsolutePath.LastIndexOf("/") + 1)), out status, out message))
                     {
                         message = null;
                         ret = _Loads[request.URL.Host + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))].Invoke(null, new object[] { Uri.UnescapeDataString(request.URL.AbsolutePath.Substring(request.URL.AbsolutePath.LastIndexOf("/") + 1)) });
                     }
                     else
                         _SetSecurityError(out status, out message);
                 }
                 else if (_Loads.ContainsKey("*" + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))))
                 {
                     if (request.IsLoadAllowed(_Loads["*" + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))].DeclaringType, Uri.UnescapeDataString(request.URL.AbsolutePath.Substring(request.URL.AbsolutePath.LastIndexOf("/") + 1)), out status, out message))
                     {
                         message = null;
                         ret = _Loads["*" + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))].Invoke(null, new object[] { Uri.UnescapeDataString(request.URL.AbsolutePath.Substring(request.URL.AbsolutePath.LastIndexOf("/") + 1)) });
                     }
                     else
                         _SetSecurityError(out status, out message);
                 }
                 if (ret != null)
                 {
                     Hashtable ht = (Hashtable)JSON.JsonDecode(request.ParameterContent);
                     if (request.IsUpdateAllowed((IModel)ret, ht, out status, out message))
                     {
                         message = null;
                         Hashtable IModelTypes = new Hashtable();
                         foreach (string str in ht.Keys)
                         {
                             if (str != "id")
                             {
                                 if (ret.GetType().GetProperty(str).GetCustomAttributes(typeof(ReadOnlyModelProperty), true).Length == 0)
                                 {
                                     Type propType = ret.GetType().GetProperty(str).PropertyType;
                                     if (propType.IsArray)
                                         propType = propType.GetElementType();
                                     else if (propType.IsGenericType)
                                     {
                                         if (propType.GetGenericTypeDefinition() == typeof(List<>))
                                             propType = propType.GetGenericArguments()[0];
                                     }
                                     var obj = _ConvertObjectToType(ht[str], ret.GetType().GetProperty(str).PropertyType);
                                     if (new List<Type>(propType.GetInterfaces()).Contains(typeof(IModel)))
                                         IModelTypes.Add(str, obj);
                                     ret.GetType().GetProperty(str).SetValue(ret, obj, new object[0]);
                                 }
                             }
                         }
                         if (_UpdateMethods.ContainsKey(ret.GetType()))
                             ret = _UpdateMethods[ret.GetType()].Invoke(ret, new object[0]);
                         else
                             ret = false;
                         if ((bool)ret && IModelTypes.Count > 0)
                             ret = IModelTypes;
                         else if (!(bool)ret)
                             ret = null;
                     }
                     else
                         _SetSecurityError(out status, out message);
                 }
                 break;
             case "DELETE":
                 Logger.Trace("Handling Delete request");
                 if (_Loads.ContainsKey(request.URL.Host + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))))
                 {
                     if (request.IsLoadAllowed(_Loads[request.URL.Host + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))].DeclaringType, Uri.UnescapeDataString(request.URL.AbsolutePath.Substring(request.URL.AbsolutePath.LastIndexOf("/") + 1)), out status, out message))
                     {
                         message = null;
                         ret = _Loads[request.URL.Host + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))].Invoke(null, new object[] { Uri.UnescapeDataString(request.URL.AbsolutePath.Substring(request.URL.AbsolutePath.LastIndexOf("/") + 1)) });
                     }
                     else
                         _SetSecurityError(out status, out message);
                 }
                 else if (_Loads.ContainsKey("*" + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))))
                 {
                     if (request.IsLoadAllowed(_Loads["*" + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))].DeclaringType, Uri.UnescapeDataString(request.URL.AbsolutePath.Substring(request.URL.AbsolutePath.LastIndexOf("/") + 1)), out status, out message))
                     {
                         message = null;
                         ret = _Loads["*" + request.URL.AbsolutePath.Substring(0, request.URL.AbsolutePath.LastIndexOf("/"))].Invoke(null, new object[] { Uri.UnescapeDataString(request.URL.AbsolutePath.Substring(request.URL.AbsolutePath.LastIndexOf("/") + 1)) });
                     }
                     else
                         _SetSecurityError(out status, out message);
                 }
                 if (ret != null)
                 {
                     if (_DeleteMethods.ContainsKey(ret.GetType()))
                     {
                         if (request.IsDeleteAllowed(ret.GetType(), ((IModel)ret).id, out status, out message))
                         {
                             message = null;
                             ret = _DeleteMethods[ret.GetType()].Invoke(ret, new object[0]);
                         }
                         else
                             _SetSecurityError(out status, out message);
                     }
                     else
                         ret = null;
                 }
                 break;
             case "POST":
                 Logger.Trace("Handling Post request");
                 Type t = null;
                 if (_TypeMaps.ContainsKey(request.URL.Host + request.URL.AbsolutePath))
                     t = _TypeMaps[request.URL.Host + request.URL.AbsolutePath];
                 else if (_TypeMaps.ContainsKey("*"+request.URL.AbsolutePath))
                     t = _TypeMaps["*"+request.URL.AbsolutePath];
                 if (t != null)
                 {
                     Hashtable mht = (Hashtable)JSON.JsonDecode(request.ParameterContent);
                     if (request.IsSaveAllowed(t, mht, out status, out message))
                     {
                         message = null;
                         IModel mod = (IModel)t.GetConstructor(Type.EmptyTypes).Invoke(new object[0]);
                         foreach (string str in mht.Keys)
                         {
                             if (str != "id")
                             {
                                 PropertyInfo pi = t.GetProperty(str);
                                 if (pi.CanWrite)
                                     t.GetProperty(str).SetValue(mod, _ConvertObjectToType(mht[str], t.GetProperty(str).PropertyType), new object[0]);
                             }
                         }
                         if (_SaveMethods.ContainsKey(t))
                         {
                             if ((bool)_SaveMethods[t].Invoke(mod, new object[0]))
                             {
                                 ret = new Hashtable();
                                 ((Hashtable)ret).Add("id", mod.id);
                             }
                             else
                             {
                                 message = "Error saving model.";
                                 status = 500;
                                 ret = null;
                             }
                         }
                         else
                             ret = null;
                     }
                     else
                         _SetSecurityError(out status, out message);
                 }
                 break;
             case "METHOD":
             case "SMETHOD":
                 Hashtable pars = (Hashtable)JSON.JsonDecode(request.ParameterContent);
                 string url = request.URL.AbsolutePath;
                 string method = url.Substring(url.LastIndexOf("/")+1);
                 url = url.Substring(0,url.Length-method.Length-1);
                 List<MethodInfo> methods = new List<MethodInfo>();
                 if (request.Method.ToUpper() == "METHOD")
                 {
                     string id = url.Substring(url.LastIndexOf("/") + 1);
                     url = url.Substring(0, url.Length - id.Length - 1);
                     id = Uri.UnescapeDataString(id);
                     if (_Loads.ContainsKey(request.URL.Host + url))
                     {
                         if (request.IsLoadAllowed(_Loads[request.URL.Host + url].DeclaringType, id, out status, out message))
                         {
                             message = null;
                             ret = _Loads[request.URL.Host + url].Invoke(null, new object[] { id });
                         }
                         else
                             _SetSecurityError(out status, out message);
                     }
                     else if (_Loads.ContainsKey("*" + url))
                     {
                         if (request.IsLoadAllowed(_Loads["*" + url].DeclaringType, id, out status, out message))
                         {
                             message = null;
                             ret = _Loads["*" + url].Invoke(null, new object[] { id });
                         }
                         else
                             _SetSecurityError(out status, out message);
                     }
                     if (ret != null)
                     {
                         if (request.IsExposedMethodAllowed((IModel)ret, method, pars, out status, out message))
                         {
                             foreach (MethodInfo m in ret.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public))
                             {
                                 if (m.Name == method)
                                 {
                                     if (m.GetCustomAttributes(typeof(ExposedMethod), false).Length > 0)
                                         methods.Add(m);
                                 }
                             }
                         }
                         else
                             ret = null;
                     }
                 }
                 else
                 {
                     ret = null;
                     if (_ExposedMethods.ContainsKey(request.URL.Host + url + "/" + method))
                         methods.AddRange(_ExposedMethods[request.URL.Host + url + "/" + method]);
                     else if (_ExposedMethods.ContainsKey("*" + url + "/" + method))
                         methods.AddRange(_ExposedMethods["*" + url + "/" + method]);
                     if (methods.Count > 0)
                     {
                         if (!request.IsStaticExposedMethodAllowed(methods[0].DeclaringType, method, pars, out status, out message))
                         {
                             methods.Clear();
                             _SetSecurityError(out status, out message);
                         }
                     }
                 }
                 if (methods.Count>0)
                 {
                     object[] opars = null;
                     MethodInfo mi = null;
                     if (pars == null || pars.Count == 0)
                     {
                         foreach (MethodInfo m in methods)
                         {
                             if (m.GetParameters().Length == 0)
                             {
                                 mi = m;
                                 break;
                             }
                         }
                         opars = new object[0];
                     }
                     else
                     {
                         opars = new object[pars.Count];
                         foreach (MethodInfo m in methods)
                         {
                             if (m.GetParameters().Length == pars.Count)
                             {
                                 bool isMethod = true;
                                 int index = 0;
                                 foreach (ParameterInfo pi in m.GetParameters())
                                 {
                                     if (pars.ContainsKey(pi.Name))
                                         opars[index] = _ConvertObjectToType(pars[pi.Name], pi.ParameterType);
                                     else
                                     {
                                         isMethod = false;
                                         break;
                                     }
                                     index++;
                                 }
                                 if (isMethod)
                                 {
                                     mi = m;
                                     break;
                                 }
                             }
                         }
                     }
                     if (mi == null)
                     {
                         message = "Unable to locate requested method to invoke";
                         status = 404;
                         ret = null;
                     }
                     else if (mi.GetCustomAttributes(typeof(ExposedMethod), false).Length == 0)
                     {
                         message = "Unable to locate requested method to invoke";
                         status = 404;
                         ret = null;
                     }
                     else
                     {
                         allowNullResponse = ((ExposedMethod)mi.GetCustomAttributes(typeof(ExposedMethod), false)[0]).AllowNullResponse;
                         message = null;
                         try
                         {
                             if (mi.ReturnType == typeof(void))
                             {
                                 mi.Invoke(ret, opars);
                                 ret = new object();
                             }
                             else
                                 ret = mi.Invoke(ret, opars);
                         }
                         catch (Exception ex)
                         {
                             message = ex.Message;
                             status = 500;
                         }
                     }
                 }
                 break;
         }
         request.SetResponseContentType("application/json");
         if (ret != null || (allowNullResponse&&message==null))
         {
             Logger.Trace("Request successfully handled, sending response");
             request.SetResponseStatus(200);
             request.WriteContent(JSON.JsonEncode(_SetupAdditionalBackbonehash(ret,request)));
             request.SendResponse();
         }
         else
         {
             if (message!=null){
                 request.SetResponseStatus(status);
                 request.WriteContent(JSON.JsonEncode(message));
             }else{
                 Logger.Trace("Handling of request failed, sending 404 response");
                 request.SetResponseStatus(404);
                 request.WriteContent(JSON.JsonEncode("Not Found"));
             }
             request.SendResponse();
         }
     }
 }