// <file>
//     <copyright see="prj:///doc/copyright.txt"/>
//     <license see="prj:///doc/license.txt"/>
//     <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
//     <version value="$version"/>
// </file>

using System;
using System.IO;
using System.Xml;
using System.Collections;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Text;
using System.Text.RegularExpressions;

using System.CodeDom;
using System.CodeDom.Compiler;

using Microsoft.CSharp;
using Microsoft.VisualBasic;
using ICSharpCode.SharpDevelop.Services;

namespace ICSharpCode.SharpDevelop.FormDesigner
{
	/// <summary>
	/// This class is able to generate a CodeDOM definition out of a XML file.
	/// </summary>
	public class CodeDOMGenerator
	{
		IDesignerHost   host;
		AmbienceService ambienceService = (AmbienceService)ICSharpCode.Core.Services.ServiceManager.Services.GetService(typeof(AmbienceService));
		
		string acceptButtonName = String.Empty;
		string cancelButtonName = String.Empty;
		
		int num = 0;
		ICodeGenerator      codeGenerator;
		CodeTypeDeclaration codeTypeDeclaration;
		
		CodeNamespace           codeNamespace;
		CodeDOMGeneratorUtility codeDOMGeneratorUtility = new CodeDOMGeneratorUtility();
		
		public CodeDOMGenerator(IDesignerHost host, CodeDomProvider codeProvider)
		{
			this.host = host;
			codeGenerator = codeProvider.CreateGenerator();
			
			codeNamespace = new CodeNamespace("GeneratedForm");
			
			this.codeDOMGeneratorUtility.AddNamespaceImport("System");
			this.codeDOMGeneratorUtility.AddNamespaceImport("System.Windows.Forms");
			
			codeNamespace.Comments.Add(new CodeCommentStatement("This file was autogenerated by a tool.", false));
		}
		
		public void ConvertContentDefinition(XmlDocument doc, TextWriter writer)
		{
			XmlElement rootComponent = (XmlElement)doc.DocumentElement.ChildNodes[0];
			
			// create class
			codeTypeDeclaration = new CodeTypeDeclaration(rootComponent.SelectSingleNode("Name/@value").Value);
			if (ambienceService.GenerateDocumentComments) {
				codeTypeDeclaration.Comments.Add(new CodeCommentStatement("<summary>", true));
				codeTypeDeclaration.Comments.Add(new CodeCommentStatement("    Add summary description for " + codeTypeDeclaration.Name, true));
				codeTypeDeclaration.Comments.Add(new CodeCommentStatement("</summary>", true));
			}
			codeTypeDeclaration.BaseTypes.Add(codeDOMGeneratorUtility.GetTypeReference(rootComponent.Name));
			codeNamespace.Types.Add(codeTypeDeclaration);
			
			// create constructor
			CodeConstructor cc = new CodeConstructor();
			cc.Attributes =  MemberAttributes.Public | MemberAttributes.Final;
			if (ambienceService.GenerateAdditionalComments) {
				cc.Statements.Add(new CodeCommentStatement(" Must be called for initialization", false));
			}
			cc.Statements.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "InitializeComponents")));
			if (ambienceService.GenerateAdditionalComments) {
				cc.Statements.Add(new CodeCommentStatement("", false));
				cc.Statements.Add(new CodeCommentStatement(" TODO : Add constructor code after InitializeComponents", false));
				cc.Statements.Add(new CodeCommentStatement("", false));
			}
			codeTypeDeclaration.Members.Add(cc);
			
			// create intialize components method
			CodeMemberMethod cm = new CodeMemberMethod();
			if (ambienceService.GenerateDocumentComments) {
				cm.Comments.Add(new CodeCommentStatement("<summary>", true));
				cm.Comments.Add(new CodeCommentStatement("  This method was autogenerated - do not change the contents manually", true));
				cm.Comments.Add(new CodeCommentStatement("</summary>", true));
			}
			
			cm.Name = "InitializeComponents";
			cm.ReturnType = new CodeTypeReference(typeof(void));
			
			// set up root component.
			SetUpObject(cm, null, host.GetType(rootComponent.Name), rootComponent);
			
			// set up other components
			Console.WriteLine("Node Count " + doc.DocumentElement.ChildNodes.Count);
			for (int i = 1; i < doc.DocumentElement.ChildNodes.Count; ++i) {
				XmlElement el = (XmlElement)doc.DocumentElement.ChildNodes[i];
				SetUpObject(cm, el.SelectSingleNode("Name/@value").Value, host.GetType(el.Name), el);
			}
			
			cm.Statements.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "ResumeLayout", new CodePrimitiveExpression(false))));
			codeTypeDeclaration.Members.Add(cm);
			
			// finally generate code
			this.codeDOMGeneratorUtility.GenerateNamespaceImports(codeNamespace);
			codeGenerator.GenerateCodeFromNamespace(codeNamespace, writer, codeDOMGeneratorUtility.CreateCodeGeneratorOptions);
		}
		
		public void ConvertContentDefinition(XmlDocument doc, string fileName)
		{
			StreamWriter writer = File.CreateText(fileName);
			ConvertContentDefinition(doc, writer);
			writer.Close();
		}
		
		void SetUpObject(CodeMemberMethod cm, string name, Type currentType, XmlElement element)
		{
			bool isThis = name == null || name.Length == 0;
			
			if (!isThis) {
				cm.Statements.Add(new CodeSnippetStatement(""));
			}
			
			if (ambienceService.GenerateAdditionalComments) {
				cm.Statements.Add(new CodeCommentStatement("", false));
				cm.Statements.Add(new CodeCommentStatement(" Set up " + (isThis ? "generated class " + codeTypeDeclaration.Name : "member " + name), false));
				cm.Statements.Add(new CodeCommentStatement("", false));
			}
			
			// create new member variable 
			if (isThis) {
				cm.Statements.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "SuspendLayout")));
			} else {
				codeTypeDeclaration.Members.Add(new CodeMemberField(codeDOMGeneratorUtility.GetTypeReference(currentType.FullName),
				                                   name));
				cm.Statements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(null, name),
				                                          new CodeObjectCreateExpression(codeDOMGeneratorUtility.GetTypeReference(currentType))));
			}
			
			foreach (XmlNode subNode in element.ChildNodes) {
				if (subNode is XmlElement) {
					SetAttributes(cm, name, currentType, (XmlElement)subNode);
				}
			}
		}
				
		void SetAttributes(CodeMemberMethod cm, string name, Type currentType, XmlElement el)
		{
			CodeExpression nameReferenceExpression = name == null || name.Length == 0 ? (CodeExpression)new CodeThisReferenceExpression() : (CodeExpression)new CodeTypeReferenceExpression(name);
			if (el.Attributes["value"] != null) {
				string val = el.Attributes["value"].InnerText;
				try {
					SetValue(cm, name, currentType, el.Name, val);
				} catch (Exception) {}
			} else {
				PropertyInfo propertyInfo = currentType.GetProperty(el.Name);
				bool isList = false;
				if (propertyInfo != null) {
					Type[] interfaces = propertyInfo.PropertyType.GetInterfaces();
					foreach (Type i in interfaces) {
						if (i == typeof(IList)) {
							isList = true;
							break;
						}
					}
				}
				
				if (isList) {
					foreach (XmlNode subNode in el.ChildNodes) {
						if (subNode is XmlElement) {
							XmlElement subElement = (XmlElement)subNode;
							Type collectionType = host.GetType(subElement.Name);
							if (collectionType == null) {
								continue;
							}
							string objName = null;
							bool isComponent = false;
							Type[] interfaces = collectionType.GetInterfaces();
							foreach (Type i in interfaces) {
								if (i == typeof(IComponent)) {
									isComponent = true;
									break;
								}
							}
							
							if (isComponent && subElement["Name"] != null && subElement["Name"].Attributes["value"] != null) {
								objName = subElement["Name"].Attributes["value"].InnerText;
							}
							
							if (objName == null || objName.Length == 0) {
								objName = "createdObject" + num++;
							}
							
							SetUpObject(cm, objName, collectionType, subElement);
							cm.Statements.Add(new CodeExpressionStatement(new CodeMethodInvokeExpression(new CodePropertyReferenceExpression(nameReferenceExpression, el.Name), 
							                                                                             "Add",
							                                                                             new CodeFieldReferenceExpression(null, objName))));
						}
					}
				}
			}
		}
		
		void SetValue(CodeMemberMethod cm, string name, Type currentType, string propertyName, string val)
		{
			// HACK: don't know why these property pops up ... it's filtered for now
			// TODO: make a better solution for this
			if (propertyName == "DataBindings") {
				return;
			}
			
			CodeExpression nameReferenceExpression = name == null || name.Length == 0 ? (CodeExpression)new CodeThisReferenceExpression() : (CodeExpression)new CodeTypeReferenceExpression(name);
			try {
				PropertyInfo propertyInfo = currentType.GetProperty(propertyName);
				
				if (propertyInfo == null) {
					return;
				}
				
				if (val.StartsWith("{") && val.EndsWith("}")) {
					val = val.Substring(1, val.Length - 2);
					Type   newType       = host.GetType(propertyInfo.PropertyType.FullName);
					ArrayList parameters = new ArrayList();
					Regex propertySet  = new Regex(@"(?<Property>[\w]+)\s*=\s*(?<Value>[\w\d]+)", RegexOptions.Compiled);
					Match match = propertySet.Match(val);
					while (true) {
						if (!match.Success) {
							break;
						}
						parameters.Add(new CodePropertyReferenceExpression(null, match.Result("${Value}")));
						
						match = match.NextMatch();
					}
					
					CodeExpression expr = new CodePropertyReferenceExpression(nameReferenceExpression,
					                                                          propertyName);
					cm.Statements.Add(new CodeAssignStatement(expr,
															  new CodeObjectCreateExpression(codeDOMGeneratorUtility.GetTypeReference(propertyInfo.PropertyType.FullName),
															                                 (CodeExpression[])parameters.ToArray(typeof(CodeExpression)))));
				} else if (propertyInfo.PropertyType.IsEnum) {
					cm.Statements.Add(new CodeAssignStatement(new CodePropertyReferenceExpression(nameReferenceExpression, propertyInfo.Name),
															  GetExpressionForEnum(propertyInfo.PropertyType, val)));
				} else if (propertyInfo.PropertyType == typeof(Color)) {
					string color = val.Substring(val.IndexOf('[') + 1).Replace("]", "");
					Type colorType = typeof(System.Drawing.Color);
					CodeExpression ce = null;
					if (colorType.GetProperty(color) != null) {
						ce = new CodePropertyReferenceExpression(codeDOMGeneratorUtility.GetTypeReferenceExpression(colorType),
						                                         color);
						
					} else if (typeof(KnownColor).GetField(color) != null){
						ce = new CodeMethodInvokeExpression(codeDOMGeneratorUtility.GetTypeReferenceExpression(colorType),
						                                    "FromKnownColor",
						                                    new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeof(KnownColor)), color));
					} else {
						string[] argb = color.Split(',', '=');
						ce = new CodeMethodInvokeExpression(codeDOMGeneratorUtility.GetTypeReferenceExpression(colorType),
						                                    "FromArgb",
						                                    new CodeArgumentReferenceExpression(argb[1]),
						                                    new CodeArgumentReferenceExpression(argb[3]),
						                                    new CodeArgumentReferenceExpression(argb[5]),
						                                    new CodeArgumentReferenceExpression(argb[7]));
					}
					cm.Statements.Add(new CodeAssignStatement(new CodePropertyReferenceExpression(nameReferenceExpression, propertyInfo.Name), ce));
				} else if (propertyInfo.PropertyType == typeof(System.Drawing.Font)) {
					// TODO : correct font parameters bold, italic
					string[] font = val.Split(',', '=');
					cm.Statements.Add(new CodeAssignStatement(new CodePropertyReferenceExpression(nameReferenceExpression, propertyInfo.Name),
					                                          new CodeObjectCreateExpression(codeDOMGeneratorUtility.GetTypeReference(typeof(System.Drawing.Font)),
					                                                                         new CodePrimitiveExpression(font[1]),
					                                                                         new CodePrimitiveExpression(Int32.Parse(font[3]))
					                                                                         )));
				} else if (propertyInfo.PropertyType == typeof(System.Windows.Forms.Cursor)) {
					string[] cursor = val.Split('[', ']', ' ', ':');
					cm.Statements.Add(new CodeAssignStatement(new CodePropertyReferenceExpression(nameReferenceExpression, propertyInfo.Name),
					                                          new CodePropertyReferenceExpression(codeDOMGeneratorUtility.GetTypeReferenceExpression(typeof(System.Windows.Forms.Cursors)),
					                                                                              cursor[3]
					                                                                              )));
				} else {
					if (val != null && val.Length > 0) {
						cm.Statements.Add(new CodeAssignStatement(new CodePropertyReferenceExpression(nameReferenceExpression, propertyInfo.Name),
						                                          new CodePrimitiveExpression(Convert.ChangeType(val, propertyInfo.PropertyType))));
					}
				}
			} catch (Exception e) {
				Console.WriteLine(e.ToString());
			}
		}
		
		CodeExpression GetExpressionForEnum(Type enumType, string val)
		{
			string[] enums = val.Split(',');
			for (int i = 0; i < enums.Length; ++i) {
				enums[i] = enums[i].Trim();
			}
			return GetExpression(enumType.FullName, enums, 0);
		}
		
		CodeExpression GetExpression(string enumType, string[] enums, int index)
		{
			CodeExpression curExpression = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(enumType), enums[index]);
			
			if (index + 1 < enums.Length) {
				return new CodeBinaryOperatorExpression(curExpression,
				                                        CodeBinaryOperatorType.BitwiseOr,
				                                        GetExpression(enumType, enums, index + 1));
			}
			return curExpression;
		}
	}
}
