//
// System.ComponentModel.Design.Serialization.CodeDomDesignerLoader
//
// 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.CodeDom;
using System.CodeDom.Compiler;

namespace System.ComponentModel.Design.Serialization
{
	public abstract class CodeDomDesignerLoader : BasicDesignerLoader, INameCreationService, IDesignerSerializationService
	{
		private CodeDomSerializer _rootSerializer;

		protected CodeDomDesignerLoader ()
		{
		}

		protected override void Initialize ()
		{
			base.Initialize ();
			base.LoaderHost.AddService (typeof (IDesignerSerializationService), this);
			base.LoaderHost.AddService (typeof (INameCreationService), this);
			base.LoaderHost.AddService (typeof (ComponentSerializationService), new 
						    CodeDomComponentSerializationService (base.LoaderHost));
			if (this.TypeResolutionService != null && 
			    LoaderHost.GetService (typeof (ITypeResolutionService)) == null)
				LoaderHost.AddService (typeof (ITypeResolutionService), this.TypeResolutionService);
			IDesignerSerializationManager manager = base.LoaderHost.GetService (typeof (IDesignerSerializationManager)) as IDesignerSerializationManager;
			if (manager != null)
				manager.AddSerializationProvider (CodeDomSerializationProvider.Instance);
		}
		
		protected override bool IsReloadNeeded ()
		{
			if (this.CodeDomProvider is ICodeDomDesignerReload)
				return ((ICodeDomDesignerReload) CodeDomProvider).ShouldReloadDesigner (Parse ());
			return base.IsReloadNeeded ();
		}

		protected override void PerformLoad (IDesignerSerializationManager manager)
		{
			if (manager == null)
				throw new ArgumentNullException ("manager");

			CodeCompileUnit document = this.Parse ();
			if (document == null)
				throw new NotSupportedException ("The language did not provide a code parser for this file");

			string namespaceName = null;
			CodeTypeDeclaration rootDocument = GetFirstCodeTypeDecl (document, out namespaceName);
			if (rootDocument == null)
				throw new InvalidOperationException ("Cannot find a declaration in a namespace to load.");

			_rootSerializer = manager.GetSerializer (manager.GetType (rootDocument.BaseTypes[0].BaseType), 
													 typeof (RootCodeDomSerializer)) as CodeDomSerializer;
			if (_rootSerializer == null)
				throw new InvalidOperationException ("Serialization not supported for this class");

			_rootSerializer.Deserialize (manager, rootDocument);

			base.SetBaseComponentClassName (namespaceName + "." + rootDocument.Name);
		}

		private CodeTypeDeclaration GetFirstCodeTypeDecl (CodeCompileUnit document, out string namespaceName)
		{
			namespaceName = null;

			foreach (CodeNamespace namesp in document.Namespaces) {
				foreach (CodeTypeDeclaration declaration in namesp.Types) {
					if (declaration.IsClass) {
						namespaceName = namesp.Name;
						return declaration;
					}
				}
			}
			return null;
		}
		
		protected override void PerformFlush (IDesignerSerializationManager manager)
		{
			if (_rootSerializer != null) {
				CodeTypeDeclaration typeDecl = (CodeTypeDeclaration) _rootSerializer.Serialize (manager, 
																								base.LoaderHost.RootComponent);
				this.Write (MergeTypeDeclWithCompileUnit (typeDecl, this.Parse ()));
			}
		}

		// Will either add the class or replace an existing class
		// with the one from GenerateClass ()
		//
		private CodeCompileUnit MergeTypeDeclWithCompileUnit (CodeTypeDeclaration typeDecl, CodeCompileUnit unit)
		{
			CodeNamespace namespac = null;
			int typeIndex = -1;

			foreach (CodeNamespace namesp in unit.Namespaces) {
				for (int i=0; i< namesp.Types.Count; i++) {
					if (namesp.Types[i].IsClass) {
						typeIndex = i;
						namespac = namesp;
					}
				}
			}

			if (typeIndex != -1)
				namespac.Types.RemoveAt (typeIndex);

			namespac.Types.Add (typeDecl);

			return unit;
		}

		protected override void OnBeginLoad ()
		{
			base.OnBeginLoad ();

			IComponentChangeService service = base.GetService (typeof (IComponentChangeService)) as IComponentChangeService;
			if (service != null)
				service.ComponentRename += this.OnComponentRename_EventHandler;
		}
		
		protected override void OnBeginUnload ()
		{
			base.OnBeginUnload ();

			IComponentChangeService service = base.GetService (typeof (IComponentChangeService)) as IComponentChangeService;
			if (service != null)
				service.ComponentRename -= this.OnComponentRename_EventHandler;
		}
		
		protected override void OnEndLoad (bool successful, ICollection errors)
		{
			base.OnEndLoad (successful, errors);
			// XXX: msdn says overriden
		}

		private void OnComponentRename_EventHandler (object sender, ComponentRenameEventArgs args)
		{
			this.OnComponentRename (args.Component, args.OldName, args.NewName);
		}

		// MSDN says that here one should raise ComponentRename event and that's nonsense.
		//
		protected virtual void OnComponentRename (object component, string oldName, string newName) 
		{
			// What shall we do with the drunken sailor,
			// what shall we do with the drunken sailor early in the morning?
		}

		protected abstract CodeDomProvider CodeDomProvider { get; }
		protected abstract ITypeResolutionService TypeResolutionService { get; }
		protected abstract CodeCompileUnit Parse ();
		protected abstract void Write (CodeCompileUnit unit);

		public override void Dispose ()
		{
			base.Dispose ();
		}

#region INameCreationService implementation

		// very simplistic implementation to generate names like "button1", "someControl2", etc
		//
		string INameCreationService.CreateName (IContainer container, Type dataType)
		{
			if (dataType == null)
				throw new ArgumentNullException ("dataType");

			string name = dataType.Name;
			char lower = Char.ToLower (name[0]);
			name = name.Remove (0, 1);
			name = name.Insert (0, Char.ToString (lower));

			int uniqueId = 1;
			bool unique = false;

			while (!unique) {
				if (container != null && container.Components[name + uniqueId] != null) {
					uniqueId++;
				} else {
					unique = true;
					name = name + uniqueId;
				}
			}

			if (this.CodeDomProvider != null)
				name = CodeDomProvider.CreateValidIdentifier (name);

			return name;
		}

		bool INameCreationService.IsValidName (string name)
		{
			if (name == null)
				throw new ArgumentNullException ("name");

			bool valid = true;
			if (base.LoaderHost != null && base.LoaderHost.Container.Components[name] != null) {
				valid = false;
			} else {
				if (this.CodeDomProvider != null) {
					valid = CodeDomProvider.IsValidIdentifier (name);
				} else {
					if (name.Trim().Length == 0)
						valid = false;
					foreach (char c in name) {
						if (!Char.IsLetterOrDigit (c)) {
							valid = false;
							break;
						}
					}
				}
			}

			return valid;
		}

		void INameCreationService.ValidateName (string name)
		{
			if (!((INameCreationService) this).IsValidName (name))
				throw new ArgumentException ("Invalid name '" + name + "'");
		}
#endregion


#region IDesignerSerializationService implementation

		ICollection IDesignerSerializationService.Deserialize (object serializationData)
		{
			if (serializationData == null)
				throw new ArgumentNullException ("serializationData");

			ComponentSerializationService service = LoaderHost.GetService (typeof (ComponentSerializationService)) as ComponentSerializationService;
			SerializationStore store = serializationData as SerializationStore;
			if (service != null && serializationData != null)
				return service.Deserialize (store, this.LoaderHost.Container);
			return new object[0];
		}

		object IDesignerSerializationService.Serialize (ICollection objects)
		{
			if (objects == null)
				throw new ArgumentNullException ("objects");

			ComponentSerializationService service = LoaderHost.GetService (typeof (ComponentSerializationService)) as ComponentSerializationService;
			if (service != null) {
				SerializationStore store = service.CreateStore ();
				foreach (object o in objects)
					service.Serialize (store, o);
				store.Close ();
				return store;
			}
			return null;
		}
#endregion
	}
}

