<#@ 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" #>
// This file was generated by a T4 Template. Do not modify directly, instead update the SmoQueryModelDefinition.xml file
// and re-run the T4 template. This can be done in Visual Studio by right-click in and choosing "Run Custom Tool",
// or from the command-line on any platform by running "build.cmd -Target=CodeGen" or "build.sh -Target=CodeGen".

using System;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlServer.Management.Smo.Broker;
using Index = Microsoft.SqlServer.Management.Smo.Index;

namespace Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel
{
<#
	var directory = Path.GetDirectoryName(Host.TemplateFile);
	string xmlFile = Path.Combine(directory, "SmoQueryModelDefinition.xml");
	
	/////////
	// Now generate all the Query methods
	/////////
	var allNodes = GetNodes(xmlFile);
	var indent = "    ";
	foreach (var nodeName in allNodes)
	{
		XmlElement nodeElement = GetNodeElement(xmlFile, nodeName);
		IList<string> parents = GetParents(nodeElement, xmlFile, nodeName);
		string nodeType = GetNodeType(nodeElement, nodeName);
		var validFor = nodeElement.GetAttribute("ValidFor");

		string queryBaseClass = "SmoQuerier";
		PushIndent(indent);
		WriteLine("");
		WriteLine(string.Format("[Export(typeof({0}))]", queryBaseClass));
		WriteLine(string.Format("internal partial class {0}Querier: {1}", nodeName, queryBaseClass));
		WriteLine("{");
		PushIndent(indent);

		// Supported Types
		WriteLine("Type[] supportedTypes = new Type[] { typeof("+ nodeType + ") };");
		if (!string.IsNullOrWhiteSpace(validFor))
		{
			WriteLine("");
			WriteLine(string.Format("public override ValidForFlag ValidFor {{ get {{ return {0}; }} }}", GetValidForFlags(validFor)));
			WriteLine("");
		}

		WriteLine("");
		WriteLine("public override Type[] SupportedObjectTypes { get { return supportedTypes; } }");
		WriteLine("");

		// Query impl
		WriteLine("public override  IEnumerable<SqlSmoObject> Query(SmoQueryContext context, string filter, bool refresh, IEnumerable<string> extraProperties)");
		WriteLine("{");
		PushIndent(indent);

		// TODO Allow override of the navigation path
		foreach(var parentType in parents)
		{
			string parentVar = string.Format("parent{0}", parentType);
			WriteLine(string.Format("{0} {1} = context.Parent as {0};", parentType, parentVar));
			WriteLine(string.Format("if ({0} != null)", parentVar));
			WriteLine("{");
			PushIndent(indent);
			
			XmlElement navPathElement = GetNavPathElement(xmlFile, nodeName, parentType);
			string navigationPath = GetNavigationPath(nodeElement, nodeName, navPathElement);
			string subField = GetNavPathAttribute(navPathElement, "SubField");
			string fieldType = GetNavPathAttribute(navPathElement, "FieldType");
			

			WriteLine(string.Format("var retValue = {0}.{1};", parentVar, navigationPath));
			WriteLine("if (retValue != null)");
			WriteLine("{");
			PushIndent(indent);
			
			
			if (IsCollection(nodeElement))
			{
				WriteLine(string.Format("retValue.ClearAndInitialize(filter, extraProperties);"));
				if (string.IsNullOrEmpty(subField) )
				{
					WriteLine(string.Format("return new SmoCollectionWrapper<{0}>(retValue).Where(c => PassesFinalFilters({1}, c));", nodeType, parentVar));
				}
				else 
				{
					WriteLine(string.Format("List<{0}> subFieldResult = new List<{0}>();", nodeType));
					WriteLine(string.Format("foreach({0} field in retValue)", fieldType));
					WriteLine("{");
					PushIndent(indent);
					WriteLine(string.Format("{0} subField = field.{1};", nodeType, subField));
					WriteLine(string.Format("if (subField != null)"));
					WriteLine("{");
					PushIndent(indent);
					WriteLine(string.Format("subFieldResult.Add(subField);"));
					PopIndent();
					WriteLine("}");
					PopIndent();
					WriteLine("}");
					WriteLine(string.Format("return subFieldResult.Where(c => PassesFinalFilters({1}, c));", nodeType, parentVar));
				}
			}
			else
			{
				WriteLine("if (refresh)");
				WriteLine("{");
				PushIndent(indent);
				WriteLine(string.Format("{0}.{1}.Refresh();", parentVar, navigationPath));
				PopIndent();
				WriteLine("}");
				WriteLine("return new SqlSmoObject[] { retValue };");
			}
			
			PopIndent();
			WriteLine("}");
			PopIndent();
			WriteLine("}");	// close If
		}

		WriteLine("return Enumerable.Empty<SqlSmoObject>();");

		PopIndent();
		WriteLine("}");	// close Query method
		PopIndent();
		WriteLine("}"); // close Class
		PopIndent();
	}
#>
}

<#+

	public static string[] GetNodes(string xmlFile)
	{
		List<string> typesList = new List<string>();
		XmlDocument doc = new XmlDocument();
		doc.Load(xmlFile);
		XmlNodeList treeTypes = doc.SelectNodes("/SmoQueryModel/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 XmlElement GetNodeElement(string xmlFile, string nodeName)
	{
		XmlDocument doc = new XmlDocument();
		doc.Load(xmlFile);
		return (XmlElement)doc.SelectSingleNode(string.Format("/SmoQueryModel/Node[@Name='{0}']", nodeName));
	}

	public static XmlElement GetNavPathElement(string xmlFile, string nodeName, string parent)
	{
		XmlDocument doc = new XmlDocument();
		doc.Load(xmlFile);
		XmlElement navPathElement = (XmlElement)doc.SelectSingleNode(string.Format("/SmoQueryModel/Node[@Name='{0}']/NavigationPath[@Parent='{1}']", nodeName, parent));
		
		return navPathElement;
	}

	public static string GetNavPathAttribute(XmlElement navPathElement, string attributeName)
	{
		return navPathElement == null ? null : navPathElement.GetAttribute(attributeName);
	}

	public static string GetNavigationPath(XmlElement nodeElement, string nodeName, XmlElement navPathElement)
	{
		string navPathField = GetNavPathAttribute(navPathElement, "Field");
		if (!string.IsNullOrEmpty(navPathField))
		{
			return navPathField;
		}
		// else use pluralized type as this is the most common scenario
		string nodeType = GetNodeType(nodeElement, nodeName);

		string nodeTypeAccessor = IsCollection(nodeElement) ? string.Format("{0}s", nodeType) : nodeType;
		return nodeTypeAccessor;
	}
	
	public static string GetNodeType(XmlElement nodeElement, string nodeName)
	{
		var type = nodeElement.GetAttribute("Type");
		if (!string.IsNullOrEmpty(type))
		{
			return type;
		}
		// Otherwise assume the type is the node name without "Sql" at the start
		var prefix = "Sql";
		return nodeName.IndexOf(prefix) == 0 ? nodeName.Substring(prefix.Length) : nodeName;
	}
	
	public static bool IsCollection(XmlElement nodeElement)
	{
		var collection = nodeElement.GetAttribute("Collection");
		bool result;
		if (bool.TryParse(collection, out result))
		{
			return result;
		}
		// Default is true
		return true;
	}

	public static IList<string> GetParents(XmlElement nodeElement, string xmlFile, string parentName)
	{
		var parentAttr = nodeElement.GetAttribute("Parent");
		if (!string.IsNullOrEmpty(parentAttr))
		{
			return new string[] { parentAttr };
		}
		
		var parentNodes = GetChildren(xmlFile, parentName, "Parent");
		if (parentNodes != null && parentNodes.Count > 0)
		{
			List<string> parents = new List<string>();
			foreach(var node in parentNodes)
			{
				parents.Add(node.InnerText);
			}
			return parents;
		}
		
		// default to assuming a type is under Database
		return new string[] { "Database" };
	}

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

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

	 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);
	}

#>