//
// System.Web.HttpApplicationFactory
//
// Author:
//	Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// (c) 2002,2003 Ximian, Inc. (http://www.ximian.com)
// (c) Copyright 2004 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Web.UI;
using System.Web.SessionState;
using System.Web.Configuration;

using System.Web.Compilation;
#if TARGET_J2EE
using vmw.common;
#endif

#if NET_2_0 && !TARGET_J2EE
using System.CodeDom.Compiler;
#endif

namespace System.Web {
	class HttpApplicationFactory {
		object this_lock = new object ();
		
		// Initialized in InitType
#if TARGET_J2EE
		static HttpApplicationFactory theFactory {
			get
			{
				HttpApplicationFactory factory = (HttpApplicationFactory)AppDomain.CurrentDomain.GetData("HttpApplicationFactory");
				if (factory == null) {
					lock(typeof(HttpApplicationFactory)) {
						factory = (HttpApplicationFactory)AppDomain.CurrentDomain.GetData("HttpApplicationFactory");
						if (factory == null) {
							factory = new HttpApplicationFactory();
							System.Threading.Thread.Sleep(1);
							AppDomain.CurrentDomain.SetData("HttpApplicationFactory", factory);
						}
					}
				}
				return factory;
			}
		}
#else
		static HttpApplicationFactory theFactory = new HttpApplicationFactory();
#endif
		MethodInfo session_end;
		bool needs_init = true;
		bool app_start_needed = true;
		Type app_type;
		HttpApplicationState app_state;
		Hashtable app_event_handlers;
		static ArrayList watchers = new ArrayList();
		static object watchers_lock = new object();
		static bool app_shutdown = false;
#if NET_2_0
		static bool app_disabled = false;
		static string[] app_browsers_files = new string[0];
#endif
		Stack available = new Stack ();
		Stack available_for_end = new Stack ();
		
		bool IsEventHandler (MethodInfo m)
		{
			int pos = m.Name.IndexOf ('_');
			if (pos == -1 || (m.Name.Length - 1) <= pos)
				return false;

			if (m.ReturnType != typeof (void))
				return false;

			ParameterInfo [] pi = m.GetParameters ();
			int length = pi.Length;
			if (length == 0)
				return true;

			if (length != 2)
				return false;

			if (pi [0].ParameterType != typeof (object) ||
			    pi [1].ParameterType != typeof (EventArgs))
				return false;
			
			return true;
		}

		void AddEvent (MethodInfo method, Hashtable appTypeEventHandlers)
		{
			string name = method.Name.Replace ("_On", "_");
			if (appTypeEventHandlers [name] == null) {
				appTypeEventHandlers [name] = method;
				return;
			}

			MethodInfo old_method = appTypeEventHandlers [name] as MethodInfo;
			ArrayList list;
			if (old_method != null){
				list = new ArrayList (4);
				list.Add (old_method);
				appTypeEventHandlers [name] = list;
			} else 
				list = appTypeEventHandlers [name] as ArrayList;

			list.Add (method);
		}

		ArrayList GetMethodsDeep (Type type)
		{
			ArrayList al = new ArrayList ();
			MethodInfo[] methods = type.GetMethods (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance  | BindingFlags.Static | BindingFlags.FlattenHierarchy);
			al.AddRange (methods);

			Type t = type.BaseType;
			while (t != null) {
				methods = t.GetMethods (BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
				al.AddRange (methods);
				t = t.BaseType;
			}

			return al;
		}
		
		Hashtable GetApplicationTypeEvents (Type type)
		{
			lock (this_lock) {
				if (app_event_handlers != null)
					return app_event_handlers;

				app_event_handlers = new Hashtable ();
				ArrayList methods = GetMethodsDeep (type);
				Hashtable used = null;
				MethodInfo m;
				string mname;
				
				foreach (object o in methods) {
					m = o as MethodInfo;
					if (m.DeclaringType != typeof (HttpApplication) && IsEventHandler (m)) {
						mname = m.ToString ();
						if (used == null)
							used = new Hashtable ();
						else if (used.ContainsKey (mname))
							continue;
						used.Add (mname, m);
						AddEvent (m, app_event_handlers);
					}
				}
				used = null;
			}

			return app_event_handlers;
		}

		Hashtable GetApplicationTypeEvents (HttpApplication app)
		{
			lock (this_lock) {
				if (app_event_handlers != null)
					return app_event_handlers;

				return GetApplicationTypeEvents (app.GetType ());
			}
		}

		bool FireEvent (string method_name, object target, object [] args)
		{
			Hashtable possibleEvents = GetApplicationTypeEvents ((HttpApplication) target);
			MethodInfo method = possibleEvents [method_name] as MethodInfo;
			if (method == null)
				return false;

			if (method.GetParameters ().Length == 0)
				args = null;

			method.Invoke (target, args);

			return true;
		}

		HttpApplication FireOnAppStart (HttpContext context)
		{
			HttpApplication app = (HttpApplication) Activator.CreateInstance (app_type, true);
			context.ApplicationInstance = app;
			app.SetContext (context);
			object [] args = new object [] {app, EventArgs.Empty};
			app.InApplicationStart = true;
			FireEvent ("Application_Start", app, args);
			app.InApplicationStart = false;
			return app;
		}

		void FireOnAppEnd ()
		{
			if (app_type == null)
				return; // we didn't even get an application

			HttpApplication app = (HttpApplication) Activator.CreateInstance (app_type, true);
			FireEvent ("Application_End", app, new object [] {new object (), EventArgs.Empty});
			app.DisposeInternal ();
			app_type = null;
		}

		//
		// This is invoked by HttpRuntime.Dispose, when we unload an AppDomain
		// To reproduce this in action, touch "global.asax" while XSP is running.
		//
		public static void Dispose ()
		{
			theFactory.FireOnAppEnd ();
		}

		static FileSystemWatcher CreateWatcher (string file, FileSystemEventHandler hnd, RenamedEventHandler reh)
		{
			FileSystemWatcher watcher = new FileSystemWatcher ();

			watcher.Path = Path.GetFullPath (Path.GetDirectoryName (file));
			watcher.Filter = Path.GetFileName (file);
			
			// This will enable the Modify flag for Linux/inotify
			watcher.NotifyFilter |= NotifyFilters.Size;
			
			watcher.Changed += hnd;
			watcher.Created += hnd;
			watcher.Deleted += hnd;
			watcher.Renamed += reh;

			watcher.EnableRaisingEvents = true;

			return watcher;
		}

		internal static void AttachEvents (HttpApplication app)
		{
			HttpApplicationFactory factory = theFactory;
			Hashtable possibleEvents = factory.GetApplicationTypeEvents (app);
			foreach (string key in possibleEvents.Keys) {
				int pos = key.IndexOf ('_');
				string moduleName = key.Substring (0, pos);
				object target;
				if (moduleName == "Application") {
					target = app;
				} else {
					target = app.Modules [moduleName];
					if (target == null)
						continue;
				}

				string eventName = key.Substring (pos + 1);
				EventInfo evt = target.GetType ().GetEvent (eventName);
				if (evt == null)
					continue;

				string usualName = moduleName + "_" + eventName;
				object methodData = possibleEvents [usualName];
				if (methodData != null && eventName == "End" && moduleName == "Session") {
					lock (factory) {
						if (factory.session_end == null)
							factory.session_end = (MethodInfo) methodData;
					}
					continue;
				}

				if (methodData == null)
					continue;

				if (methodData is MethodInfo) {
					factory.AddHandler (evt, target, app, (MethodInfo) methodData);
					continue;
				}

				ArrayList list = (ArrayList) methodData;
				foreach (MethodInfo method in list)
					factory.AddHandler (evt, target, app, method);
			}
		}

		void AddHandler (EventInfo evt, object target, HttpApplication app, MethodInfo method)
		{
			int length = method.GetParameters ().Length;

			if (length == 0) {
				NoParamsInvoker npi = new NoParamsInvoker (app, method);
				evt.AddEventHandler (target, npi.FakeDelegate);
			} else {
				evt.AddEventHandler (target, Delegate.CreateDelegate (
							     evt.EventHandlerType, app,
#if NET_2_0
							     method
#else
							     method.Name
#endif
						     ));
			}
			
		}

		internal static void InvokeSessionEnd (object state)
		{
			InvokeSessionEnd (state, null, EventArgs.Empty);
		}
		
		internal static void InvokeSessionEnd (object state, object source, EventArgs e)
		{
			HttpApplicationFactory factory = theFactory;
			MethodInfo method = null;
			HttpApplication app = null;
			lock (factory.available_for_end) {
				method = factory.session_end;
				if (method == null)
					return;

				app = GetApplicationForSessionEnd ();
			}

			app.SetSession ((HttpSessionState) state);
			try {
				method.Invoke (app, new object [] {(source == null ? app : source), e});
			} catch (Exception) {
				// Ignore
			}
			RecycleForSessionEnd (app);
		}

		static HttpStaticObjectsCollection MakeStaticCollection (ArrayList list)
		{
			if (list == null || list.Count == 0)
				return null;

			HttpStaticObjectsCollection coll = new HttpStaticObjectsCollection ();
			foreach (ObjectTagBuilder tag in list) {
				coll.Add (tag);
			}

			return coll;
		}
		
		internal static HttpApplicationState ApplicationState {
#if TARGET_J2EE
			get {
				HttpApplicationFactory factory = theFactory;
				if (factory.app_state == null)
					factory.app_state = new HttpApplicationState (null, null);
				return factory.app_state;
			}
#else
			get {
				if (theFactory.app_state == null) {
					HttpStaticObjectsCollection app = MakeStaticCollection (GlobalAsaxCompiler.ApplicationObjects);
					HttpStaticObjectsCollection ses = MakeStaticCollection (GlobalAsaxCompiler.SessionObjects);

					theFactory.app_state = new HttpApplicationState (app, ses);
				}
				return theFactory.app_state;
			}
#endif
		}

		internal static Type AppType {
			get {
				return theFactory.app_type;
			}
		}
		
		void InitType (HttpContext context)
		{
			lock (this_lock) {
				if (!needs_init)
					return;

#if NET_2_0
				try {
#endif
					string physical_app_path = HttpRuntime.AppDomainAppPath;
					string app_file = null;
					
					app_file = Path.Combine (physical_app_path, "Global.asax");
					if (!File.Exists (app_file)) {
						app_file = Path.Combine (physical_app_path, "global.asax");
						if (!File.Exists (app_file))
							app_file = null;
					}
			
#if !NET_2_0
					WebConfigurationSettings.Init (context);
#endif
		
#if NET_2_0 && !TARGET_J2EE
					AppResourcesCompiler ac = new AppResourcesCompiler (context);
					ac.Compile ();

#if WEBSERVICES_DEP
					AppWebReferencesCompiler awrc = new AppWebReferencesCompiler ();
					awrc.Compile ();
#endif
					
					// Todo: Generate profile properties assembly from Web.config here
				
					AppCodeCompiler acc = new AppCodeCompiler ();
					acc.Compile ();

					// Note whether there are any App_Browsers/*.browser files.  If there
					// are we will be using *.browser files for sniffing in addition to browscap.ini
					string app_browsers_path = Path.Combine (physical_app_path, "App_Browsers");
					app_browsers_files = new string[0];
					if (Directory.Exists (app_browsers_path)) {
						app_browsers_files = Directory.GetFiles (app_browsers_path, "*.browser");
					}
#endif

					if (app_file != null) {
#if TARGET_J2EE
						app_file = System.Web.Util.UrlUtils.ResolveVirtualPathFromAppAbsolute("~/" + Path.GetFileName(app_file));
						app_type = System.Web.J2EE.PageMapper.GetObjectType(context, app_file);
#else
#if NET_2_0
						app_type = BuildManager.GetCompiledType ("~/" + Path.GetFileName (app_file));
#else
						app_type = ApplicationFileParser.GetCompiledApplicationType (app_file, context);
#endif
#endif
						if (app_type == null) {
							string msg = String.Format ("Error compiling application file ({0}).", app_file);
							throw new ApplicationException (msg);
						}
					} else {
						app_type = typeof (System.Web.HttpApplication);
						app_state = new HttpApplicationState ();
					}

					WatchLocationForRestart("Global.asax");
					WatchLocationForRestart("global.asax");
					WatchLocationForRestart("Web.config");
					WatchLocationForRestart("web.config");
					WatchLocationForRestart("Web.Config");
					needs_init = false;
#if NET_2_0
				} catch (Exception) {
					if (BuildManager.CodeAssemblies != null)
						BuildManager.CodeAssemblies.Clear ();
					if (BuildManager.TopLevelAssemblies != null)
						BuildManager.TopLevelAssemblies.Clear ();
					if (WebConfigurationManager.ExtraAssemblies != null)
						WebConfigurationManager.ExtraAssemblies.Clear ();
					throw;
				}
#endif
				
				//
				// Now init the settings
				//

			}
		}
		
		//
		// Multiple-threads might hit this one on startup, and we have
		// to delay-initialize until we have the HttpContext
		//
		internal static HttpApplication GetApplication (HttpContext context)
		{
#if TARGET_J2EE
			if (context.ApplicationInstance!=null)
				return context.ApplicationInstance;
#endif
			HttpApplicationFactory factory = theFactory;
			HttpApplication app = null;
			if (factory.app_start_needed){
				if (context == null)
					return null;

				factory.InitType (context);
				lock (factory) {
					if (factory.app_start_needed) {
						foreach (string dir in HttpApplication.BinDirs)
							WatchLocationForRestart (dir, "*.dll");
#if NET_2_0
									// Restart if the App_* directories are created...
			                        WatchLocationForRestart (".", "App_Code");
			                        WatchLocationForRestart (".", "App_Browsers");
			                        WatchLocationForRestart (".", "App_GlobalResources");
			                        // ...or their contents is changed.
			                        WatchLocationForRestart ("App_Code", "*", true);
			                        WatchLocationForRestart ("App_Browsers", "*");
			                        WatchLocationForRestart ("App_GlobalResources", "*");
#endif
			                        app = factory.FireOnAppStart (context);
						factory.app_start_needed = false;
						return app;
					}
				}
			}

			lock (factory.available) {
				if (factory.available.Count > 0) {
					app = (HttpApplication) factory.available.Pop ();
					app.RequestCompleted = false;
					return app;
				}
			}
			
			return (HttpApplication) Activator.CreateInstance (factory.app_type, true);
		}

		// The lock is in InvokeSessionEnd
		static HttpApplication GetApplicationForSessionEnd ()
		{
			HttpApplicationFactory factory = theFactory;
			if (factory.available_for_end.Count > 0)
				return (HttpApplication) factory.available_for_end.Pop ();

			HttpApplication app = (HttpApplication) Activator.CreateInstance (factory.app_type, true);
			app.InitOnce (false);

			return app;
		}

		internal static void RecycleForSessionEnd (HttpApplication app)
		{
			HttpApplicationFactory factory = theFactory;
			lock (factory.available_for_end) {
				if (factory.available_for_end.Count < 32)
					factory.available_for_end.Push (app);
				else
					app.Dispose ();
			}
		}

		internal static void Recycle (HttpApplication app)
		{
			HttpApplicationFactory factory = theFactory;
			lock (factory.available) {
				if (factory.available.Count < 32)
					factory.available.Push (app);
				else
					app.Dispose ();
			}
		}

		internal static bool ContextAvailable {
			get { return theFactory != null && !theFactory.app_start_needed; }
		}


                internal static bool WatchLocationForRestart (string filter)
	        {
			return WatchLocationForRestart ("", filter, false);
	        }

		internal static bool WatchLocationForRestart (string virtualPath, string filter)
		{
			return WatchLocationForRestart (virtualPath, filter, false);
		}
		
                internal static bool WatchLocationForRestart(string virtualPath, string filter, bool watchSubdirs)
		{
			// map the path to the physical one
			string physicalPath = HttpRuntime.AppDomainAppPath;
			physicalPath = Path.Combine(physicalPath, virtualPath);
			bool isDir = Directory.Exists(physicalPath);
			bool isFile = isDir ? false : File.Exists(physicalPath);
			
			if (isDir || isFile) {
				// create the watcher
				FileSystemEventHandler fseh = new FileSystemEventHandler(OnFileChanged);
				RenamedEventHandler reh = new RenamedEventHandler(OnFileRenamed);
				FileSystemWatcher watcher = CreateWatcher(Path.Combine(physicalPath, filter), fseh, reh);
				if (isDir)
					watcher.IncludeSubdirectories = watchSubdirs;
				
				lock (watchers_lock) {
					watchers.Add(watcher);
				}
				return true;
			} else {
				return false;
			}
	        }

#if NET_2_0
		internal static bool ApplicationDisabled {
			get { return app_disabled; }
			set { app_disabled = value; }
		}

		internal static string[] AppBrowsersFiles {
			get { return app_browsers_files; }
		}
		
		static System.Web.Configuration.nBrowser.Build capabilities_processor = null;
		static object capabilities_processor_lock = new object();
		internal static System.Web.Configuration.ICapabilitiesProcess CapabilitiesProcessor {
			get {
				lock (capabilities_processor_lock) {
					if (capabilities_processor == null) {
						capabilities_processor = new System.Web.Configuration.nBrowser.Build();
						string machine_browsers_path = Path.Combine (HttpRuntime.MachineConfigurationDirectory, "Browsers");
						if (Directory.Exists (machine_browsers_path)) {
							string[] machine_browsers_files 
								= Directory.GetFiles (machine_browsers_path, "*.browser");
							foreach (string f in machine_browsers_files) {
								capabilities_processor.AddBrowserFile(f);
							}
						}
						foreach (string f in app_browsers_files) {
							capabilities_processor.AddBrowserFile(f);
						}
					}
				}
				return capabilities_processor;
			}
		}
#endif
		
		internal static void DisableWatchers ()
		{
			lock (watchers_lock) {
				foreach (FileSystemWatcher watcher in watchers)
					watcher.EnableRaisingEvents = false;
			}
		}

		internal static void EnableWatchers ()
		{
			lock (watchers_lock) {
				foreach (FileSystemWatcher watcher in watchers)
					watcher.EnableRaisingEvents = true;
			}
		}
		
	        static void OnFileRenamed(object sender, RenamedEventArgs args)
		{
			OnFileChanged(sender, args);
	        }

	        static void OnFileChanged(object sender, FileSystemEventArgs args)
	        {
	        	lock (watchers_lock) {
				if(app_shutdown)
					return;
				app_shutdown = true;

				// Disable event raising to avoid concurrent restarts
				DisableWatchers ();
				
				// Restart application
				HttpRuntime.UnloadAppDomain();
			}
	        }
	}
}

