<#@ template language="C#" debug="true" hostspecific="true" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #>
<#@ assembly name="EnvDTE" #>
<#@ assembly name="EnvDTE80" #>
<#@ assembly name="VSLangProj" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="EnvDTE80" #>
<#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Globalization" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<# 
/*
 * T4ResX
 * Author  Robert Hoffmann (itechnology)
 * License MIT / http://bit.ly/mit-license
 * 
 * Version 1.00
 * https://github.com/itechnology/T4ResX
 */
#>
<# 
	/* CONFIGURATION SETTINGS
	 ************************/

	// If you are using this in an assembly where adding a RESX adds a code behind designer.cs file, then be sure to set this to true
	// Otherwise the designer.cs and the T4ResX.cs will contain the same namespaces and compilation will fail
	// Classic ASP.NET websites do not generate designer files in the codebehind
	var removeDesignerFiles = false;

	/* CONFIGURATION SETTINGS
	 ************************/




	/* T4ResX: Find all RESX files
	 * Not alot to see here .. :)
	 * All the important stuff is in T4Helper & T4ResXHelpers
	 * Make this alot easier to maintain and elaborate outside of the .tt environement
	 **********************************************************************************/
	T4Helper = new DteHelper(this.Host);

	var projectItems  = T4Helper.GetAllProjectItems();
	var project       = T4Helper.GetProject(T4Helper.GetCurrentProjectName());
	var projectPath   = System.IO.Path.GetDirectoryName(project.FileName);
	var rootNameSpace = project.Properties.Item("RootNamespace").Value.ToString();

	var items = new List<T4ResXHelpers.ResXItem>();
	projectItems
		.ToList()
		.ForEach(projectItem => {
			var itemPath = T4Helper.GetProjectItemFullPath(projectItem);

			if (itemPath.EndsWith(".resx")) {				
				if (removeDesignerFiles  && !Regex.IsMatch(itemPath, T4ResXHelpers.CultureInvariantRegex, RegexOptions.IgnoreCase)) {
					T4Helper.SetPropertyValue(projectItem, "CustomTool", "");                  
                }

				T4ResXHelpers.AddResX(projectPath, rootNameSpace, itemPath, items);	
			}
	});
 #>
/*
 * T4ResX
 * Author  Robert Hoffmann (itechnology)
 * License MIT / http://bit.ly/mit-license
 *
 * Version 1.00
 * https://github.com/itechnology/T4ResX
 */ 
 using System;
 using System.Linq; 
 using System.Threading;
 using System.Reflection;
 using System.Collections.Generic; 
 using System.Text.RegularExpressions;

 namespace <#= rootNameSpace #> {
 	/// <summary>
	/// Class that contains all our little helper functions
	/// </summary>
	public static class Utilities {
		/// <summary>
		/// A fake attribute that allows us to filter classes by Attribute
		/// It's helpfull when using GetResourcesByNameSpace(), and when T4ResX is tossed into a project containing other classes/properties
		/// Like this we only return stuff generated by T4ResX itself
		/// </summary>
		public class Localized : Attribute {}

		///<summary>
		/// We bind this function to our replacement function when needed
		/// Like this the replacement function can reside in any assembly you like
		/// Bind it once on ApplicationStart, or rebind it to a different replacement function before calling it
		///
		/// Poor Man's IOC
		/// http://www.i-technology.net
		///</summary>
		public static Func<string, string> GetReplacementString = key => key;

		#region Methods
		/// <summary>
		/// Look up ressources from a specific namespace
		/// </summary>
		/// <param name="ns">Namspace to get resources from</param>
		/// <returns>Dictionary&lt;namespace, Dictionary&lt;key, value&gt;&gt;</returns>
		public static Dictionary<string, Dictionary<string, string>> GetResourcesByNameSpace(string ns)
		{
			var result = new Dictionary<string, Dictionary<string, string>>();

			var qs = ns.Split('^');
				ns = qs[0];

			var path  = string.IsNullOrEmpty(ns) ? "<#= rootNameSpace #>" : string.Format("{0}.{1}", "<#= rootNameSpace #>", ns);
			var wCard = path;

			if (ns.EndsWith(".*"))
			{
				wCard = path.Replace(".*", "");
			}
            
			var current = Assembly.GetExecutingAssembly();
			current
				.GetTypes()
                .Where(type => type.GetCustomAttributes(typeof(Localized), false).Length != 0)
                .Where(type => type.Namespace != null && (ns == "" || (ns.EndsWith(".*")
                                                                           ? type.Namespace.StartsWith(wCard, StringComparison.InvariantCultureIgnoreCase)
                                                                           : string.Equals(type.Namespace, path, StringComparison.InvariantCultureIgnoreCase)))
                 )
				.Where(type => qs.Length != 2 || Regex.IsMatch(type.Name, qs[1], RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline))
				.ToList()
				.ForEach(typeFound =>
				{
					var instance = current.CreateInstance(typeFound.FullName);
					if (instance != null)
					{
						var instanceType  = instance.GetType();
						var instanceClass = instanceType.FullName.Replace(wCard, "");
						var propertyList  = new Dictionary<string, string>();

						instanceType
							.GetProperties()
							.Where(t => t.GetCustomAttributes(typeof(Localized), false).Length != 0)
							.ToList()
							.ForEach(property => propertyList.Add(property.Name, property.GetValue(null, null).ToString()));

					    if (propertyList.Count > 0)
					    {
					        result.Add(instanceClass.StartsWith(".") ? instanceClass.Substring(1) : instanceClass, propertyList);
					    }
					}
				});

			return result;
		}
		#endregion
	}
 }
    <# 
	var currentClassName = string.Empty;
	var writeClassHeader = false;

	// We only want culture invariant entries for now
	var resxItems = items.Where(c => string.IsNullOrEmpty(c.Culture));

	foreach (T4ResXHelpers.ResXItem item in resxItems) {	
		// Since we are lazy writing everything below with partial classes, we need to know when we can or cannot add the ResourceManager
		if (string.Equals(currentClassName, item.ClassName))
		{
			writeClassHeader = false;
		}
		else
		{
			writeClassHeader = true;
			currentClassName = item.ClassName;			
        }					
	#>

namespace <#= T4ResXHelpers.Current.NormalizeString(item.NameSpace)#>  {
	<# if (writeClassHeader){ Write("[Utilities.Localized]"); } #>	
	public partial class <#= item.ClassName #> {
	<#
		// If we are entering a new class, we must add some functions to the top of it
		if (writeClassHeader)
		{
			var path =	T4ResXHelpers.Current.NormalizeString(item.NameSpace.Replace(rootNameSpace, "")) + "^" + item.ClassName;
			if (path.StartsWith(".")) {
				path = path.Substring(1);
            }
	#>

		///<summary>
		/// Return this class as a Dictionary&lt;class, Dictionary&lt;key, value&gt;&gt;
		///</summary>
		public static Dictionary<string, Dictionary<string, string>> GetAsDictionary() {
			return Utilities.GetResourcesByNameSpace("<#=path#>");
		}

		private static System.Resources.ResourceManager _resourceManager;    
    
		///<summary>
		/// Get the ResourceManager
		///</summary>
		private static System.Resources.ResourceManager ResourceManager 
		{
			get 
			{
				return _resourceManager ?? (_resourceManager = new System.Resources.ResourceManager("<#=T4ResXHelpers.Current.NormalizeString(item.NameSpace)#>.<#=item.ClassName#>", typeof(<#=item.ClassName#>).Assembly));
			}
		}

		///<summary>
		///	Get localized entry for a given key
		///</summary>
		public static string GetResourceString(string key, params object[] args)
		{
			var value = ResourceManager.GetString(key, Thread.CurrentThread.CurrentUICulture);

			if (!string.IsNullOrEmpty(value))
			{
                var regex  = @"{\b\p{Lu}{3,}\b}";
                var tokens = Regex.Matches(value, regex).Cast<Match>().Select(m => m.Value).ToList();
                    tokens
                        .ForEach(t =>
                        {
                            value = value.Replace(t, Utilities.GetReplacementString(t.Replace("{", "").Replace("}", "")));
                        });

                if (args.Any())
                {
                    regex  = @"{[0-9]{1}}";
                    tokens = Regex.Matches(value, regex).Cast<Match>().Select(m => m.Value).ToList();

                    if (tokens.Any())
                    {
                        // If argument length is less than token length, add an error message
                        // This can happen if arguments are accidentally forgottent in a translation
                        if (args.Count() < tokens.Count())
                        {
                            var newArgs = new List<object>();
                            for (var i = 0; i < tokens.Count(); i++)
                            {
                                newArgs.Add(args.Length > i ? args[i] : "argument {" + i + "} is undefined");
                            }

                            args = newArgs.ToArray();
                        }

                        value = string.Format(value, args);
                    }
                }		        		
			}
	
			return value;
		} 
		<#
		} // END writeClassHeader
		if (T4ResXHelpers.Current.HasTokens(item.Value)) {
		#>

		///<summary>
		///    <list type='bullet'>
		///        <item>
		///            <description><#= item.Value.Replace("\r", "").Replace("\n", " ")#></description>
		///        </item>
		///        <item>
		///            <description><#= item.Comment.Replace("\r", "").Replace("\n", " ")#></description>
		///        </item>
		///    </list>
		///</summary>
		public static string <#= T4ResXHelpers.Current.NormalizeItem(item.Key, false)#>Formatted(params object[] args) { return GetResourceString("<#=T4ResXHelpers.Current.NormalizeItem(item.Key, false)#>", args); }
		<#
		} // END HasTokens(item.Value)
		#>

        ///<summary>
        ///    <list type='bullet'>
        ///        <item>
        ///            <description><#= item.Value.Replace("\r", "").Replace("\n", " ")#></description>
        ///        </item>
        ///        <item>
        ///            <description><#= item.Comment.Replace("\r", "").Replace("\n", " ")#></description>
        ///        </item>
        ///    </list>
		///</summary>
		[Utilities.Localized]
		public static string <#= T4ResXHelpers.Current.NormalizeItem(item.Key, false)#> { get { return GetResourceString("<#=item.Key#>"); } }

		<# 
		if ((T4ResXHelpers.Current.GetType(item.Comment) & T4ResXHelpers.ResxType.Constant) == T4ResXHelpers.ResxType.Constant) {	
		#>

        ///<summary>
        ///    <list type='bullet'>
        ///        <item>
        ///            <description><#= item.Value.Replace("\r", "").Replace("\n", " ")#></description>
        ///        </item>
        ///        <item>
        ///            <description><#= item.Comment.Replace("\r", "").Replace("\n", " ")#></description>
        ///        </item>
        ///        <item>
        ///            <description>
		///					There are places where we cannot use strings as they are considered dynamic
		///					
		///					[RegularExpressionAttribute(User.PseudoRegexConstant, ErrorMessageResourceName = "PseudoError", ErrorMessageResourceType = typeof(User))]
		///
		///					However:
		///					constant = no dynamic content
		///					If you have an idea of how to make constants dynamically localizable, let me know !
		///				</description>
        ///        </item>
        ///    </list>
		///</summary>	
		public const string <#=T4ResXHelpers.Current.NormalizeItem(item.Key, false)#>Constant = "<#=item.Value.Replace("\r", "").Replace("\n", " ")#>";
		<#
		} // END T4ResXHelpers.Current.GetType(item.Comment)
		#>

	}
}
<#
	} // END foreach
#>
<#+
    #region I-Technology.NET T4 Helpers
    /// <summary>
    /// 
    /// INFO: to be included at the bottom of the T4 file
    /// </summary>
    public class T4ResXHelpers
    {
        #region Singleton
        // http://www.yoda.arachsys.com/csharp/singleton.html (Fourth: Simplified)
        /// <summary>
        /// Public instance to Helpers
        /// </summary>
        public static readonly T4ResXHelpers Current = new T4ResXHelpers();

        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static T4ResXHelpers() { }

        T4ResXHelpers()
        {
            // Eventual init code
        }
        #endregion

        /// <summary>
        /// Determine the processing level. Not used yet.
        /// </summary>
        public enum ProcessLevel
        {
            /// <summary>
            /// Process all files from Folder recursive, in which T4ResX.tt resides
            /// </summary>
            Folder,

            /// <summary>
            /// Process all files from Project recursive, in which T4ResX.tt resides
            /// </summary>
            Project
        }

        /// <summary>
        /// Declared type of entry. Not used yet.
        /// </summary>
        [Flags]
        public enum ResxType
        {
            None     = 0,
            Constant = 1
        }

        /// <summary>
        /// Template used for each RESX item discovered
        /// </summary>
        public class ResXItem
        {
            public string NameSpace { get; set; }
            public string ClassName { get; set; }

            public string Key       { get; set; }
            public string Value     { get; set; }
            public string Comment   { get; set; }
            public string Culture   { get; set; }
        }

        /// <summary>
        /// Match files without culture extension
        /// </summary>
        public const string CultureInvariantRegex = @".*\.[a-z]{2}(-[a-z]{2})?\.resx$";

        /// <summary>
        /// Finds tokens in the form of {NAME}
        /// </summary>
        public const string NamedTokenRegex = @"{\b\p{Lu}{3,}\b}";

        /// <summary>
        /// Finds tokens in the form of {0}
        /// </summary>
        public const string ParamTokenRegex = @"{[0-9]{1}}";

        /// <summary>
        /// Finds tokens in the form of {NAME} & {0}
        /// </summary>
        public const string AnyTokenRegex = @"{[0-9]{1}}|{\b\p{Lu}{3,}\b}";

        /// <summary>
        /// Get the declared type of an item
        /// INFO: Currently only constants works. This is open for future ideas.
        /// </summary>
        public ResxType GetType(string value)
        {
            var result = ResxType.None;

            if (Regex.IsMatch(value, @"\[type:constant\]"))
            {
                result |= ResxType.Constant;
            }

            return result;
        }

        /// <summary>
        /// See if the content contains any tokens
        /// </summary>
        public bool HasTokens(string content)
        {
            return Regex.IsMatch(content, AnyTokenRegex);
        }

        ///<summary>
        /// Reformat a string to various forms
        ///</summary>
        public string NormalizeString(string s, bool isClass = true, bool camelCase = true)
        {

            return s.Split('.')
                    .Aggregate((c, n) => NormalizeItem(c, isClass, camelCase) + (isClass ? "." : "_") + NormalizeItem(n, isClass, camelCase));
        }

        /// <summary>
        /// Same as above but single item
        /// </summary>
        public string NormalizeItem(string s, bool isClass = true, bool camelCase = true)
        {
            s = s.Replace(isClass ? "." : "_", "#");

            var r = @"[^\p{L}0-9#]";
            var m = Regex.Matches(s, r);

            foreach (Match match in m)
            {
                if (camelCase)
                {
                    var chars              = s.ToCharArray();
                    chars[match.Index + 1] = char.ToUpper(s[match.Index + 1]);
                    s                      = new string(chars);
                }

                s = s.Remove(match.Index, 1);
                s = s.Insert(match.Index, "_");
            }

            if (Regex.IsMatch(s, @"^[0-9]"))
            {
                s = s.Insert(0, "_");
            }

            return s.Replace("#", isClass ? "." : "_");
        }

        #region ResXLoading
        public static void AddResX(string projectPath, string rootNameSpace, string itemPath, List<ResXItem> items)
        {            
            var culture = CultureInfo.InvariantCulture;
            var file    = System.IO.Path.GetFileNameWithoutExtension(itemPath);
            try
            {
                if (file != null)
                {
                    culture = CultureInfo.CreateSpecificCulture(file.Split('.').Last());
                }
            }
            catch
            {
                culture = CultureInfo.InvariantCulture;
            }

            var xml = new XmlDocument();
            xml.Load(itemPath);

            if (xml.DocumentElement != null)
            {
                var nodes = xml.DocumentElement.SelectNodes("//data");
                if (nodes != null)
                {
                    foreach (XmlElement element in nodes)
                    {
						var className = System.IO.Path.GetFileNameWithoutExtension(itemPath);
						var nameSpace = rootNameSpace + itemPath.Replace(projectPath, "").Replace("\\", ".").Replace("." + className + ".resx", "");

                        var entry = new ResXItem
                                        {
											ClassName = className,
											NameSpace = nameSpace,

                                            Key       = string.Empty,
                                            Value     = string.Empty,
                                            Comment   = string.Empty,
                                            Culture   = culture.Name
                                        };

                        var elementKey = element.Attributes.GetNamedItem("name");
                        if (elementKey != null)
                        {
                            entry.Key = elementKey.Value ?? string.Empty;
                        }

                        var elementValue = element.SelectSingleNode("value");
                        if (elementValue != null)
                        {
                            entry.Value = elementValue.InnerText;
                        }

                        var elementComment = element.SelectSingleNode("comment");
                        if (elementComment != null)
                        {
                            entry.Comment = elementComment.InnerText;
                        }

                        items.Add(entry);
                    }
                }
            }
        }
        #endregion      
    }
    #endregion

    #region TangibleT4 Helpers
    public DteHelper T4Helper;   

    /// <summary>
    /// Collection of Visual Studio Automation-Helper methods.
    /// </summary>
    /// <returns></returns>
    public class DteHelper 
    {
	    public DteHelper(object host)
	    {
		    Host = host as ITextTemplatingEngineHost;
	    }
	
	    public EnvDTE.DTE Dte
	    {
		    get
		    {
			    return GetHostServiceProvider();
		    }
	    }
	
	    private readonly ITextTemplatingEngineHost Host;
	
	    /// Functions requires hostspecific true
	    /// <summary>
	    /// Gets the solution name of the project.
	    /// </summary>
	    public string GetSolutionName()
	    {
            return Path.GetFileNameWithoutExtension(Dte.Solution.FullName);
	    }
	
	    public EnvDTE.Project GetProject(string projectName)
	    {
		    return GetAllProjects().First(p => p.Name == projectName);
	    }
	
	    /// <summary>
	    /// Get all projects of the solution.
	    /// Works not with nested solutions folders.
	    /// </summary>
	    /// <returns></returns>
	    public IEnumerable<EnvDTE.Project> GetAllProjects()
	    {
		    var projectList = new List<EnvDTE.Project>();
		
		    var folders = Dte.Solution.Projects.Cast<EnvDTE.Project>().Where(p => p.Kind == EnvDTE80.ProjectKinds.vsProjectKindSolutionFolder);

		    foreach (EnvDTE.Project folder in folders)
		    {
			    if (folder.ProjectItems == null) continue;
			
			    foreach (EnvDTE.ProjectItem item in folder.ProjectItems)
			    {
				    if (item.Object is EnvDTE.Project)
				    {
					    projectList.Add(item.Object as EnvDTE.Project);
				    }
			    }
		    }
		
		    var projects = Dte.Solution.Projects.Cast<EnvDTE.Project>().Where(p => p.Kind != EnvDTE80.ProjectKinds.vsProjectKindSolutionFolder);
		    if (projects.Any())
		    {
		        projectList.AddRange(projects);
		    }
		
		    return projectList;
	    }

	    public IEnumerable<EnvDTE.ProjectItem> GetAllSolutionItems()
	    {
		    var itemList = new List<EnvDTE.ProjectItem>();
		    foreach (EnvDTE.Project item in GetAllProjects())
		    {
			    if (item == null || item.ProjectItems == null)
			    {
			        continue;
			    }
			
			    itemList.AddRange(GetAllProjectItemsRecursive(item.ProjectItems));	 
		    }
		
		    return itemList;
	    }
	
	    public IEnumerable<EnvDTE.ProjectItem> GetAllProjectItems()
	    {
            return GetAllProjectItemsRecursive(GetTemplateAsProjectItem(Dte).ContainingProject.ProjectItems);
	    }
	
        public IEnumerable<EnvDTE.ProjectItem> GetAllProjectItemsRecursive(EnvDTE.ProjectItems projectItems) 
	    {
    	    foreach (EnvDTE.ProjectItem projectItem in projectItems) 
		    {
			    if (projectItem.ProjectItems == null)
			    {
			        continue;
			    }

        	    foreach (EnvDTE.ProjectItem subItem in GetAllProjectItemsRecursive(projectItem.ProjectItems))
        	    {
            	    yield return subItem;
        	    }
			
        	    yield return projectItem;
    	    }
	    }
	
	    public string GetProjectItemFullPath(EnvDTE.ProjectItem item)
	    {
		    return item.Properties.Item("FullPath").Value.ToString();
	    }
	
	    /// <summary>
	    /// Gets the project name of the active template file.
	    /// </summary>
	    public string GetCurrentProjectName()
	    {
            return Dte.ActiveDocument.ProjectItem.ContainingProject.Name;
	    }

	    /// <summary>
	    /// Sets the custom tool for generated item.
	    /// <param name="generatedFilename">Filename of the generated item</param>
	    /// <param name="customToolName">The name of the custom tool.</param>
	    /// <example>
	    /// 	SetCustomToolForGeneratedItem("Resource.resx", "ResXFileCodeGenerator");
	    /// </example>
	    /// </summary>
	    public void SetCustomToolForGeneratedItem(string generatedFilename, string customToolName)
	    {
            EnvDTE.ProjectItem subItem = GetTemplateAsProjectItem(Dte).ProjectItems.Cast<EnvDTE.ProjectItem>().First(p => p.Name == generatedFilename);
		
		    SetPropertyValue(subItem, "CustomTool", customToolName);
	    }

	    /// <summary>
	    /// Sets the custom tool for the first generated item.
	    /// <param name="customToolName">The name of the custom tool.</param>
	    /// <example>
	    /// 	SetCustomToolForFirstGeneratedItem("ResXFileCodeGenerator");
	    /// </example>
	    /// </summary>
	    public void SetCustomToolForFirstGeneratedItem(string customToolName)
	    {
            EnvDTE.ProjectItem firstSubItem = GetTemplateAsProjectItem(Dte).ProjectItems.Cast<EnvDTE.ProjectItem>().First();

		    SetPropertyValue(firstSubItem, "CustomTool", customToolName);
	    }

	    /// <summary>
	    /// Sets a property value for the project item.
	    /// </summary>
	    public void SetPropertyValue(EnvDTE.ProjectItem item, string propertyName, object value)
	    {
		    EnvDTE.Property property = item.Properties.Item(propertyName);

		    if (property == null)
		    {
			    throw new ArgumentException(String.Format("The property {0} was not found.", propertyName));
		    }

			property.Value = value;
	    }

	    /// <summary>
	    /// Gets the T4 template as vs projectitem.
	    /// </summary>
	    public EnvDTE.ProjectItem GetTemplateAsProjectItem(EnvDTE.DTE dte)
	    {
		    return dte.Solution.FindProjectItem(Host.TemplateFile);
	    }

	    /// <summary>
	    /// Adds a missing file to the t4 vs projectitem.
	    /// </summary>
	    public void AddMissingFileToProject(EnvDTE.ProjectItem pItem, string fileName)
	    {
		    var isMissing = !(from itm in pItem.ProjectItems.Cast<EnvDTE.ProjectItem>()
		                      where itm.Name == fileName
		                      select itm).Any();

		    if (isMissing)
		    {
		        pItem.ProjectItems.AddFromFile(GetPath(fileName));
		    }	
	    }

	    /// <summary>
	    /// Gets the vs automation object EnvDTE.DTE.
	    /// </summary>
	    public EnvDTE.DTE GetHostServiceProvider()
	    {
		    var hostServiceProvider = Host as IServiceProvider;
		    EnvDTE.DTE dte = null;
		    if (hostServiceProvider != null)
		    {
			    dte = hostServiceProvider.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
		    }

		    return dte;
	    }
	
	    /// <summary>
	    /// Gets the full path of the file.
	    /// </summary>
	    public string GetPath(string fileName)
	    {
	        string templateDirectory = Path.GetDirectoryName(Host.TemplateFile);
	        if (templateDirectory != null)
	        {
	            return Path.Combine(templateDirectory, fileName);
	        }

	        return null;
	    }
    }
    #endregion
#>