//
// System.ComponentModel.Design.Serialization.BasicDesignerLoader
//
// Authors:	 
//	  Ivan N. Zlatev (contact i-nZ.net)
//
// (C) 2007 Ivan N. Zlatev

//
// 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.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;

namespace System.ComponentModel.Design.Serialization
{
	public abstract class BasicDesignerLoader : DesignerLoader, IDesignerLoaderService
	{

		[Flags]
		protected enum ReloadOptions 
		{
			Default,
			Force,
			ModifyOnError,
			NoFlush
		}

		private bool _loaded;
		private bool _loading;
		private IDesignerLoaderHost _host;
		private int _dependenciesCount;
		private bool _notificationsEnabled;
		private bool _modified;
		private string _baseComponentClassName;
		private DesignerSerializationManager _serializationMananger;
		private bool _flushing;
		private bool _reloadScheduled;
		private ReloadOptions _reloadOptions;

		protected BasicDesignerLoader ()
		{
			_loading = _loaded = _flushing = _reloadScheduled = false;
			_host = null;
			_notificationsEnabled = false;
			_modified = false;
			_dependenciesCount = 0;
		}
		
		protected virtual void Initialize ()
		{
			_serializationMananger = new DesignerSerializationManager (_host);

			DesignSurfaceServiceContainer serviceContainer = _host.GetService (typeof (IServiceContainer)) as DesignSurfaceServiceContainer;
			if (serviceContainer != null) {
				serviceContainer.AddService (typeof (IDesignerLoaderService), (IDesignerLoaderService) this);
				serviceContainer.AddNonReplaceableService (typeof (IDesignerSerializationManager), _serializationMananger);
			}
		}
		
		public override void BeginLoad (IDesignerLoaderHost host)
		{
			if (host == null)
				throw new ArgumentNullException ("host");
			if (_loaded)
				throw new InvalidOperationException ("Already loaded.");
			if (_host != null && _host != host)
				throw new InvalidOperationException ("Trying to load with a different host");

			if (_host == null) { // beingload is called on reload - no need to initialize twice.
				_host = host;
				Initialize ();
			}
			IDisposable session = _serializationMananger.CreateSession ();

			IDesignerLoaderService loader = _host.GetService (typeof (IDesignerLoaderService)) as IDesignerLoaderService;

			if (loader != null) {
				_dependenciesCount = -1;
				loader.AddLoadDependency ();
			} else {
				OnBeginLoad ();
			}

			bool successful = true;

			try {
				PerformLoad (_serializationMananger);
			} catch (Exception e) {
				successful = false;
				_serializationMananger.Errors.Add (e);
			}

			if (loader != null)
				loader.DependentLoadComplete (successful, _serializationMananger.Errors);
			else
				OnEndLoad (successful, _serializationMananger.Errors);

			session.Dispose ();
		}
	
		protected abstract void PerformLoad (IDesignerSerializationManager serializationManager);
		
		protected virtual void OnBeginLoad ()
		{
			_loading = true;
		}
		
		protected virtual void OnEndLoad (bool successful, ICollection errors)
		{
			_host.EndLoad (_baseComponentClassName, successful,  errors);

			if (successful) {
				_loaded = true;
				EnableComponentNotification (true);
			} else {
				if (_reloadScheduled) { // we are reloading
					bool modify = ((_reloadOptions & ReloadOptions.ModifyOnError) == ReloadOptions.ModifyOnError);
					if (modify) {
						OnModifying ();
						this.Modified = true;
					}
				}
			}
			_loading = false;
		}
		
		public override bool Loading {
			get { return _loading; }
		}

		protected IDesignerLoaderHost LoaderHost { 
			get { return _host; }
		}

		protected virtual bool Modified { 
			get { return _modified; }
			set { _modified = value; }
		}

		protected object PropertyProvider { 
			get {
				if (!_loaded)
					throw new InvalidOperationException ("host not initialized");
				return _serializationMananger.PropertyProvider;
			} 
			set {
				if (!_loaded)
					throw new InvalidOperationException ("host not initialized");
				_serializationMananger.PropertyProvider = value;
			}
		}

		protected bool ReloadPending { 
			get { return _reloadScheduled; }
		}
		
		protected virtual bool EnableComponentNotification (bool enable)
		{
			if (!_loaded)
				throw new InvalidOperationException ("host not initialized");

			IComponentChangeService service = _host.GetService (typeof (IComponentChangeService)) as IComponentChangeService;
			if (service != null && _notificationsEnabled != enable) {
				if (enable) {
					service.ComponentAdding += new ComponentEventHandler (OnComponentAdding);
					service.ComponentAdded += new ComponentEventHandler (OnComponentAdded);
					service.ComponentRemoving += new ComponentEventHandler (OnComponentRemoving);
					service.ComponentRemoved += new ComponentEventHandler (OnComponentRemoved);
					service.ComponentChanging += new ComponentChangingEventHandler (OnComponentChanging);
					service.ComponentChanged += new ComponentChangedEventHandler (OnComponentChanged);
					service.ComponentRename += new ComponentRenameEventHandler (OnComponentRename);
				} else {
					service.ComponentAdding -= new ComponentEventHandler (OnComponentAdding);
					service.ComponentAdded -= new ComponentEventHandler (OnComponentAdded);
					service.ComponentRemoving -= new ComponentEventHandler (OnComponentRemoving);
					service.ComponentRemoved -= new ComponentEventHandler (OnComponentRemoved);
					service.ComponentChanging -= new ComponentChangingEventHandler (OnComponentChanging);
					service.ComponentChanged -= new ComponentChangedEventHandler (OnComponentChanged);
					service.ComponentRename -= new ComponentRenameEventHandler (OnComponentRename);
				}
			}
			return _notificationsEnabled == true ? true : false;
		}

		private void OnComponentAdded (object sender, ComponentEventArgs args)
		{
			if (!_loading && _loaded)
				this.Modified = true;
		}

		private void OnComponentRemoved (object sender, ComponentEventArgs args)
		{
			if (!_loading && _loaded)
				this.Modified = true;
		}

		private void OnComponentAdding (object sender, ComponentEventArgs args)
		{
			if (!_loading && _loaded)
				OnModifying ();
		}

		private void OnComponentRemoving (object sender, ComponentEventArgs args)
		{
			if (!_loading && _loaded)
				OnModifying ();
		}

		private void OnComponentChanged (object sender, ComponentChangedEventArgs args)
		{
			if (!_loading && _loaded)
				this.Modified = true;
		}

		private void OnComponentChanging (object sender, ComponentChangingEventArgs args)
		{
			if (!_loading && _loaded)
				OnModifying ();
		}

		private void OnComponentRename (object sender, ComponentRenameEventArgs args)
		{
			if (!_loading && _loaded) {
					OnModifying ();
					this.Modified = true;
			}
		}

		public override void Flush ()
		{
			if (!_loaded)
				throw new InvalidOperationException ("host not initialized");

			if (!_flushing && this.Modified) {
				_flushing = true;
				using ((IDisposable)_serializationMananger.CreateSession ()) {
					try {
						PerformFlush (_serializationMananger);
					} catch (Exception e) {
						_serializationMananger.Errors.Add (e);
						ReportFlushErrors (_serializationMananger.Errors);
					}
				}
				_flushing = false;
			}
		}
		
		protected abstract void PerformFlush (IDesignerSerializationManager serializationManager);
		
		// MSDN: The default implementation always returns true.
		protected virtual bool IsReloadNeeded ()
		{
			return true;
		}
		

		protected virtual void OnBeginUnload ()
		{
		}
		
		protected virtual void OnModifying ()
		{
		}
		
		// MSDN: reloads are performed at idle time
		protected void Reload (ReloadOptions flags)
		{
			if (!_reloadScheduled) {
				_reloadScheduled = true;
				_reloadOptions = flags;
				bool force = ((flags & ReloadOptions.Force) == ReloadOptions.Force);
				if (force)
					ReloadCore ();
				else
					Application.Idle += new EventHandler (OnIdle);
			}
		}

		private void OnIdle (object sender,  EventArgs args)
		{
			Application.Idle -= new EventHandler (OnIdle);
			ReloadCore ();
		}

		private void ReloadCore ()
		{
			bool flush = !((_reloadOptions & ReloadOptions.NoFlush) == ReloadOptions.NoFlush);

			if (flush)
				Flush ();
			Unload ();
			_host.Reload ();
			BeginLoad (_host); // calls EndLoad, which will check for ReloadOptions.ModifyOnError
			_reloadScheduled = false;
		}

		private void Unload ()
		{
			if (_loaded) {
				OnBeginUnload ();
				EnableComponentNotification (false);
				_loaded = false;
				_baseComponentClassName = null;
			}
		}


		// The default implementation of ReportFlushErrors raises the last exception in the collection.
		//
		protected virtual void ReportFlushErrors (ICollection errors)
		{
			object last = null;
			foreach (object o in errors)
				last = o;
			throw (Exception)last;
		}
		
		// Must be called during PerformLoad by subclasses.
		protected void SetBaseComponentClassName (string name)
		{
			if (name == null)
				throw new ArgumentNullException ("name");
			_baseComponentClassName = name;
		}
		
#region IDesignerLoaderService implementation

		void IDesignerLoaderService.AddLoadDependency ()
		{
			_dependenciesCount++;
			if (_dependenciesCount == 0) {
				_dependenciesCount = 1;
				OnBeginLoad ();
			}
		}
		
		void IDesignerLoaderService.DependentLoadComplete (bool successful, ICollection errorCollection)
		{
			if (_dependenciesCount == 0)
				throw new InvalidOperationException ("dependencies == 0");

			_dependenciesCount--;
			if (_dependenciesCount == 0) {
				OnEndLoad (successful,  errorCollection);
			}
		}

		bool IDesignerLoaderService.Reload ()
		{
			if (_dependenciesCount == 0) {
				this.Reload (ReloadOptions.Force);
				return true;
			}
			return false;
		}
#endregion
		
		protected object GetService (Type serviceType)
		{
			if (_host != null)
				return _host.GetService (serviceType);
			return null;
		}
			
		public override void Dispose ()
		{
			this.LoaderHost.RemoveService (typeof (IDesignerLoaderService));
			Unload ();
		}
	}
}

