<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Globalization" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
using System;
using System.Collections.Generic;
using System.Composition;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.ServiceLayer;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes;

namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
{
	
<#
	var directory = Path.GetDirectoryName(Host.TemplateFile);
	string xmlFile = Path.Combine(directory, "TreeNodeDefinition.xml");
	
	/////////
	// TODO - is Generate all the ReverseDependencies needed?
	/////////
	// var allReverseDependencies = GetReverseDependencies(xmlFile);
	// WriteLine("    internal static class TreeNodeRules");
	// WriteLine("    {");
	// WriteLine("        internal static Dictionary<Type, IList<Type>> TypeReverseDependencyMap = new Dictionary<Type, IList<Type>>()");
	// WriteLine("        {");
	// foreach (var reverseDependencyKey in allReverseDependencies.Keys)
	// {
	// 	bool isFirstDependentType = true;
	// 	StringBuilder dependentListBuilder = new StringBuilder("{");
	// 	foreach (var dependentType in allReverseDependencies[reverseDependencyKey])
	// 	{
	// 		if (isFirstDependentType)
	// 		{
	// 			isFirstDependentType = false;
	// 		}
	// 		else
	// 		{
	// 			dependentListBuilder.Append(",");
	// 		}
	// 
	// 		dependentListBuilder.Append(string.Format(CultureInfo.InvariantCulture, " typeof({0})", dependentType));
	// 	}
	// 	dependentListBuilder.Append(" }");
	// 
	// 	WriteLine(string.Format(CultureInfo.InvariantCulture, "                {{ typeof({0}), new List<Type> {1} }}", reverseDependencyKey, dependentListBuilder.ToString()));
	// }
	// WriteLine("        };");
	// WriteLine("    }");
	// WriteLine("");

	/////////
	// First generate all the TreeNodes
	/////////
	var allTreeNodes = GetUniqueTreeNodes(xmlFile);
	foreach (var TreeNode in allTreeNodes)
	{
		var name = TreeNode.GetAttribute("Name");
		WriteLine(string.Format("    internal sealed partial class {0} : SmoTreeNode", name));
		WriteLine("    {");
		WriteLine(string.Format("    	public {0}() : base()", name));
		WriteLine("    	{");
		WriteLine("    		NodeValue = string.Empty;");
		WriteLine(string.Format("    		this.NodeType = \"{0}\";", name.Replace("TreeNode", string.Empty)));
		WriteLine(string.Format("    		this.NodeTypeId = NodeTypes.{0};", name.Replace("TreeNode", string.Empty)));
		WriteLine("	    	OnInitialize();");
		WriteLine("    	}");
		WriteLine("    }");
		WriteLine("");
	}

	/////////
	// Now generate all the ChildFactories
	/////////
	var allNodes = GetNodes(xmlFile);
	foreach (var type in allNodes)
	{
		XmlElement nodeElement = GetNodeElement(xmlFile, type);
		var imageAttr = nodeElement.GetAttribute("Image");
		var isAlwaysLeaf = nodeElement.GetAttributeNode("IsAlwaysLeaf");
		var baseClass = nodeElement.GetAttribute("BaseClass");
		var strategy = nodeElement.GetAttribute("Strategy");
		var nodeType = nodeElement.GetAttribute("NodeType");
		var ChildQuerierTypes = nodeElement.GetAttribute("ChildQuerierTypes");
		var TreeNode = nodeElement.GetAttribute("TreeNode");
		var isAsync = nodeElement.GetAttributeNode("IsAsyncLoad");
		var disableSort = nodeElement.GetAttributeNode("DisableSort");

		string childFactoryBaseClass = "SmoChildFactoryBase";
		
		// TODO Will we need alternative child factories? If so, add code here to support this
		
		if (isAlwaysLeaf == null)
		{
			WriteLine("    [Export(typeof(ChildFactory))]");
			WriteLine("    [Shared]");
			
			WriteLine(string.Format("    internal partial class {0}ChildFactory : {1}", type, childFactoryBaseClass));
			
			WriteLine("    {");
			WriteLine(string.Format("        public override IEnumerable<string> ApplicableParents() {{ return new[] {{ \"{0}\" }}; }}", type));
			
			List<XmlElement> children = GetChildren(xmlFile, type);
			List<XmlElement> filters = GetNodeFilters(xmlFile, type);
			List<XmlElement> smoProperties = GetNodeSmoProperties(xmlFile, type);

			if (filters.Count > 0)
				{
					WriteLine("");
					WriteLine("        public override IEnumerable<NodeFilter> Filters");
					WriteLine("        {");
					WriteLine("           get");
					WriteLine("           {");

					WriteLine("                var filters = new List<NodeFilter>();");
					foreach (var filter in filters) 
					{
						var propertyName = filter.GetAttribute("Property");
						var propertyType = filter.GetAttribute("Type");
						var propertyValue = filter.GetAttribute("Value");
						var validFor = filter.GetAttribute("ValidFor");
						var typeToReverse = filter.GetAttribute("TypeToReverse");

						List<XmlElement> filterValues = GetNodeFilterValues(xmlFile, type, propertyName);

						
						WriteLine("                filters.Add(new NodeFilter");
						WriteLine("                {");
						WriteLine(string.Format("                   Property = \"{0}\",", propertyName));
						WriteLine(string.Format("                   Type = typeof({0}),", propertyType));
						if (!string.IsNullOrWhiteSpace(typeToReverse))
						{
							WriteLine(string.Format("                   TypeToReverse = typeof({0}Querier),", typeToReverse));
						}
						if (!string.IsNullOrWhiteSpace(validFor))
						{
							WriteLine(string.Format("                   ValidFor = {0},", GetValidForFlags(validFor)));
						}
						if (propertyValue != null && (filterValues == null || filterValues.Count == 0)) 
						{
							WriteLine(string.Format("                   Values = new List<object> {{ {0} }},", propertyValue));
						}
						if (filterValues != null && filterValues.Count > 0) 
						{
							string filterValueType = "object";
							if (propertyType == "Enum") 
							{
									
							}
							WriteLine(string.Format("                   Values = new List<object>"));
							WriteLine(string.Format("                   {{"));
							for(int i = 0; i < filterValues.Count; i++) 
							{
								string separator = "";
								if (i != filterValues.Count - 1)
								{
									separator = ",";
								}
								var filterValue = filterValues[i];
								WriteLine(string.Format("                      {{ {0} }}{1}", filterValue.InnerText, separator ));
										
							}
							WriteLine(string.Format("                   }}"));
						}
						WriteLine("                });");

						
					}
					
					WriteLine("                return filters;");
					WriteLine("           }");
					WriteLine("        }");
					
				}


			if (smoProperties.Count > 0)
				{
					WriteLine("");
					WriteLine("        public override IEnumerable<NodeSmoProperty> SmoProperties");
					WriteLine("        {");
					WriteLine("           get");
					WriteLine("           {");

					WriteLine("                var properties = new List<NodeSmoProperty>();");
					foreach (var smoPropertiy in smoProperties) 
					{
						var propertyName = smoPropertiy.GetAttribute("Name");
						var validFor = smoPropertiy.GetAttribute("ValidFor");

						

						
						WriteLine("                properties.Add(new NodeSmoProperty");
						WriteLine("                {");
						WriteLine(string.Format("                   Name = \"{0}\",", propertyName));
						
						if (!string.IsNullOrWhiteSpace(validFor))
						{
							WriteLine(string.Format("                   ValidFor = {0}", GetValidForFlags(validFor)));
						}
						WriteLine("                });");
						
						
					}
					
					WriteLine("                return properties;");
					WriteLine("           }");
					WriteLine("        }");
					
				}

			
			if (children.Count > 0)
			{
				WriteLine("");
				WriteLine("        protected override void OnExpandPopulateFolders(IList<TreeNode> currentChildren, TreeNode parent)");
				WriteLine("        {");
				foreach (var child in children)
				{
					XmlElement childAsXmlElement = GetNodeElement(xmlFile, child.GetAttribute("Name"));
					if (childAsXmlElement == null)
					{
						// TODO SHould we error with clear message that this needs to be fixed?
						continue;
					}
					string childImage = childAsXmlElement.GetAttribute("Image");
					var msShippedOwned = childAsXmlElement.GetAttributeNode("IsMsShippedOwned");
					var validFor = childAsXmlElement.GetAttribute("ValidFor");

					if (TreeNodeExists(xmlFile, child.GetAttribute("Name") + "TreeNode"))
					{
						WriteLine(string.Format("            currentChildren.Add(new {0}TreeNode {{ SortPriority = SmoTreeNode.NextSortPriority }} );", child.GetAttribute("Name")));
					}
					else
					{
						WriteLine("            currentChildren.Add(new FolderNode {");
						WriteLine(string.Format("                NodeValue = {0},", childAsXmlElement.GetAttribute("LocLabel")));
						WriteLine(string.Format("                NodeType = \"{0}\",", "Folder"));
						WriteLine(string.Format("                NodeTypeId = NodeTypes.{0},", child.GetAttribute("Name")));
						WriteLine(string.Format("                IsSystemObject = {0},", child.GetAttribute("IsSystemObject") == "1" ? "true" : "false"));
						
						if (msShippedOwned != null)
						{
							WriteLine("                IsMsShippedOwned = true,");
						}
						if (!string.IsNullOrWhiteSpace(validFor))
						{
							WriteLine(string.Format("                ValidFor = {0},", GetValidForFlags(validFor)));
						}
						WriteLine("                SortPriority = SmoTreeNode.NextSortPriority,");
						WriteLine("            });");
					}
				}
				WriteLine("        }");
			}

			if (!string.IsNullOrWhiteSpace(strategy))
			{
				string[] allTypes = ChildQuerierTypes.Split(new [] { ';' }, StringSplitOptions.RemoveEmptyEntries);
				WriteLine("");
				WriteLine("        internal override Type[] ChildQuerierTypes");
				WriteLine("        {");
				WriteLine("           get");
				WriteLine("           {");
				if (!string.IsNullOrWhiteSpace(ChildQuerierTypes))
				{
					Write("              return new [] {");
					foreach (var typeToRe in allTypes)
					{
					   Write(string.Format(" typeof({0}Querier),", typeToRe));
					}
					WriteLine(" };");
				}
				else
				{
					Write("              return new Type[0];");
				}
				WriteLine("           }");
				WriteLine("        }");

				WriteLine("");
				
				WriteLine("        public override TreeNode CreateChild(TreeNode parent, object context)");
				WriteLine("        {");
				if (string.IsNullOrWhiteSpace(TreeNode))
				{
					WriteLine("            var child = new SmoTreeNode();");
					WriteLine("            child.IsAlwaysLeaf = true;");
					
					if (!string.IsNullOrEmpty(nodeType)) 
					{
						
						WriteLine(string.Format("            child.NodeType = \"{0}\";", nodeType));
					}
						
				}
				else
				{
					var modelNodeChildren = GetNodeElement(xmlFile, TreeNode.Replace("TreeNode",string.Empty));
					WriteLine(string.Format("            var child = new {0}();", TreeNode));
					if (modelNodeChildren.ChildNodes.Count == 0)
					{
						WriteLine("            child.IsAlwaysLeaf = true;");
					}
				}
				if (disableSort != null)
				{
					WriteLine("            child.SortPriority = SmoTreeNode.NextSortPriority;");
				}
				WriteLine("            InitializeChild(parent, child, context);");
				

				WriteLine("            return child;");
				WriteLine("        }");
			}
			else if (baseClass == "ModelBased")
			{
				WriteLine("");
				WriteLine("        internal override Type[] ChildQuerierTypes { get {return null;} }");
				WriteLine("");
				// TODO Is reverse engineering strategy every needed?
				// WriteLine("        protected override ReverseEngineeringStrategy Strategy { get {return ReverseEngineeringStrategy.None;} }");
				WriteLine("");
				WriteLine("        public override TreeNode CreateChild(TreeNode parent, object context)");
				WriteLine("        {");
				WriteLine("            return null;");
				WriteLine("        }");
			}
			
			WriteLine("    }");
			WriteLine("");
		}
	}
#>
}

<#+
	public static string GetValidForFlags(string validForStr)
	{
		List<string> flags = new List<string>();
		if (validForStr.Contains("Sql2005"))
		{
			flags.Add("ValidForFlag.Sql2005");
		}

		if (validForStr.Contains("Sql2008"))
		{
			flags.Add("ValidForFlag.Sql2008");
		}

		if (validForStr.Contains("Sql2012"))
		{
			flags.Add("ValidForFlag.Sql2012");
		}

		if (validForStr.Contains("Sql2014"))
		{
			flags.Add("ValidForFlag.Sql2014");
		}

		if (validForStr.Contains("Sql2016"))
		{
			flags.Add("ValidForFlag.Sql2016");
		}

		if (validForStr.Contains("Sql2017"))
		{
			flags.Add("ValidForFlag.Sql2017");
		}

		if (validForStr.Contains("AzureV12"))
		{
			flags.Add("ValidForFlag.AzureV12");
		}

		if (validForStr.Contains("AllOnPrem"))
		{
			flags.Add("ValidForFlag.AllOnPrem");
		}
		if (validForStr.Contains("AllAzure"))
		{
			flags.Add("ValidForFlag.AllAzure");
		}
		if (validForStr.Contains("NotSqlDw"))
		{
			flags.Add("ValidForFlag.NotSqlDw");
		}
		if (validForStr.Contains("SqlOnDemand"))
		{
			flags.Add("ValidForFlag.SqlOnDemand");
		}
		if (validForStr == "NotSqlDemand")
		{
			flags.Add("ValidForFlag.NotSqlDemand");
		}
		if (validForStr == "All")
		{
			flags.Add("ValidForFlag.All");
		}

		return string.Join("|", flags);
	}
	public static string[] GetNodes(string xmlFile)
	{
		List<string> typesList = new List<string>();
		XmlDocument doc = new XmlDocument();
		doc.Load(xmlFile);
		XmlNodeList treeTypes = doc.SelectNodes("/ServerExplorerTree/Node");
		if (treeTypes != null)
		{
			foreach (var type in treeTypes)
			{
				XmlElement element = type as XmlElement;
				if (element != null)
				{
					typesList.Add(element.GetAttribute("Name"));
				}
			}
		}
		return typesList.ToArray();
	}
	
	public static Dictionary<string, List<string>> GetReverseDependencies(string xmlFile)
	{
		Dictionary<string, List<string>> dependencyMap = new Dictionary<string, List<string>>();
		XmlDocument doc = new XmlDocument();
		doc.Load(xmlFile);
		XmlNodeList treeTypes = doc.SelectNodes("/ServerExplorerTree/ReverseDependencyList/ReverseDependency");
		if (treeTypes != null)
		{
			foreach (var type in treeTypes)
			{
				XmlElement element = type as XmlElement;
				if (element != null)
				{
					string typeName = element.GetAttribute("Type");
					string dependency = element.GetAttribute("DependsOn");
					List<string> dependenciesForType;
					if (dependencyMap.TryGetValue(typeName, out dependenciesForType))
					{
						dependenciesForType.Add(dependency);
					}
					else
					{
						string[] allDepedencies = dependency.Split(new [] { ';' }, StringSplitOptions.RemoveEmptyEntries);
						dependenciesForType = new List<string>();
						dependenciesForType.AddRange(allDepedencies);
						dependencyMap.Add(typeName, dependenciesForType);
					}
				}
			}
		}
		return dependencyMap;
	}

	public static XmlElement GetNodeElement(string xmlFile, string nodeName)
	{
		XmlDocument doc = new XmlDocument();
		doc.Load(xmlFile);
		return (XmlElement)doc.SelectSingleNode(string.Format("/ServerExplorerTree/Node[@Name='{0}']", nodeName));
	}

	public static bool TreeNodeExists(string xmlFile, string TreeNode)
	{
		XmlDocument doc = new XmlDocument();
		doc.Load(xmlFile);
		var found = (XmlElement)doc.SelectSingleNode(string.Format("/ServerExplorerTree/CodeGenOptions/UniqueTreeNode[@Name='{0}']", TreeNode));

		return (found != null);
	}

	public static List<XmlElement> GetUniqueTreeNodes(string xmlFile)
	{
		XmlDocument doc = new XmlDocument();
		doc.Load(xmlFile);

		List<XmlElement> retElements = new List<XmlElement>();
		XmlNodeList nodeList = doc.SelectNodes("/ServerExplorerTree/CodeGenOptions/UniqueTreeNode");
		foreach (var item in nodeList)
		{
			XmlElement itemAsElement = item as XmlElement;
			if (itemAsElement != null)
			{
				retElements.Add(itemAsElement);
			}
		}
		return retElements;
	}

	public static List<XmlElement> GetChildren(string xmlFile, string parentName)
	{
		XmlElement nodeElement = GetNodeElement(xmlFile, parentName);
		XmlDocument doc = new XmlDocument();
		doc.Load(xmlFile);

		List<XmlElement> retElements = new List<XmlElement>();
		XmlNodeList nodeList = doc.SelectNodes(string.Format("/ServerExplorerTree/Node[@Name='{0}']/Child", parentName));
		foreach (var item in nodeList)
		{
			XmlElement itemAsElement = item as XmlElement;
			if (itemAsElement != null)
			{
			  retElements.Add(itemAsElement);
			}
		}
		return retElements;
	}

	public static List<XmlElement> GetNodeFilters(string xmlFile, string parentName)
	{
		XmlElement nodeElement = GetNodeElement(xmlFile, parentName);
		XmlDocument doc = new XmlDocument();
		doc.Load(xmlFile);

		List<XmlElement> retElements = new List<XmlElement>();
		XmlNodeList nodeList = doc.SelectNodes(string.Format("/ServerExplorerTree/Node[@Name='{0}']/Filters/Filter", parentName));
		foreach (var item in nodeList)
		{
			XmlElement itemAsElement = item as XmlElement;
			if (itemAsElement != null)
			{
				retElements.Add(itemAsElement);
			}
		}
		return retElements;
	}
	

	public static List<XmlElement> GetNodeSmoProperties(string xmlFile, string parentName)
	{
		XmlElement nodeElement = GetNodeElement(xmlFile, parentName);
		XmlDocument doc = new XmlDocument();
		doc.Load(xmlFile);

		List<XmlElement> retElements = new List<XmlElement>();
		XmlNodeList nodeList = doc.SelectNodes(string.Format("/ServerExplorerTree/Node[@Name='{0}']/Properties/Property", parentName));
		foreach (var item in nodeList)
		{
			XmlElement itemAsElement = item as XmlElement;
			if (itemAsElement != null)
			{
			  retElements.Add(itemAsElement);
			}
		}
		return retElements;
	}

	public static List<XmlElement> GetNodeFilterValues(string xmlFile, string parentName, string filterProperty)
	{
		XmlElement nodeElement = GetNodeElement(xmlFile, parentName);
		XmlDocument doc = new XmlDocument();
		doc.Load(xmlFile);

		List<XmlElement> retElements = new List<XmlElement>();
		XmlNodeList nodeList = doc.SelectNodes(string.Format("/ServerExplorerTree/Node[@Name='{0}']/Filters/Filter[@Property='{1}']/Value", parentName, filterProperty));
		foreach (var item in nodeList)
		{
			XmlElement itemAsElement = item as XmlElement;
			if (itemAsElement != null)
			{
				retElements.Add(itemAsElement);
			}
		}
		return retElements;
	}
#>