//
// System.ComponentModel.Design.Serialization.CodeDomComponentSerializationService
//
// 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.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Collections;
using System.Collections.Generic;
using System.IO;

namespace System.ComponentModel.Design.Serialization
{
	// A ComponentSerializationService that uses a CodeDomSerializationStore 
	// to serialize Components and MemberDescriptors to CodeStatement and CodeStatementCollection
	//
	public sealed class CodeDomComponentSerializationService : ComponentSerializationService
	{
		[Serializable]
		private class CodeDomSerializationStore : SerializationStore, ISerializable
		{
			[Serializable]
			private class Entry
			{
				private bool _isSerialized;
				private object _serialized;
				private bool _absolute;
				private string _name;

				protected Entry ()
				{
				}

				public Entry (string name)
				{
					if (name == null)
						throw new ArgumentNullException ("name");
					_name = name;
					_isSerialized = false;
					_absolute = false;
				}

				public bool IsSerialized {
					get { return _isSerialized; }
					set { _isSerialized = value; }
				}

				public object Serialized {
					get { return _serialized; }
					set { 
						_serialized = value;
						_isSerialized = true;
					}
				}

				public bool Absolute {
					get { return _absolute; }
					set { _absolute = value; }
				}

				public string Name {
					get { return _name; }
					set { _name = value; }
				}
			}

			[Serializable]
			private class MemberEntry : Entry
			{
				private MemberDescriptor _descriptor;

				protected MemberEntry ()
				{
				}

				public MemberEntry (MemberDescriptor descriptor)
				{
					if (descriptor == null)
						throw new ArgumentNullException ("descriptor");
					_descriptor = descriptor;
					base.Name = descriptor.Name;
				}

				public MemberDescriptor Descriptor {
					get { return _descriptor; }
					set { _descriptor = value; }
				}
			}

			[Serializable]
			private class ObjectEntry : Entry
			{
				private Type _type;
				[NonSerialized]
				private object _instance;
				private Dictionary<string,MemberEntry> _members;
				private bool _entireObject;

				protected ObjectEntry ()
				{
				}

				public ObjectEntry (object instance, string name) : base (name)
				{
					if (instance == null)
						throw new ArgumentNullException ("instance");
					_instance = instance;
					_type = instance.GetType ();
					_entireObject = false;
				}

				public Type Type {
					get { return _type; }
				}

				public object Instance {
					get { return _instance; }
					set { 
						_instance = value;
						if (value != null)
							_type = value.GetType ();
					}
				}

				public Dictionary<string,MemberEntry> Members {
					get { 
						if (_members == null)
							_members = new Dictionary <string, MemberEntry> ();
						return _members; 
					}
					set { _members = value; }
				}

				public bool IsEntireObject {
					get { return _entireObject; }
					set { _entireObject = value; }
				}
			}

			// When e.g Copy->Pasting a component will not be preserved, but the serialized 
			// data will contain references to the original component name. We handle this 
			// here by checking whether CreateInstance returns a component with a different
			// name and we map the old name to the new one so that GetInstance will return
			// a reference to the new component.
			//
			private class InstanceRedirectorDesignerSerializationManager : IDesignerSerializationManager, IServiceProvider
			{
				DesignerSerializationManager _manager;
				private Dictionary <string, string> _nameMap;

				public InstanceRedirectorDesignerSerializationManager (IServiceProvider provider, IContainer container,
										       bool validateRecycledTypes)
				{
					if (provider == null)
						throw new ArgumentNullException ("provider");
					DesignerSerializationManager manager = new DesignerSerializationManager (provider);
					manager.PreserveNames = false;
					if (container != null)
						manager.Container = container;
					manager.ValidateRecycledTypes = validateRecycledTypes;
					_manager = manager;
				}

				public IDisposable CreateSession ()
				{
					return _manager.CreateSession ();
				}

				public IList Errors {
					get { return _manager.Errors; }
				}

				object IServiceProvider.GetService (Type service)
				{
					return ((IServiceProvider)_manager).GetService (service);
				}

				#region IDesignerSerializationManager Wrapper Implementation

				void IDesignerSerializationManager.AddSerializationProvider (IDesignerSerializationProvider provider)
				{
					((IDesignerSerializationManager)_manager).AddSerializationProvider (provider);
				}

				void IDesignerSerializationManager.RemoveSerializationProvider (IDesignerSerializationProvider provider)
				{
					((IDesignerSerializationManager)_manager).RemoveSerializationProvider (provider);
				}

				object IDesignerSerializationManager.CreateInstance (Type type, ICollection arguments, string name, bool addToContainer)
				{
					object instance = ((IDesignerSerializationManager)_manager).CreateInstance (type, arguments, name, addToContainer);
					string newName = ((IDesignerSerializationManager)_manager).GetName (instance);
					if (newName != name) {
						if (_nameMap == null)
							_nameMap = new Dictionary<string, string> ();
						_nameMap[name] = newName;
					}
					return instance;
				}

				object IDesignerSerializationManager.GetInstance (string name)
				{
					if (_nameMap != null && _nameMap.ContainsKey (name))
						return ((IDesignerSerializationManager)_manager).GetInstance (_nameMap[name]);
					return ((IDesignerSerializationManager)_manager).GetInstance (name);
				}

				Type IDesignerSerializationManager.GetType (string name)
				{
					return ((IDesignerSerializationManager)_manager).GetType (name);
				}

				object IDesignerSerializationManager.GetSerializer (Type type, Type serializerType)
				{
					return ((IDesignerSerializationManager)_manager).GetSerializer (type, serializerType);
				}

				string IDesignerSerializationManager.GetName (object instance)
				{
					return ((IDesignerSerializationManager)_manager).GetName (instance);
				}

				void IDesignerSerializationManager.SetName (object instance, string name)
				{
					((IDesignerSerializationManager)_manager).SetName (instance, name);
				}

				void IDesignerSerializationManager.ReportError (object error)
				{
					((IDesignerSerializationManager)_manager).ReportError (error);
				}

				ContextStack IDesignerSerializationManager.Context {
					get { return ((IDesignerSerializationManager)_manager).Context; }
				}

				PropertyDescriptorCollection IDesignerSerializationManager.Properties {
					get { return ((IDesignerSerializationManager)_manager).Properties; }
				}

				event EventHandler IDesignerSerializationManager.SerializationComplete {
					add { ((IDesignerSerializationManager)_manager).SerializationComplete += value; }
					remove { ((IDesignerSerializationManager)_manager).SerializationComplete -= value; }
				}

				event ResolveNameEventHandler IDesignerSerializationManager.ResolveName {
					add { ((IDesignerSerializationManager)_manager).ResolveName += value; }
					remove { ((IDesignerSerializationManager)_manager).ResolveName -= value; }
				}
				#endregion
			} // InstanceRedirectorDesignerSerializationManager

			private bool _closed;
			private Dictionary <string, ObjectEntry> _objects;
			private IServiceProvider _provider;
			private ICollection _errors;

			internal CodeDomSerializationStore () : this (null)
			{
			}

			internal CodeDomSerializationStore (IServiceProvider provider)
			{
				_provider = provider;
			}

			private CodeDomSerializationStore (SerializationInfo info, StreamingContext context)
			{
				_objects = (Dictionary<string, ObjectEntry>) info.GetValue ("objects", typeof (Dictionary<string, ObjectEntry>));
				_closed = (bool) info.GetValue ("closed", typeof (bool));
			}

			void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
			{
				info.AddValue ("objects", _objects);
				info.AddValue ("closed", _closed);
			}

			public override void Close () 
			{
				if (!_closed) {
					Serialize (_provider);
					_closed = true;
				}
			}

			internal static CodeDomSerializationStore Load (Stream stream)
			{
				return new BinaryFormatter ().Deserialize (stream) as CodeDomSerializationStore;
			}

			public override void Save (Stream stream) 
			{
				Close ();
				new BinaryFormatter ().Serialize (stream, this);
			}

			private void Serialize (IServiceProvider provider)
			{
				if (provider == null || _objects == null)
					return;

				// Use a new serialization manager to prevent from "deadlocking" the surface one
				// by trying to create new session when one currently exists
				// 
				InstanceRedirectorDesignerSerializationManager manager = 
					new InstanceRedirectorDesignerSerializationManager (provider, null, false);
				((IDesignerSerializationManager)manager).AddSerializationProvider (CodeDomSerializationProvider.Instance);
				IDisposable session = manager.CreateSession ();
				foreach (ObjectEntry objectEntry in _objects.Values) {
					if (objectEntry.IsEntireObject) {
						CodeDomSerializer serializer = (CodeDomSerializer) ((IDesignerSerializationManager)manager).GetSerializer (objectEntry.Type, 
													typeof (CodeDomSerializer));
						if (serializer != null) {
							object serialized = null;
							if (objectEntry.Absolute)
								serialized = serializer.SerializeAbsolute (manager, 
													   objectEntry.Instance);
							else
								serialized = serializer.Serialize (manager, objectEntry.Instance);
							objectEntry.Serialized = serialized;
						}
					} else {
						foreach (MemberEntry memberEntry in objectEntry.Members.Values) {
							CodeDomSerializer serializer = (CodeDomSerializer) ((IDesignerSerializationManager)manager).GetSerializer (
								objectEntry.Type, typeof (CodeDomSerializer));
							if (serializer != null) {
								object serialized = null;
								if (memberEntry.Absolute) {
									serialized = serializer.SerializeMemberAbsolute (manager, 
															 objectEntry.Instance, 
															 memberEntry.Descriptor);
								} else {
									serialized = serializer.SerializeMember (manager,
														 objectEntry.Instance, 
														 memberEntry.Descriptor);
								}
								memberEntry.Serialized = serialized;
							}
						}
					}
				}
				_errors = manager.Errors;
				session.Dispose ();
			}

			internal void AddObject (object instance, bool absolute)
			{
				if (_closed)
					throw new InvalidOperationException ("store is closed");

				if (_objects == null)
					_objects = new Dictionary <string, ObjectEntry> ();
				string objectName = GetName (instance);

				if (!_objects.ContainsKey (objectName)) {
					ObjectEntry objectEntry = new ObjectEntry (instance, objectName);
					objectEntry.Absolute = absolute;
					objectEntry.IsEntireObject = true;
					_objects[objectName] = objectEntry;
				}
			}

			internal void AddMember (object owner, MemberDescriptor member, bool absolute)
			{
				if (_closed)
					throw new InvalidOperationException ("store is closed");
				if (member == null)
					throw new ArgumentNullException ("member");
				if (owner == null)
					throw new ArgumentNullException ("owner");

				if (_objects == null)
					_objects = new Dictionary <string, ObjectEntry> ();

				string objectName = GetName (owner);
				if (!_objects.ContainsKey (objectName))
					_objects.Add (objectName,  new ObjectEntry (owner, objectName));
				MemberEntry memberEntry = new MemberEntry (member);
				memberEntry.Absolute = absolute;
				_objects[objectName].Members[member.Name] = memberEntry;
			}

			private string GetName (object value)
			{
				if (value == null)
					throw new ArgumentNullException ("value");
				string name = null;

				IComponent component = value as IComponent;
				if (component != null && component.Site != null) {
					if (component.Site is INestedSite)
						name = ((INestedSite)component.Site).FullName;
					else
						name = component.Site != null ? component.Site.Name : null;
				} else if (value is MemberDescriptor) {
					name = ((MemberDescriptor) value).Name;
				} else {
					name = value.GetHashCode ().ToString ();
				}

				return name;
			}

			internal ICollection Deserialize (IServiceProvider provider, IContainer container, 
							  bool validateRecycledTypes, bool applyDefaults)
			{
				List<object> objectInstances = new List<object> ();

				if (provider == null || _objects == null)
					return objectInstances;
				_provider = provider;

				// Use a new serialization manager to prevent from "deadlocking" the surface one
				// by trying to create new session when one currently exists
				// 
				InstanceRedirectorDesignerSerializationManager manager = 
					new InstanceRedirectorDesignerSerializationManager (provider, container, validateRecycledTypes);
				((IDesignerSerializationManager)manager).AddSerializationProvider (CodeDomSerializationProvider.Instance);
				IDisposable session = manager.CreateSession ();
				foreach (ObjectEntry entry in _objects.Values)
					objectInstances.Add (DeserializeEntry (manager, entry));
				_errors = manager.Errors;
				session.Dispose ();
				return objectInstances;
			}

			private object DeserializeEntry (IDesignerSerializationManager manager, ObjectEntry objectEntry)
			{
				object deserialized = null;

				if (objectEntry.IsEntireObject) {
					CodeDomSerializer serializer = (CodeDomSerializer) manager.GetSerializer (objectEntry.Type, 
														  typeof (CodeDomSerializer));
					if (serializer != null) {
						deserialized = serializer.Deserialize (manager, objectEntry.Serialized);
						// check if the name of the object has changed
						// (if it e.g clashes with another name)
						string newName = manager.GetName (deserialized);
						if (newName != objectEntry.Name)
							objectEntry.Name = newName;
					}
				} else {
					foreach (MemberEntry memberEntry in objectEntry.Members.Values) {
						CodeDomSerializer serializer = (CodeDomSerializer) manager.GetSerializer (objectEntry.Type, 
															  typeof (CodeDomSerializer));
						if (serializer != null) 
							serializer.Deserialize (manager, memberEntry.Serialized);
					}
				}

				return deserialized;
			}

			public override ICollection Errors {
				get {
					if (_errors == null)
						_errors = new object[0];
					return _errors;
				}
			}
		} // CodeDomSerializationStore

		private IServiceProvider _provider;

		public CodeDomComponentSerializationService () : this (null)
		{
		}

		public CodeDomComponentSerializationService (IServiceProvider provider)
		{
			_provider = provider;
		}

		public override SerializationStore CreateStore ()
		{
			return new CodeDomSerializationStore (_provider);
		}

		public override SerializationStore LoadStore (Stream stream)
		{
			return CodeDomSerializationStore.Load (stream);
		}

		public override ICollection Deserialize (SerializationStore store)
		{
			return this.Deserialize (store, null);
		}

		public override ICollection Deserialize (SerializationStore store, IContainer container)
		{
			return DeserializeCore (store, container, true, true);
		}

		public override void DeserializeTo (SerializationStore store, IContainer container, bool validateRecycledTypes, bool applyDefaults)
		{
			DeserializeCore (store, container, validateRecycledTypes, applyDefaults);
		}

		private ICollection DeserializeCore (SerializationStore store, IContainer container, bool validateRecycledTypes, 
						     bool applyDefaults)
		{
			CodeDomSerializationStore codeDomStore = store as CodeDomSerializationStore;
			if (codeDomStore == null)
				throw new InvalidOperationException ("store type unsupported");
			return codeDomStore.Deserialize (_provider, container, validateRecycledTypes, applyDefaults);
		}

		public override void Serialize (SerializationStore store, object value)
		{
			SerializeCore (store, value, false);
		}

		public override void SerializeAbsolute (SerializationStore store, object value)
		{
			SerializeCore (store, value, true);
		}

		public override void SerializeMember (SerializationStore store, object owningObject, MemberDescriptor member)
		{
			SerializeMemberCore (store, owningObject, member, false);
		}

		public override void SerializeMemberAbsolute (SerializationStore store, object owningObject, MemberDescriptor member)
		{
			SerializeMemberCore (store, owningObject, member, true);
		}

		private void SerializeCore (SerializationStore store, object value, bool absolute)
		{
			if (value == null)
				throw new ArgumentNullException ("value");
			if (store == null)
				throw new ArgumentNullException ("store");

			CodeDomSerializationStore codeDomStore = store as CodeDomSerializationStore;
			if (store == null)
				throw new InvalidOperationException ("store type unsupported");

			codeDomStore.AddObject (value, absolute);
		}

		private void SerializeMemberCore (SerializationStore store, object owningObject, MemberDescriptor member, bool absolute)
		{
			if (member == null)
				throw new ArgumentNullException ("member");
			if (owningObject == null)
				throw new ArgumentNullException ("owningObject");
			if (store == null)
				throw new ArgumentNullException ("store");

			CodeDomSerializationStore codeDomStore = store as CodeDomSerializationStore;
			if (codeDomStore == null)
				throw new InvalidOperationException ("store type unsupported");
			codeDomStore.AddMember (owningObject, member, absolute);
		}
	}
}
