//
// System.ComponentModel.Design.Serialization.CodeDomSerializerBase
//
// 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.Reflection;
using System.ComponentModel;
using System.ComponentModel.Design;

using System.CodeDom;

namespace System.ComponentModel.Design.Serialization
{
	[EditorBrowsable (EditorBrowsableState.Never)]
	public abstract class CodeDomSerializerBase
	{

		// MSDN says that if the deserialization fails a CodeExpression is returned
		//
		private sealed class DeserializationErrorMarker : CodeExpression
		{
			public override bool Equals (object o)
			{
				return false;
			}

			public override int GetHashCode ()
			{
				return base.GetHashCode ();
			}
		}

		private static readonly DeserializationErrorMarker _errorMarker = new DeserializationErrorMarker ();

		internal CodeDomSerializerBase ()
		{
		}

		private class ExpressionTable : Hashtable // just so that we have a specific type to append to the context stack
		{
		}

		protected CodeExpression SerializeToExpression (IDesignerSerializationManager manager, object instance)
		{
			if (manager == null)
				throw new ArgumentNullException ("manager");

			CodeExpression expression = null;
			if (instance != null)
				expression = this.GetExpression (manager, instance); // 1 - IDesignerSerializationManager.GetExpression
			if (expression == null) {
				CodeDomSerializer serializer = this.GetSerializer (manager, instance); // 2 - manager.GetSerializer().Serialize()
				if (serializer != null) {
					object serialized = serializer.Serialize (manager, instance);
					expression = serialized as CodeExpression; // 3 - CodeStatement or CodeStatementCollection
					if (expression == null) {
						CodeStatement statement = serialized as CodeStatement;
						CodeStatementCollection statements = serialized as CodeStatementCollection;

						if (statement != null || statements != null) {
							CodeStatementCollection contextStatements = null;

							StatementContext context = manager.Context[typeof (StatementContext)] as StatementContext;
							if (context != null && instance != null)
								contextStatements = context.StatementCollection[instance];

							if (contextStatements == null)
								contextStatements = manager.Context[typeof (CodeStatementCollection)] as CodeStatementCollection;

							if (contextStatements != null) {
								if (statements != null)
									contextStatements.AddRange (statements);
								else
									contextStatements.Add (statement);
							}
						}
					}
					if (expression == null && instance != null)
						expression = this.GetExpression (manager, instance); // 4
				} else {
					ReportError (manager, "No serializer found for type '" + instance.GetType ().Name + "'");
				}
			}
			return expression;
		}

		protected CodeDomSerializer GetSerializer (IDesignerSerializationManager manager, object instance)
		{
			DesignerSerializerAttribute attrInstance, attrType;
			attrType = attrInstance = null;

			CodeDomSerializer serializer = null;
			if (instance == null)
				serializer = this.GetSerializer (manager, null);
			else {		
				AttributeCollection attributes = TypeDescriptor.GetAttributes (instance);
				foreach (Attribute a in attributes) {
					DesignerSerializerAttribute designerAttr = a as DesignerSerializerAttribute;
					if (designerAttr != null && manager.GetType (designerAttr.SerializerBaseTypeName) == typeof (CodeDomSerializer)) {
						attrInstance = designerAttr;
						break;
					}
				}
	
				attributes = TypeDescriptor.GetAttributes (instance.GetType ());
				foreach (Attribute a in attributes) {
					DesignerSerializerAttribute designerAttr = a as DesignerSerializerAttribute;
					if (designerAttr != null && manager.GetType (designerAttr.SerializerBaseTypeName) == typeof (CodeDomSerializer)) {
						attrType = designerAttr;
						break;
					}
				}
	
				// if there is metadata modification in the instance then create the specified serializer instead of the one
				// in the Type.
				if (attrType != null && attrInstance != null && attrType.SerializerTypeName != attrInstance.SerializerTypeName)
					serializer = Activator.CreateInstance (manager.GetType (attrInstance.SerializerTypeName)) as CodeDomSerializer;
				else
					serializer = this.GetSerializer (manager, instance.GetType ());
			}

			return serializer;
		}

		protected CodeDomSerializer GetSerializer (IDesignerSerializationManager manager, Type instanceType)
		{
			return manager.GetSerializer (instanceType, typeof (CodeDomSerializer)) as CodeDomSerializer;
		}

		protected CodeExpression GetExpression (IDesignerSerializationManager manager, object instance)
		{
			if (manager == null)
				throw new ArgumentNullException ("manager");
			if (instance == null)
				throw new ArgumentNullException ("instance");

			CodeExpression expression = null;

			ExpressionTable expressions = manager.Context[typeof (ExpressionTable)] as ExpressionTable;
			if (expressions != null) // 1st try: ExpressionTable
				expression = expressions [instance] as CodeExpression;

			if (expression == null) { // 2nd try: RootContext
				RootContext context = manager.Context[typeof (RootContext)] as RootContext;
				if (context != null && context.Value == instance)
					expression = context.Expression;
			}

			if (expression == null) { // 3rd try: IReferenceService (instance.property.property.property
				string name = manager.GetName (instance);
				if (name == null || name.IndexOf (".") == -1) {
					IReferenceService service = manager.GetService (typeof (IReferenceService)) as IReferenceService;
					if (service != null) {
						name = service.GetName (instance);
						if (name != null && name.IndexOf (".") != -1) {
							string[] parts = name.Split (new char[] { ',' });
							instance = manager.GetInstance (parts[0]);
							if (instance != null) {
								expression = SerializeToExpression (manager, instance);
								if (expression != null) {
									for (int i=1; i < parts.Length; i++)
										expression = new CodePropertyReferenceExpression (expression, parts[i]);
								}
							}
						}
					}
				}
			}
			return expression;
		}

		protected void SetExpression (IDesignerSerializationManager manager, object instance, CodeExpression expression)
		{
			SetExpression (manager, instance, expression, false);
		}

		// XXX: isPreset - what does this do when set?
		//
		protected void SetExpression (IDesignerSerializationManager manager, object instance, CodeExpression expression, bool isPreset)
		{
			if (manager == null)
				throw new ArgumentNullException ("manager");
			if (instance == null)
				throw new ArgumentNullException ("instance");
			if (expression == null)
				throw new ArgumentNullException ("expression");

			ExpressionTable expressions = manager.Context[typeof (ExpressionTable)] as ExpressionTable;
			if (expressions == null) {
				expressions = new ExpressionTable ();
				manager.Context.Append (expressions);
			}

			expressions[instance] = expression;
		}

		protected bool IsSerialized (IDesignerSerializationManager manager, object value) 
		{
			return this.IsSerialized (manager, value, false);
		}

		// XXX: What should honorPreset do?
		protected bool IsSerialized (IDesignerSerializationManager manager, object instance, bool honorPreset) 
		{
			if (instance == null)
				throw new ArgumentNullException ("instance");
			if (manager == null)
				throw new ArgumentNullException ("manager");

			if (this.GetExpression (manager, instance) != null)
				return true;
			else
				return false;
		}

		protected CodeExpression SerializeCreationExpression (IDesignerSerializationManager manager, object value, out bool isComplete) 
		{
			if (value == null)
				throw new ArgumentNullException ("value");
			if (manager == null)
				throw new ArgumentNullException ("manager");

			CodeExpression expression = null;

			TypeConverter converter = TypeDescriptor.GetConverter (value);
			if (converter != null && converter.CanConvertTo (typeof (InstanceDescriptor))) {
				InstanceDescriptor descriptor = converter.ConvertTo (value, typeof (InstanceDescriptor)) as InstanceDescriptor;
				isComplete = descriptor.IsComplete;
				if (descriptor != null && descriptor.MemberInfo != null)
					expression = this.SerializeInstanceDescriptor (manager, descriptor);
				else
					ReportError (manager, "Unable to serialize to InstanceDescriptor", 
						     "Value Type: " + value.GetType ().Name + System.Environment.NewLine + 
						     "Value (ToString): " + value.ToString ());
			} else {
				if (value.GetType().GetConstructor (Type.EmptyTypes) != null)
					expression = new CodeObjectCreateExpression (value.GetType ().FullName, new CodeExpression[0]);
				isComplete = false;
			}
			return expression;
		}

		private CodeExpression SerializeInstanceDescriptor (IDesignerSerializationManager manager, InstanceDescriptor descriptor)
		{
			CodeExpression expression = null;
			MemberInfo member = descriptor.MemberInfo;
			CodeExpression target = new CodeTypeReferenceExpression (member.DeclaringType);

			if (member is PropertyInfo) {
				expression = new CodePropertyReferenceExpression (target, member.Name);
			} else if (member is FieldInfo) {
				expression = new CodeFieldReferenceExpression (target, member.Name);
			} else if (member is MethodInfo) {
				CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression (target, member.Name);
				if (descriptor.Arguments != null && descriptor.Arguments.Count > 0)
					methodInvoke.Parameters.AddRange (SerializeParameters (manager, descriptor.Arguments));
				expression = methodInvoke;
			} else if (member is ConstructorInfo) {
				CodeObjectCreateExpression createExpr = new CodeObjectCreateExpression (member.DeclaringType);
				if (descriptor.Arguments != null && descriptor.Arguments.Count > 0)
					createExpr.Parameters.AddRange (SerializeParameters (manager, descriptor.Arguments));
				expression = createExpr;
			}

			return expression;
		}

		private CodeExpression[] SerializeParameters (IDesignerSerializationManager manager, ICollection parameters)
		{
			CodeExpression[] expressions = null;

			if (parameters != null && parameters.Count > 0) {
				expressions = new CodeExpression[parameters.Count];
				int i = 0;
				foreach (object parameter in parameters) {
					expressions[i] = this.SerializeToExpression (manager, parameter);
					i++;
				}
			}

			return expressions;
		}

		protected void SerializeEvent (IDesignerSerializationManager manager, CodeStatementCollection statements, 
					       object value, EventDescriptor descriptor) 
		{
			if (descriptor == null)
				throw new ArgumentNullException ("descriptor");
			if (value == null)
				throw new ArgumentNullException ("value");
			if (statements == null)
				throw new ArgumentNullException ("statements");
			if (manager == null)
				throw new ArgumentNullException ("manager");

			MemberCodeDomSerializer serializer = manager.GetSerializer (descriptor.GetType (), typeof (MemberCodeDomSerializer)) as MemberCodeDomSerializer;
			if (serializer != null && serializer.ShouldSerialize (manager, value, descriptor))
				serializer.Serialize (manager, value, descriptor, statements);
		}

		protected void SerializeEvents (IDesignerSerializationManager manager, CodeStatementCollection statements, 
						object value, params Attribute[] filter)
		{
			if (filter == null)
				throw new ArgumentNullException ("filter");
			if (value == null)
				throw new ArgumentNullException ("value");
			if (statements == null)
				throw new ArgumentNullException ("statements");
			if (manager == null)
				throw new ArgumentNullException ("manager");

			EventDescriptorCollection events = TypeDescriptor.GetEvents (value, filter);
			foreach (EventDescriptor e in events) 
				this.SerializeEvent (manager, statements, value, e);
		}

		protected void SerializeProperty (IDesignerSerializationManager manager, CodeStatementCollection statements, 
						  object value, PropertyDescriptor propertyToSerialize)
		{
			if (propertyToSerialize == null)
				throw new ArgumentNullException ("propertyToSerialize");
			if (value == null)
				throw new ArgumentNullException ("value");
			if (statements == null)
				throw new ArgumentNullException ("statements");
			if (manager == null)
				throw new ArgumentNullException ("manager");

			MemberCodeDomSerializer serializer = manager.GetSerializer (propertyToSerialize.GetType (), 
										    typeof (MemberCodeDomSerializer)) as MemberCodeDomSerializer;
			if (serializer != null && serializer.ShouldSerialize (manager, value, propertyToSerialize))
				serializer.Serialize (manager, value, propertyToSerialize, statements);
		}
		
		protected void SerializeProperties (IDesignerSerializationManager manager, CodeStatementCollection statements, 
											object value, Attribute[] filter) 
		{
			if (filter == null)
				throw new ArgumentNullException ("filter");
			if (value == null)
				throw new ArgumentNullException ("value");
			if (statements == null)
				throw new ArgumentNullException ("statements");
			if (manager == null)
				throw new ArgumentNullException ("manager");

			PropertyDescriptorCollection properties = TypeDescriptor.GetProperties (value, filter);
			foreach (PropertyDescriptor property in properties) {
				if (!property.Attributes.Contains (DesignerSerializationVisibilityAttribute.Hidden))
					this.SerializeProperty (manager, statements, value, property);
			}
		}

		protected virtual object DeserializeInstance (IDesignerSerializationManager manager, Type type, 
							      object[] parameters, string name, bool addToContainer)
		{
			if (type == null)
				throw new ArgumentNullException ("type");
			if (manager == null)
				throw new ArgumentNullException ("manager");

			return manager.CreateInstance (type, parameters, name, addToContainer);
		}

		protected string GetUniqueName (IDesignerSerializationManager manager, object instance)
		{
			if (instance == null)
				throw new ArgumentNullException ("instance");
			if (manager == null)
				throw new ArgumentNullException ("manager");

			string name = manager.GetName (instance);
			if (name == null) {
				INameCreationService service = manager.GetService (typeof (INameCreationService)) as INameCreationService;
				name = service.CreateName (null, instance.GetType ());
				if (name == null)
					name = instance.GetType ().Name.ToLower ();
				manager.SetName (instance, name);
			}
			return name;
		}

		protected object DeserializeExpression (IDesignerSerializationManager manager, string name, CodeExpression expression) 
		{
			if (expression == null)
				throw new ArgumentNullException ("expression");
			if (manager == null)
				throw new ArgumentNullException ("manager");

			bool errorOccurred = false;
			object deserialized = null;

			// CodeThisReferenceExpression
			//
			CodeThisReferenceExpression thisExpr = expression as CodeThisReferenceExpression;
			if (thisExpr != null) {
				RootContext context = manager.Context[typeof (RootContext)] as RootContext;
				if (context != null) {
					deserialized = context.Value;
				} else {
					IDesignerHost host = manager.GetService (typeof (IDesignerHost)) as IDesignerHost;
					if (host != null)
						deserialized = host.RootComponent;
				}
			}
			
			// CodeVariableReferenceExpression
			//
			CodeVariableReferenceExpression varRef = expression as CodeVariableReferenceExpression;
			if (deserialized == null && varRef != null) {
				deserialized = manager.GetInstance (varRef.VariableName);
				if (deserialized == null) {
					ReportError (manager, "Variable '" + varRef.VariableName + "' not initialized prior to reference");
					errorOccurred = true;
				}
			}

			// CodeFieldReferenceExpression (used for Enum references as well)
			//
			CodeFieldReferenceExpression fieldRef = expression as CodeFieldReferenceExpression;
			if (deserialized == null && fieldRef != null) {
				deserialized = manager.GetInstance (fieldRef.FieldName);
				if (deserialized == null) {
					object fieldHolder = DeserializeExpression (manager, null, fieldRef.TargetObject);
					FieldInfo field = null;
					if (fieldHolder is Type) // static field
						field = ((Type)fieldHolder).GetField (fieldRef.FieldName, 
										      BindingFlags.GetField | BindingFlags.Public | BindingFlags.Static);
					else // instance field
						field = fieldHolder.GetType().GetField (fieldRef.FieldName, 
											BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance);
					if (field != null)
						deserialized = field.GetValue (fieldHolder);
				}
				if (deserialized == null)
					ReportError (manager, "Field '" + fieldRef.FieldName + "' not initialized prior to reference");
			}
				

			// CodePrimitiveExpression
			//
			CodePrimitiveExpression primitiveExp = expression as CodePrimitiveExpression;
			if (deserialized == null && primitiveExp != null)
				deserialized = primitiveExp.Value;

			// CodePropertyReferenceExpression
			//
			CodePropertyReferenceExpression propRef = expression as CodePropertyReferenceExpression;
			if (deserialized == null && propRef != null) {
				object target = DeserializeExpression (manager, null, propRef.TargetObject);
				if (target != null && target != _errorMarker) {
					bool found = false;
					if (target is Type) {
						PropertyInfo property = ((Type)target).GetProperty (propRef.PropertyName,
												    BindingFlags.GetProperty | 
												    BindingFlags.Public | BindingFlags.Static);
						if (property != null) {
							deserialized = property.GetValue (null, null);
							found = true;
						}

						// NRefactory seems to produce PropertyReferences to reference some fields and enums
						//
						FieldInfo field = ((Type)target).GetField (propRef.PropertyName,
											   BindingFlags.GetField | BindingFlags.Public | BindingFlags.Static);
						if (field != null) {
							deserialized = field.GetValue (null);
							found = true;
						}
					} else {
						PropertyDescriptor property = TypeDescriptor.GetProperties (target)[propRef.PropertyName];
						if (property != null) {
							deserialized = property.GetValue (target);
							found = true;
						}

						FieldInfo field = target.GetType().GetField (propRef.PropertyName,
											     BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance);
						if (field != null) {
							deserialized = field.GetValue (null);
							found = true;
						}
					}
					
					if (!found) {
						ReportError (manager, "Missing field '" + propRef.PropertyName + " 'in type " + 
							     (target is Type ? ((Type)target).Name : target.GetType ().Name) + "'");
						errorOccurred = true;
					}
				}
			}

			// CodeObjectCreateExpression
			//
			CodeObjectCreateExpression createExpr = expression as CodeObjectCreateExpression;
			if (deserialized == null && createExpr != null) {
				Type type = manager.GetType (createExpr.CreateType.BaseType);
				if (type == null) {
					ReportError (manager, "Type '" + createExpr.CreateType.BaseType + "' not found." + 
						     "Are you missing a reference?");
					errorOccurred = true;
				} else {
					object[] arguments = new object[createExpr.Parameters.Count];
					for (int i=0; i < createExpr.Parameters.Count; i++) {
						arguments[i] = this.DeserializeExpression (manager, null, createExpr.Parameters[i]);
						if (arguments[i] == _errorMarker) {
							errorOccurred = true;
							break;
						}
					}
					if (!errorOccurred) {
						bool addToContainer = false;
						if (typeof(IComponent).IsAssignableFrom (type))
							addToContainer = true;
						deserialized = this.DeserializeInstance (manager, type, arguments, name, addToContainer);
						if (deserialized == _errorMarker || deserialized == null) {
							string info = "Type to create: " + createExpr.CreateType.BaseType + System.Environment.NewLine +
								"Name: " + name + System.Environment.NewLine +
								"addToContainer: " + addToContainer.ToString () + System.Environment.NewLine +
								"Parameters Count: " + createExpr.Parameters.Count + System.Environment.NewLine;
	
							for (int i=0; i < arguments.Length; i++) {
								info += "Parameter Number: " + i.ToString () + System.Environment.NewLine +
									"Parameter Type: " + (arguments[i] == null ? "null" : arguments[i].GetType ().Name) +
									System.Environment.NewLine +
									"Parameter '" + i.ToString () + "' Value: " + arguments[i].ToString () + System.Environment.NewLine;
							}
							ReportError (manager, 
								     "Unable to create an instance of type '" + createExpr.CreateType.BaseType + "'",
								     info);
							errorOccurred = true;
						}
					}
				}
			}

			// CodeArrayCreateExpression
			//
			CodeArrayCreateExpression arrayCreateExpr = expression as CodeArrayCreateExpression;
			if (deserialized == null && arrayCreateExpr != null) {
				Type arrayType = manager.GetType (arrayCreateExpr.CreateType.BaseType);
				if (arrayType == null) {
					ReportError (manager, "Type '" + arrayCreateExpr.CreateType.BaseType + "' not found." + 
						     "Are you missing a reference?");
					errorOccurred = true;
				} else {
					ArrayList initializers = new ArrayList ();
					Type elementType = arrayType.GetElementType ();
					deserialized = Array.CreateInstance (arrayType, arrayCreateExpr.Initializers.Count);
					for (int i = 0; i < arrayCreateExpr.Initializers.Count; i++) {
						object element = this.DeserializeExpression (manager, null, arrayCreateExpr.Initializers[i]);
						errorOccurred = (element == _errorMarker);
						if (!errorOccurred) {
							if (arrayType.IsInstanceOfType (element)) {
								initializers.Add (element);
							} else {
								ReportError (manager, 
									     "Array initializer element type incompatible with array type.",
									     "Array Type: " + arrayType.Name + System.Environment.NewLine +
									     "Array Element Type: " + elementType + System.Environment.NewLine +
									     "Initializer Type: " + (element == null ? "null" : element.GetType ().Name));
								errorOccurred = true;
							}
						}
					}
					if (!errorOccurred)
						initializers.CopyTo ((Array)deserialized, 0);
				}
			}

			// CodeMethodInvokeExpression
			//
			CodeMethodInvokeExpression methodExpr = expression as CodeMethodInvokeExpression;
			if (deserialized == null && methodExpr != null) {
				object target = this.DeserializeExpression (manager, null, methodExpr.Method.TargetObject);
				object[] parameters = null;
				if (target == _errorMarker || target == null) {
					errorOccurred = true;
				} else {
					parameters = new object[methodExpr.Parameters.Count];
					for (int i=0; i < methodExpr.Parameters.Count; i++) {
						parameters[i] = this.DeserializeExpression (manager, null, methodExpr.Parameters[i]);
						if (parameters[i] == _errorMarker) {
							errorOccurred = true;
							break;
						}
					}
				}

				if (!errorOccurred) {
					MethodInfo method = null;
					if (target is Type) {
						method = GetExactMethod ((Type)target, methodExpr.Method.MethodName, 
												 BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static,
												 parameters);
					} else {
						method = GetExactMethod (target.GetType(), methodExpr.Method.MethodName, 
												 BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance,
												 parameters);
					}
	
					if (method != null) {
						deserialized = method.Invoke (target, parameters);
					} else {
						string info = 
							"Method Name: " + methodExpr.Method.MethodName + System.Environment.NewLine +
							"Method is: " + (target is Type ? "static" : "instance") + System.Environment.NewLine +
							"Method Holder Type: " + (target is Type ? ((Type)target).Name : target.GetType ().Name) + System.Environment.NewLine +
							"Parameters Count: " + methodExpr.Parameters.Count + System.Environment.NewLine +
							System.Environment.NewLine;

						for (int i = 0; i < parameters.Length; i++) {
							info += "Parameter Number: " + i.ToString () + System.Environment.NewLine +
								"Parameter Type: " + (parameters[i] == null ? "null" : parameters[i].GetType ().Name) +
								System.Environment.NewLine +
								"Parameter " + i.ToString () + " Value: " + parameters[i].ToString () + System.Environment.NewLine;
						}
						ReportError (manager, 
							     "Method '" + methodExpr.Method.MethodName + "' missing in type '" + 
							     (target is Type ? ((Type)target).Name : target.GetType ().Name + "'"),
							     info);
						errorOccurred = true;
					}
				}
			}

			// CodeTypeReferenceExpression
			//
			CodeTypeReferenceExpression typeRef = expression as CodeTypeReferenceExpression;
			if (deserialized == null && typeRef != null) {
				deserialized = manager.GetType (typeRef.Type.BaseType);
				if (deserialized == null) {
					ReportError (manager, "Type '" + typeRef.Type.BaseType + "' not found." + 
						     "Are you missing a reference?");
					errorOccurred = true;
				}
			}

			// CodeCastExpression
			// 
			CodeCastExpression castExpr = expression as CodeCastExpression;
			if (deserialized == null && castExpr != null) {
				Type targetType = manager.GetType (castExpr.TargetType.BaseType);
				object instance = DeserializeExpression (manager, null, castExpr.Expression);
				if (instance != null && instance != _errorMarker && targetType != null) {
					IConvertible convertible = instance as IConvertible;
					if (convertible != null) {
						try {
							instance = convertible.ToType (targetType, null);
						} catch {
							errorOccurred = true;
						}
					} else {
						errorOccurred = true;
					}
					if (errorOccurred) {
						ReportError (manager, "Unable to convert type '" + instance.GetType ().Name + 
							     "' to type '" + castExpr.TargetType.BaseType + "'",
							     "Target Type: " + castExpr.TargetType.BaseType + System.Environment.NewLine +
							     "Instance Type: " + (instance == null ? "null" : instance.GetType ().Name) + System.Environment.NewLine +
							     "Instance Value: " + (instance == null ? "null" : instance.ToString()) + System.Environment.NewLine +
							     "Instance is IConvertible: " + (instance is IConvertible).ToString());
					}

					deserialized = instance;
				}
			}


			// CodeBinaryOperatorExpression
			//
			CodeBinaryOperatorExpression binOperator = expression as CodeBinaryOperatorExpression;
			if (deserialized == null && binOperator != null) {
				string errorText = null;
				IConvertible left = null;
				IConvertible right = null;
				switch (binOperator.Operator) {
					case CodeBinaryOperatorType.BitwiseOr:
						left = DeserializeExpression (manager, null, binOperator.Left) as IConvertible;
						right = DeserializeExpression (manager, null, binOperator.Right) as IConvertible;
						if (left is Enum && right is Enum) {
							deserialized = Enum.ToObject (left.GetType (), Convert.ToInt64 (left) | Convert.ToInt64 (right));
						} else {
							errorText = "CodeBinaryOperatorType.BitwiseOr allowed only on Enum types";
							errorOccurred = true;
						}
						break;
					default:
						errorText = "Unsupported CodeBinaryOperatorType: " + binOperator.Operator.ToString ();
						errorOccurred = true;
						break;
				}

				if (errorOccurred) {
					string info = "BinaryOperator Type: " + binOperator.Operator.ToString() + System.Environment.NewLine +
						"Left Type: " + (left == null ? "null" : left.GetType().Name) + System.Environment.NewLine +
						"Left Value: " + (left == null ? "null" : left.ToString ()) + System.Environment.NewLine +
						"Left Expression Type: " + binOperator.Left.GetType ().Name + System.Environment.NewLine +
						"Right Type: " + (right == null ? "null" : right.GetType().Name) + System.Environment.NewLine +
						"Right Value: " + (right == null ? "null" : right.ToString ()) + System.Environment.NewLine +
						"Right Expression Type: " + binOperator.Right.GetType ().Name;
					ReportError (manager, errorText, info);
				}
			}


			if (!errorOccurred) {
				if (deserialized == null && !(expression is CodePrimitiveExpression) && !(expression is CodeMethodInvokeExpression)) {
					ReportError (manager, "Unsupported Expression Type: " + expression.GetType ().Name);
					errorOccurred = true;
				}
			}

			if (errorOccurred)
				deserialized = _errorMarker;
			return deserialized;
		}

		// Searches for a method on type that matches argument types
		//
		private MethodInfo GetExactMethod (Type type, string methodName, BindingFlags flags, ICollection argsCollection)
		{
			object[] arguments = null;
			Type[] types = Type.EmptyTypes;

			if (argsCollection != null) {
				arguments = new object[argsCollection.Count];
				types = new Type[argsCollection.Count];
				argsCollection.CopyTo (arguments, 0);

				for (int i=0; i < arguments.Length; i++) {
					if (arguments[i] == null)
						types[i] = null;
					else
						types[i] = arguments[i].GetType ();
				}
			}

			return type.GetMethod (methodName, flags, null, types, null);
		}

		protected void DeserializeStatement (IDesignerSerializationManager manager, CodeStatement statement)
		{
			if (statement == null)
				throw new ArgumentNullException ("statement");
			if (manager == null)
				throw new ArgumentNullException ("manager");

			// CodeAssignStatement
			//
			CodeAssignStatement assignment = statement as CodeAssignStatement;
			if (assignment != null)
				DeserializeAssignmentStatement (manager, assignment);

			// CodeExpressionStatement
			//
			CodeExpressionStatement expression = statement as CodeExpressionStatement;
			if (expression != null)
				this.DeserializeExpression (manager, null, expression.Expression);

			// CodeAttachEventStatement
			//
			CodeAttachEventStatement attachStatement = statement as CodeAttachEventStatement;
			if (attachStatement != null) {
				string methodName = null;

				CodeObjectCreateExpression createExpr = attachStatement.Listener as CodeObjectCreateExpression;
				if (createExpr != null && createExpr.Parameters.Count == 1 ) { // += new EventType (method)
					CodeMethodReferenceExpression handlerRef = createExpr.Parameters[0] as CodeMethodReferenceExpression;
					if (handlerRef != null)
						methodName = handlerRef.MethodName;
				}

				CodeDelegateCreateExpression delegateCreateExpr = attachStatement.Listener as CodeDelegateCreateExpression;
				if (delegateCreateExpr != null)// += new EventType (method)
					methodName = delegateCreateExpr.MethodName;

				CodeMethodReferenceExpression methodRef = attachStatement.Listener as CodeMethodReferenceExpression;
				if (methodRef != null) // += method
					methodName = methodRef.MethodName;

				object component = DeserializeExpression (manager, null, attachStatement.Event.TargetObject);
				if (component != null && component != _errorMarker && methodName != null) {
					string error = null;
					EventDescriptor eventDescriptor = TypeDescriptor.GetEvents (component)[attachStatement.Event.EventName];
					if (eventDescriptor != null) {
						IEventBindingService service = manager.GetService (typeof (IEventBindingService)) as IEventBindingService;
						if (service != null)
							service.GetEventProperty (eventDescriptor).SetValue (component, methodName);
						else
							error = "IEventBindingService missing";
					} else {
						error = "No event '" + attachStatement.Event.EventName + 
							"' found in type '" + component.GetType ().Name + "'";
					}

					if (error != null) {
						ReportError (manager, error, "Method Name: " + methodName + System.Environment.NewLine +
							"Event Name: " + attachStatement.Event.EventName + System.Environment.NewLine +
							"Listener Expression Type: " + methodRef.GetType ().Name + System.Environment.NewLine +
							"Event Holder Type: " + component.GetType ().Name + System.Environment.NewLine +
							"Event Holder Expression Type: " + attachStatement.Event.TargetObject.GetType ().Name);
					}
				}
			}
		}

		private void DeserializeAssignmentStatement (IDesignerSerializationManager manager, CodeAssignStatement statement)
		{
			CodeExpression leftExpr = statement.Left;
			
			// Assign to a Property
			//
			CodePropertyReferenceExpression propRef = leftExpr as CodePropertyReferenceExpression;
			if (propRef != null) {
				object propertyHolder = DeserializeExpression (manager, null, propRef.TargetObject);
				object value = null;
				if (propertyHolder != null && propertyHolder != _errorMarker)
					value = DeserializeExpression (manager, null, statement.Right);

				if (value != null && value != _errorMarker && propertyHolder != null) {
					PropertyDescriptor property = TypeDescriptor.GetProperties (propertyHolder)[propRef.PropertyName];
					if (property != null) {
						property.SetValue (propertyHolder, value);
					} else {
						ReportError (manager, 
							     "Missing property '" + propRef.PropertyName + 
							     "' in type '" + propertyHolder.GetType ().Name + "'");
					}
				}
			}
			
			// Assign to a Field
			// 
			CodeFieldReferenceExpression fieldRef = leftExpr as CodeFieldReferenceExpression;
			if (fieldRef != null && fieldRef.FieldName != null) {
				// Note that if the Right expression is a CodeCreationExpression the component will be created in this call
				//
				object fieldHolder = DeserializeExpression (manager, null, fieldRef.TargetObject);
				object value = null;
				if (fieldHolder != null && fieldHolder != _errorMarker)
					value = DeserializeExpression (manager, fieldRef.FieldName, statement.Right);
				FieldInfo field = null;

				RootContext context = manager.Context[typeof (RootContext)] as RootContext;
				if (fieldHolder != null && fieldHolder != _errorMarker && value != _errorMarker) {
					if (fieldRef.TargetObject is CodeThisReferenceExpression && context != null && context.Value == fieldHolder) {
						// Do not deserialize the fields of the root component, because the root component type 
						// is actually an instance of the its parent type, e.g: CustomControl : _UserControl_
						// and thus it doesn't contain the fields. The trick is that once DeserializeExpression 
						// is called on a CodeObjectCreateExpression the component is created and is added to the name-instance
						// table.
						// 
					} else {
						if (fieldHolder is Type) // static field
							field = ((Type)fieldHolder).GetField (fieldRef.FieldName, 
											      BindingFlags.GetField | BindingFlags.Public | BindingFlags.Static);
						else // instance field
							field = fieldHolder.GetType().GetField (fieldRef.FieldName, 
												BindingFlags.GetField | BindingFlags.Public | BindingFlags.Instance);
						if (field != null)
							field.SetValue (fieldHolder, value);
						else {
							ReportError (manager, "Field '" + fieldRef.FieldName + "' missing in type '" +
								     fieldHolder.GetType ().Name + "'",
								     "Field Name: " + fieldRef.FieldName + System.Environment.NewLine +
								     "Field is: " + (fieldHolder is Type ? "static" : "instance") + System.Environment.NewLine +
								     "Field Value: " + (value == null ? "null" : value.ToString()) + System.Environment.NewLine +
								     "Field Holder Type: " + fieldHolder.GetType ().Name + System.Environment.NewLine +
								     "Field Holder Expression Type: " + fieldRef.TargetObject.GetType ().Name);
						}
					}
				}
			}

			// Assign to a Variable
			// 
			CodeVariableReferenceExpression varRef = leftExpr as CodeVariableReferenceExpression;
			if (varRef != null && varRef.VariableName != null) {
				object value = DeserializeExpression (manager, varRef.VariableName, statement.Right);
				// If .Right is not CodeObjectCreateExpression the instance won't be assigned a name, 
				// so do it ourselves
				if (value != _errorMarker && manager.GetName (value) == null)
					manager.SetName (value, varRef.VariableName);
			}
		}

		internal void ReportError (IDesignerSerializationManager manager, string message)
		{
			this.ReportError (manager, message, String.Empty);
		}

		internal void ReportError (IDesignerSerializationManager manager, string message, string details)
		{
			try {
				throw new Exception (message);
			} catch (Exception e) {
				e.Data["Details"] = message + Environment.NewLine + Environment.NewLine + details;
				manager.ReportError (e);
			}
		}

#region Resource Serialization - TODO
		protected CodeExpression SerializeToResourceExpression (IDesignerSerializationManager manager, object value) 
		{
			throw new NotImplementedException ();
		}
		
		protected CodeExpression SerializeToResourceExpression (IDesignerSerializationManager manager, object value, 
									bool ensureInvariant) 
		{
			throw new NotImplementedException ();
		}
	 	
		protected void SerializePropertiesToResources (IDesignerSerializationManager manager, CodeStatementCollection statements, 
							       object value, Attribute[] filter) 
		{
			throw new NotImplementedException ();
		}

		protected void SerializeResource (IDesignerSerializationManager manager, string resourceName, object value)
		{
			throw new NotImplementedException ();
		}

		protected void SerializeResourceInvariant (IDesignerSerializationManager manager, string resourceName, object value) 
		{
			throw new NotImplementedException ();
		}

		protected void DeserializePropertiesFromResources (IDesignerSerializationManager manager, object value, Attribute[] filter)
		{
			throw new NotImplementedException ();
		}
#endregion
	}
}
