<#
/*
T4MVC Version 3.9.1
Find latest version and documentation at http://mvccontrib.codeplex.com/wikipage?title=T4MVC
Discuss on StackOverflow or on Codeplex (https://t4mvc.codeplex.com/discussions)

T4MVC is part of the MvcContrib project, but in a different Codeplex location (http://t4mvc.codeplex.com)
Maintained by David Ebbo, with much feedback from the MVC community (thanks all!)
david.ebbo@microsoft.com
http://twitter.com/davidebbo
http://blog.davidebbo.com/ (previously: http://blogs.msdn.com/davidebb)

Related blog posts: http://blogs.msdn.com/davidebb/archive/tags/T4MVC/default.aspx

Please use in accordance to the MvcContrib license (http://mvccontrib.codeplex.com/license)
*/
#>
<#@ template language="C#" debug="true" hostspecific="true" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="Microsoft.VisualStudio.Shell.Interop" #>
<#@ assembly name="EnvDTE" #>
<#@ assembly name="EnvDTE80" #>
<#@ assembly name="VSLangProj" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="EnvDTE80" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<# // To debug, uncomment the next two lines !! 
// System.Diagnostics.Debugger.Launch();
// System.Diagnostics.Debugger.Break();
#>
<#settings=MvcSettings.Load(Host);#>
<#PrepareDataToRender(this); #>
<#var manager = Manager.Create(Host, GenerationEnvironment); #>
<#manager.StartHeader(); #>// <auto-generated />
// This file was generated by a T4 template.
// Don't change it directly as your change would get overwritten.  Instead, make changes
// to the .tt file (i.e. the T4 template) and save it to regenerate this file.

// Make sure the compiler doesn't complain about missing Xml comments
#pragma warning disable 1591
#region T4MVC

using System;
using System.Diagnostics;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Web;
using System.Web.Hosting;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Web.Routing;
using <#=settings.T4MVCNamespace #>;
<#foreach (var referencedNamespace in settings.ReferencedNamespaces) { #>
using <#=referencedNamespace #>;
<#} #>
<#manager.EndBlock(); #>

[<#= GeneratedCode #>, DebuggerNonUserCode]
public static partial class <#=settings.HelpersPrefix #>
{
<#if (settings.IncludeAreasToken) { #>
public static class Areas
{
<#} #>
<#foreach (var area in Areas.Where(a => !string.IsNullOrEmpty(a.Name))) { #>
    static readonly <#=area.Name #>Class s_<#=area.Name #> = new <#=area.Name #>Class();
    public static <#=area.Name #>Class <#=EscapeID(area.Namespace) #> { get { return s_<#=area.Name #>; } }
<#} #>
<#if (settings.IncludeAreasToken) { #>
}
<#} #>
<#foreach (var controller in DefaultArea.GetControllers()) { #>
    public static <#=controller.FullClassName #> <#=controller.Name #> = new <#=controller.FullDerivedClassName #>();
<#} #>
}

namespace <#=settings.T4MVCNamespace #>
{
<#foreach (var area in Areas.Where(a => !string.IsNullOrEmpty(a.Name))) { #>
    [<#= GeneratedCode #>, DebuggerNonUserCode]
    public class <#=area.Name #>Class
    {
        public readonly string Name = "<#=ProcessAreaOrControllerName(area.Name) #>";
<#foreach (var controller in area.GetControllers()) { #>
        public <#=controller.FullClassName #> <#=controller.Name #> = new <#=controller.FullDerivedClassName #>();
<#} #>
    }
<#} #>
}

namespace <#=settings.T4MVCNamespace #>
{
    [<#= GeneratedCode #>, DebuggerNonUserCode]
    public class Dummy
    {
        private Dummy() { }
        public static Dummy Instance = new Dummy();
    }
}

<#foreach (var resultType in ResultTypes.Values) { #>
[<#= GeneratedCode #>, DebuggerNonUserCode]
internal partial class T4MVC_<#=resultType.UniqueName #> : <#=resultType.FullName #>, IT4MVCActionResult
{
    public T4MVC_<#=resultType.UniqueName #>(string area, string controller, string action, string protocol = null): base(<#resultType.Constructor.WriteNonEmptyParameterValues(true); #>)
    {
        this.InitMVCT4Result(area, controller, action, protocol);
    }
    <#foreach (var method in resultType.AbstractMethods) { #> 
    <#=method.IsPublic ? "public" : "protected" #> override <#=method.ReturnType#> <#=method.Name #>(<#method.WriteFormalParameters(true); #>) {<# if(method.ReturnType != "void") {#> return default(<#=method.ReturnType#>); <#} #> }
    <#} #>

    public string Controller { get; set; }
    public string Action { get; set; }
    public string Protocol { get; set; }
    public RouteValueDictionary RouteValueDictionary { get; set; }
}
<#} #>



namespace <#=settings.LinksNamespace #>
{
<#
foreach (string folder in settings.StaticFilesFolders.Concat(GetStaticFilesViewFolders())) {
    ProcessStaticFiles(Project, folder);
}
#>
    [<#= GeneratedCode #>, DebuggerNonUserCode]
    public static partial class Bundles
    {
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public static partial class Scripts {}
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public static partial class Styles {}
    }
}

<#
RenderAdditionalCode();
#>
<#foreach (var controller in GetAbstractControllers().Where(c => !c.HasDefaultConstructor)) { #>
<#manager.StartNewFile(controller.GeneratedFileName); #>
namespace <#=controller.Namespace #>
{
    [<#= GeneratedCode #>, DebuggerNonUserCode]
    public partial class <#=controller.ClassName #>
    {
        protected <#=controller.ClassName #>() { }
    }
}
<#manager.EndBlock(); #>
<#} #>

<#foreach (var controller in GetControllers()) { #>
<#
    // Don't generate the file at all if the existing one is up to date
    // NOTE: disable this optimization since it doesn't catch view changes! It can be re-enabled later if smarter change detection is added
    //if (controller.GeneratedCodeIsUpToDate) {
    //    manager.KeepGeneratedFile(controller.GeneratedFileName);
    //    continue;
    //}
#>
<#manager.StartNewFile(controller.GeneratedFileName); #>
<#if (!String.IsNullOrEmpty(controller.Namespace)) { #>
namespace <#=controller.Namespace #>
{
<#} #>
    public <#if (!controller.NotRealController) { #>partial <#} #>class <#=controller.ClassName #>
    {
<#if (!controller.NotRealController) { #>
<#if (!controller.HasExplicitConstructor) { #>
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public <#=controller.ClassName #>() { }

<#} #>
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        protected <#=controller.ClassName #>(Dummy d) { }

        [<#= GeneratedCode #>, DebuggerNonUserCode]
        protected RedirectToRouteResult RedirectToAction(ActionResult result)
        {
            var callInfo = result.GetT4MVCResult();
            return RedirectToRoute(callInfo.RouteValueDictionary);
        }

        [<#= GeneratedCode #>, DebuggerNonUserCode]
        protected RedirectToRouteResult RedirectToAction(Task<ActionResult> taskResult)
        {
            return RedirectToAction(taskResult.Result);
        }

        [<#= GeneratedCode #>, DebuggerNonUserCode]
        protected RedirectToRouteResult RedirectToActionPermanent(ActionResult result)
        {
            var callInfo = result.GetT4MVCResult();
            return RedirectToRoutePermanent(callInfo.RouteValueDictionary);
        }

        [<#= GeneratedCode #>, DebuggerNonUserCode]
        protected RedirectToRouteResult RedirectToActionPermanent(Task<ActionResult> taskResult)
        {
            return RedirectToActionPermanent(taskResult.Result);
        }

<#foreach (var method in controller.ActionMethodsUniqueWithoutParameterlessOverload) { #>
        [NonAction]
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public virtual <#=method.ReturnTypeFullName #> <#=method.Name #>()
        {
<#if (method.ReturnTypeFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>") { #>
            var callInfo = new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
            return System.Threading.Tasks.Task.FromResult(callInfo as ActionResult);
<#} else { #>
            return new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
<#} #>
        }
<#} #>
<#foreach (var method in controller.CustomActionMethodsUniqueWithoutParameterlessOverload) { #>
        [NonAction]
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public virtual <#=method.ReturnTypeFullName #> <#=method.ActionName #>()
        {
            return new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
        }
<#} #>
<#foreach (var method in controller.CustomActionMethods) { #>
        [NonAction]
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public virtual <#=method.ReturnTypeFullName #> <#=method.ActionName #>(<#method.WriteFormalParameters(true, true); #>)
        {
            var callInfo = new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
<#if (method.Parameters.Count > 0) { #>
<#foreach (var p in method.Parameters) { #>
            ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, <#=p.RouteNameExpression #>, <#=p.Name #>);
<#} #>
<#}#>
<#if (method.ReturnTypeFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>") { #>
            return System.Threading.Tasks.Task.FromResult(callInfo as ActionResult);
<#} else { #>
            return callInfo;
<#} #>
        }
<#} #>

        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public <#=controller.ClassName #> Actions { get { return <#=controller.T4MVCControllerFullName #>; } }
        [<#= GeneratedCode #>]
        public readonly string Area = "<#=ProcessAreaOrControllerName(controller.AreaName) #>";
        [<#= GeneratedCode #>]
        public readonly string Name = "<#=ProcessAreaOrControllerName(controller.Name) #>";
        [<#= GeneratedCode #>]
        public const string NameConst = "<#=ProcessAreaOrControllerName(controller.Name) #>";

        static readonly ActionNamesClass s_actions = new ActionNamesClass();
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public ActionNamesClass ActionNames { get { return s_actions; } }
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public class ActionNamesClass
        {
<#foreach (var method in controller.ActionMethodsWithUniqueNames) { #>
<#  if (settings.UseLowercaseRoutes) { #>
            public readonly string <#=method.ActionName #> = (<#=method.ActionNameValueExpression #>).ToLowerInvariant();
<#  } else { #>
            public readonly string <#=method.ActionName #> = <#=method.ActionNameValueExpression #>;
<#  }
} #>
        }

<#
// Issue: we can't honor UseLowercaseRoutes here because ToLowerInvariant() is not valid in constants!
if (!settings.UseLowercaseRoutes) { #>
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public class ActionNameConstants
        {
<#foreach (var method in controller.ActionMethodsWithUniqueNames) { #>
            public const string <#=method.ActionName #> = <#=method.ActionNameValueExpression #>;
<#
} #>
        }

<#}
} #>

<#if (settings.GenerateParamsForActionMethods && !settings.GenerateParamsAsConstantsForActionMethods){
foreach (var group in controller.UniqueParameterNamesGroupedByActionName) if (group.Any()) { #>
        static readonly ActionParamsClass_<#=group.Key #> s_params_<#=group.Key #> = new ActionParamsClass_<#=group.Key #>();
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public ActionParamsClass_<#=group.Key #> <#=group.Key + settings.ParamsPropertySuffix #> { get { return s_params_<#=group.Key #>; } }
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public class ActionParamsClass_<#=group.Key #>
        {
<#foreach (var param in group) { #>
<#  if (settings.UseLowercaseRoutes) { #>
            public readonly string <#=param.Name #> = (<#=param.RouteNameExpression #>).ToLowerInvariant();
<#  } else { #>
            public readonly string <#=param.Name #> = <#=param.RouteNameExpression #>;
<#  }
} #>
        }
<# } #>
<#} #>
<#if (settings.GenerateParamsAsConstantsForActionMethods){

foreach (var group in controller.UniqueParameterNamesGroupedByActionName) if (group.Any()) { #>
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public class <#=group.Key + settings.ParamsPropertySuffix#>
        {
<#foreach (var param in group) { #>
<#  if (settings.UseLowercaseRoutes) { #>
            public const string <#=param.Name #> = (<#=param.RouteNameExpression #>).ToLowerInvariant();
<#  } else { #>
            public const string <#=param.Name #> = <#=param.RouteNameExpression #>;
<#  }
} #>
        }
<# } #>
<#} #>
        static readonly ViewsClass s_views = new ViewsClass();
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public ViewsClass Views { get { return s_views; } }
        [<#= GeneratedCode #>, DebuggerNonUserCode]
        public class ViewsClass
        {
<#RenderControllerViews(controller);#>
        }
    }

<#if (!controller.NotRealController) { #>
    [<#= GeneratedCode #>, DebuggerNonUserCode]
    public partial class <#=controller.DerivedClassName #> : <#=controller.FullClassName #>
    {
        public <#=controller.DerivedClassName #>() : base(Dummy.Instance) { }

<#foreach (var method in controller.ActionMethods.Where(m => !m.IsCustomReturnType)) { #>
        [NonAction]
        partial void <#=method.Name #>Override(T4MVC_<#=method.ReturnTypeUniqueName #> callInfo<#if (method.Parameters.Count > 0) { #>, <#method.WriteFormalParameters(true); #><#}#>);

        [NonAction]
        public override <#=method.ReturnTypeFullName #> <#=method.Name #>(<#method.WriteFormalParameters(true); #>)
        {
            var callInfo = new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
<#if (method.Parameters.Count > 0) { #>
<#foreach (var p in method.Parameters) { #>
            ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, <#=p.RouteNameExpression #>, <#=p.Name #>);
<#} #>
<#}#>
            <#=method.Name #>Override(callInfo<#if (method.Parameters.Count > 0) { #><#foreach (var p in method.Parameters) { #>, <#=p.Name #><#}}#>);
<#if (method.ReturnTypeFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>") { #>
            return System.Threading.Tasks.Task.FromResult(callInfo as ActionResult);
<#} else { #>
            return callInfo;
<#} #>
        }

<#} #>
    }
<#} #>
<#if (!String.IsNullOrEmpty(controller.Namespace)) { #>
}
<#} #>

<#manager.EndBlock(); #>
<#} #>


<# if (settings.ExplicitHtmlHelpersForPartials) {
        manager.StartNewFile("T4MVC.ExplicitExtensions.cs"); #>

namespace System.Web.Mvc {
    [<#= GeneratedCode #>]
    public static class HtmlHelpersForExplicitPartials
    {
    <#  
        foreach(var partialView in GetPartials()) {
            string partialName = partialView.Key;
            string partialPath = partialView.Value;
            string partialRenderMethod = string.Format(settings.ExplicitHtmlHelpersForPartialsFormat, partialName);
    #> 
        ///<summary>
        ///Render the <b><#= partialName #></b> partial.
        ///</summary>
        public static void <#= partialRenderMethod #>(this HtmlHelper html) {
            html.RenderPartial("<#= partialPath #>");
        }
        
        ///<summary>
        ///Render the <b><#= partialName #></b> partial.
        ///</summary>
        public static void <#= partialRenderMethod #>(this HtmlHelper html, object model) {
            html.RenderPartial("<#= partialPath #>", model);
        }
    <# } #> 
    }
}
<#  manager.EndBlock(); #>
<#  }   #>

<#manager.StartFooter(); #>
#endregion T4MVC
#pragma warning restore 1591
<#manager.EndBlock(); #>
<#settings.SaveChanges(manager); #>
<#manager.Process(settings.SplitIntoMultipleFiles); #>

<#@ Include File="T4MVC.tt.hooks.t4" #>

<#+ 
static MvcSettings settings;
const string ControllerSuffix = "Controller";

static DTE Dte;
static Project Project;
static string AppRoot;
static HashSet<AreaInfo> Areas;
static AreaInfo DefaultArea;
static Dictionary<string, ResultTypeInfo> ResultTypes;
static TextTransformation TT;
static string T4FileName;
static string T4Folder;
static string GeneratedCode = @"GeneratedCode(""T4MVC"", ""2.0"")";
static Microsoft.CSharp.CSharpCodeProvider codeProvider = new Microsoft.CSharp.CSharpCodeProvider();
List<string> virtualPathesForStaticFiles = new List<string>();

IEnumerable<ControllerInfo> GetControllers()
{
    var controllers = new List<ControllerInfo>();

    foreach (var area in Areas)
    {
        controllers.AddRange(area.GetControllers());
    }

    return controllers;
}

IEnumerable<ControllerInfo> GetAbstractControllers()
{
    var controllers = new List<ControllerInfo>();

    foreach (var area in Areas)
    {
        controllers.AddRange(area.GetAbstractControllers());
    }

    return controllers;
}

IDictionary<string, string> GetPartials()
{
    var parts = GetControllers()
        .Select(m => m.ViewsFolder)
        .SelectMany(m => m.Views)
        .Where(m => IsPartialView(m.Value));

    var partsDic = new Dictionary<string, KeyValuePair<string, string>>();

    foreach(var part in parts)
    {
        string viewName = Sanitize(part.Key);
        
        // Check if we already have a partial view by that name (e.g. if two Views folders have the same ascx)
        int keyCollisionCount = partsDic.Where(m => m.Key == viewName || m.Value.Key == viewName).Count();

        if (keyCollisionCount > 0)
        {
            // Append a numbered suffix to avoid the conflict
            partsDic.Add(viewName + keyCollisionCount.ToString(), part);
        }
        else
        {
            partsDic.Add(viewName, part);
        }
    }

    return partsDic.ToDictionary(k => k.Key, v => v.Value.Value);
}

bool IsPartialView(string viewFilePath)
{
    string viewFileName = Path.GetFileName(viewFilePath);

    if (viewFileName.EndsWith(".ascx")) return true;
    
    if ((viewFileName.EndsWith(".cshtml") || viewFileName.EndsWith(".vbhtml")) && viewFileName.StartsWith("_"))
    {
        return true;
    }

    return false;
}

void PrepareDataToRender(TextTransformation tt)
{
    TT = tt;
    T4FileName = Path.GetFileName(Host.TemplateFile);
    T4Folder = Path.GetDirectoryName(Host.TemplateFile);
    Areas = new HashSet<AreaInfo>();
    ResultTypes = new Dictionary<string, ResultTypeInfo>();

    // Get the DTE service from the host
    var serviceProvider = Host as IServiceProvider;
    if (serviceProvider != null)
    {
        Dte = (EnvDTE.DTE)serviceProvider.GetService(typeof(EnvDTE.DTE));
    }

    // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe
    if (Dte == null)
    {
        throw new Exception("T4MVC can only execute through the Visual Studio host");
    }

    Project = GetProjectContainingT4File(Dte);

    if (Project == null)
    {
        Error("Could not find the VS Project containing the T4 file.");
        return;
    }

    // Get the path of the root folder of the app
    AppRoot = Path.GetDirectoryName(Project.FullName) + '\\';

    // Process controllers and views from project root
    ProcessProjectRoot(Project);

    // Process controllers and views from areas.
    ProcessAreas(Project);
}

Project GetProjectContainingT4File(DTE dte)
{

    // Find the .tt file's ProjectItem
    ProjectItem projectItem = dte.Solution.FindProjectItem(Host.TemplateFile);

    // If the .tt file is not opened, open it
    if (projectItem.Document == null)
        projectItem.Open(EnvDTE.Constants.vsViewKindCode);

    return projectItem.ContainingProject;
}

void ProcessProjectRoot(Project project)
{
    var area = new AreaInfo() { Name = null };

    // process controllers and views from their folders
    ProcessAreaControllers(project.ProjectItems, area, null);
    ProcessAreaViews(project.ProjectItems, area, null);

    // does the project root contain a folder which follows the "feature folder" convention?
    // If yes, controllers and views from there are added to the existing default area. 
    // this means, if you use both conventions, you cannot have two controllers with the same name!
    if (!String.IsNullOrEmpty(settings.FeatureFolderRootArea)) 
    {
        ProjectItem featureFolderItem = GetProjectItem(project, settings.FeatureFolderRootArea);
        if (featureFolderItem != null)
        {
            ProcessArea(featureFolderItem, area);
        }
    }

    Areas.Add(area);
    DefaultArea = area;
}

void ProcessAreas(Project project)
{
    // Get the Areas folder
    ProjectItem areaProjectItem = GetProjectItem(project, settings.AreasFolder);
    
    // Process areas folder
    if (areaProjectItem != null)
    {
        foreach (ProjectItem item in areaProjectItem.ProjectItems)
        {
            if (IsFolder(item))
            {
                ProcessArea(item, null);
            }
        }
    }

    // Process portable areas
    foreach (string portableArea in settings.PortableAreas)
    {
        ProjectItem portableAreaProjectItem = GetProjectItem(project, portableArea);

        if (portableAreaProjectItem == null)
            return;

        if (IsFolder(portableAreaProjectItem))
        {
            ProcessArea(portableAreaProjectItem, null);
        }
    }
}

void ProcessArea(ProjectItem areaFolder, AreaInfo existingArea)
{
    var area = existingArea ?? new AreaInfo() { Name = areaFolder.Name };
    area.IsFeatureFolderArea = IsFeatureFolderArea(area.Name, areaFolder);

    ProcessAreaControllers(areaFolder.ProjectItems, area, areaFolder);
    ProcessAreaViews(areaFolder.ProjectItems, area, areaFolder);

    if (existingArea == null)
        Areas.Add(area);
}

void ProcessAreaControllers(ProjectItems areaFolderItems, AreaInfo area, ProjectItem areaFolder)
{
    if (area.IsFeatureFolderArea)
    {
        // Every folder within the area might contain controllers when using "FeatureFolders".
        ProcessControllersRecursive(areaFolder, area);
    }
    else
    {
        // Get area Controllers folder
        ProjectItem controllerProjectItem = GetProjectItem(areaFolderItems, settings.ControllersFolder);
        if (controllerProjectItem == null)
            return;

        ProcessControllersRecursive(controllerProjectItem, area);
    }
}

void ProcessAreaViews(ProjectItems areaFolderItems, AreaInfo area, ProjectItem areaFolder)
{
    ProjectItem viewsProjectItem;

    if (area.IsFeatureFolderArea)
    {
        // For "FeatureFolders", we don't have to search for a "Views"-folder since the area directly contains the feature-folders holding the views.
        viewsProjectItem = areaFolder;
    }
    else
    {
        // Get area Views folder
        viewsProjectItem = GetProjectItem(areaFolderItems, settings.ViewsRootFolder);
        if (viewsProjectItem == null)
            return;
    }

    ProcessAllViews(viewsProjectItem, area);
}

void ProcessControllersRecursive(ProjectItem projectItem, AreaInfo area)
{

    // Recurse into all the sub-items (both files and folder can have some - e.g. .tt files)
    foreach (ProjectItem item in projectItem.ProjectItems)
    {
        ProcessControllersRecursive(item, area);
    }

    if (projectItem.FileCodeModel != null)
    {
        DateTime controllerLastWriteTime = File.GetLastWriteTime(projectItem.get_FileNames(0));
        foreach (var type in projectItem.FileCodeModel.CodeElements.OfType<CodeClass2>())
        {
            ProcessControllerType(type, area, controllerLastWriteTime);
        }
        // Process all the elements that are namespaces
        foreach (var ns in projectItem.FileCodeModel.CodeElements.OfType<CodeNamespace>())
        {
            foreach (var type in ns.Members.OfType<CodeClass2>())
            {
                ProcessControllerType(type, area, controllerLastWriteTime);
            }
        }
    }
}

void ProcessControllerType(CodeClass2 type, AreaInfo area, DateTime controllerLastWriteTime)
{
    // Only process controllers
    if (!IsController(type))
        return;

    // Don't process generic classes (their concrete derived classes will be processed)
    if (type.IsGeneric)
        return;

    //Ignore references to controllers we create
    if(area.Controllers.Any(c => c.DerivedClassName == type.Name))
        return;

    // Make sure the class is partial
    if (type.ClassKind != vsCMClassKind.vsCMClassKindPartialClass)
    {
        try
        {
            type.ClassKind = vsCMClassKind.vsCMClassKindPartialClass;
        }
        catch
        {
            // If we couldn't make it partial, give a warning and skip it
            Warning(String.Format("{0} was not able to make the class {1} partial. Please change it manually if possible", T4FileName, type.Name));
            return;
        }
        Warning(String.Format("{0} changed the class {1} to be partial", T4FileName, type.Name));
    }

    // Collect misc info about the controller class and add it to the collection
    var controllerInfo = new ControllerInfo
    {
        Area = area,
        Namespace = type.Namespace != null ? type.Namespace.Name : String.Empty,
        ClassName = type.Name
    };

    //Filter references to controllers we create
    foreach(var derived in area.Controllers.Where(c => c.ClassName == controllerInfo.DerivedClassName).ToArray())
        area.Controllers.Remove(derived);

    // Check if the controller has changed since the generated file was last created
    DateTime lastGenerationTime = File.GetLastWriteTime(controllerInfo.GeneratedFileFullPath);
    if (lastGenerationTime > controllerLastWriteTime)
    {
        controllerInfo.GeneratedCodeIsUpToDate = true;
    }

    // Either process new ControllerInfo or integrate results into existing object for partially defined controllers
    var target = area.Controllers.Add(controllerInfo) ? controllerInfo : area.Controllers.First(c => c.Equals(controllerInfo));
    target.HasExplicitConstructor |= HasExplicitConstructor(type);
    target.HasExplicitDefaultConstructor |= HasExplicitDefaultConstructor(type);

    if (type.IsAbstract)
    {
        // If it's abstract, set a flag and don't process action methods (derived classes will)
        target.IsAbstract = true;
    }
    else
    {
        // Process all the action methods in the controller
        ProcessControllerActionMethods(target, type);
    }
}

void ProcessControllerActionMethods(ControllerInfo controllerInfo, CodeClass2 current)
{

    bool isAsyncController = IsAsyncController(current);

    // We want to process not just the controller class itself, but also its parents, as they
    // may themselves define actions
    for (CodeClass2 type = current; type != null && type.FullName != "System.Web.Mvc.Controller"; type = (CodeClass2)type.Bases.Item(1))
    {

        // If the type doesn't come from this project, some actions on it will fail. Try to get a real project type if possible.
        if (type.InfoLocation != vsCMInfoLocation.vsCMInfoLocationProject)
        {
            // Go through all the projects in the solution
            for (int i = 1; i <= Dte.Solution.Projects.Count; i++)
            {
                Project prj = null;
                try
                {
                    prj = Dte.Solution.Projects.Item(i);
                }
                catch (System.Runtime.Serialization.SerializationException)
                {
                    // Some project types (that we don't care about) cause a strange exception, so ingore it
                    continue;
                }

                // Skip it if it's the current project or doesn't have a code model
                try
                {
                   if (prj == Project || prj.CodeModel == null)
                      continue;
                }
                catch (System.NotImplementedException)
                {
                   // Installer project does not implement CodeModel property
                   continue;
                }

                try
                {
                    // If we can get a local project type, use it instead of the original
                    var codeType = prj.CodeModel.CodeTypeFromFullName(type.FullName);
                    if (codeType != null && codeType.InfoLocation == vsCMInfoLocation.vsCMInfoLocationProject)
                    {
                        type = (CodeClass2)codeType;
                        break;
                    }
                }
                catch (System.ArgumentException)
                {
                    // CodeTypeFromFullName throws when called on VB projects with a type it doesn't know
                    // (instead of returning null), so ignore those exceptions (See http://t4mvc.codeplex.com/workitem/7)
                }
            }
        }

        foreach (CodeFunction2 method in GetMethods(type))
        {
            // Ignore non-public methods
            if (method.Access != vsCMAccess.vsCMAccessPublic)
                continue;

            // Ignore methods that are marked as not being actions
            if (GetAttribute(method.Attributes, "System.Web.Mvc.NonActionAttribute") != null)
                continue;

            // Ignore methods that are marked as Obsolete
            if (GetAttribute(method.Attributes, "System.ObsoleteAttribute") != null)
                continue;

            // Ignore generic methods
            if (method.IsGeneric)
                continue;

            if(isAsyncController && settings.SupportAsyncActions && (method.Type.TypeKind == vsCMTypeRef.vsCMTypeRefVoid) && method.Name.EndsWith("Async")) 
            {
                //Async methods return void and there could be multiple matching Completed methods, so we will use
                //the generic ActionResult as the return type for the method.
                var resultType = Project.CodeModel.CreateCodeTypeRef("System.Web.Mvc.ActionResult");
                // If we haven't yet seen this return type, keep track of it
                if (!ResultTypes.ContainsKey(resultType.AsFullName))
                {
                    var resTypeInfo = new ResultTypeInfo(resultType);
                    ResultTypes[resultType.AsFullName] = resTypeInfo;
                }

                // Collect misc info about the action method and add it to the collection
                controllerInfo.ActionMethods.Add(new ActionMethodInfo(method, current, resultType));

                continue;
            }

            // This takes care of avoiding generic types which cause method.Type.CodeType to blow up
            if (method.Type.TypeKind != vsCMTypeRef.vsCMTypeRefCodeType || !(method.Type.CodeType is CodeClass2))
                continue;

            // We only support action methods that return an ActionResult and Task<ActionResult> derived types
            if (!method.Type.CodeType.get_IsDerivedFrom("System.Web.Mvc.ActionResult") && method.Type.CodeType.FullName !="System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>")
            {
                Warning(String.Format("{0} doesn't support {1}.{2} because it doesn't return a supported {3} type", T4FileName, type.Name, method.Name, method.Type.CodeType.FullName));
                continue;
            }

            // Ignore async completion methods as they can't really be used in T4MVC, and can cause issues.
            // See http://stackoverflow.com/questions/5419173/t4mvc-asynccontroller
            if (isAsyncController && method.Name.EndsWith("Completed", StringComparison.OrdinalIgnoreCase))
                continue;

            var methodType = method.Type;
            if(method.Type.CodeType.FullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>")
                methodType = Project.CodeModel.CreateCodeTypeRef("System.Web.Mvc.ActionResult");

            // If we haven't yet seen this return type, keep track of it
            var resTypeInfo2 = new ResultTypeInfo(methodType);
            if (!ResultTypes.ContainsKey(resTypeInfo2.FullName))
            {
                ResultTypes[resTypeInfo2.FullName] = resTypeInfo2;
            }

            // Make sure the method is virtual
            if (!method.CanOverride && method.OverrideKind != vsCMOverrideKind.vsCMOverrideKindOverride)
            {
                try
                {
                    method.CanOverride = true;
                }
                catch
                {
                    // If we couldn't make it virtual, give a warning and skip it
                    Warning(String.Format("{0} was not able to make the action method {1}.{2} virtual. Please change it manually if possible", T4FileName, type.Name, method.Name));
                    continue;
                }
                Warning(String.Format("{0} changed the action method {1}.{2} to be virtual", T4FileName, type.Name, method.Name));
            }

            // Collect misc info about the action method and add it to the collection
            controllerInfo.ActionMethods.Add(new ActionMethodInfo(method, current));
        }
    }
}

void ProcessAllViews(ProjectItem viewsProjectItem, AreaInfo area)
{
    // Go through all the sub-folders in the Views folder
    foreach (ProjectItem item in viewsProjectItem.ProjectItems)
    {

        // We only care about sub-folders, not files
        if (!IsFolder(item))
            continue;

        // Find the controller for this view folder
        ControllerInfo controller = area.Controllers.SingleOrDefault(c => c.Name.Equals(item.Name, StringComparison.OrdinalIgnoreCase));

        if (controller == null)
        {
            // If it doesn't match a controller, treat as a pseudo-controller for consistency
            controller = new ControllerInfo
            {
                Area = area,
                NotRealController = true,
                Namespace = MakeClassName(settings.T4MVCNamespace, area.Name),
                ClassName = item.Name + ControllerSuffix
            };
            area.Controllers.Add(controller);
        }

        AddViewsRecursive(item.ProjectItems, controller.ViewsFolder);
    }
}

void AddViewsRecursive(ProjectItems items, ViewsFolderInfo viewsFolder)
{
  AddViewsRecursive(items, viewsFolder, false);
}

void AddViewsRecursive(ProjectItems items, ViewsFolderInfo viewsFolder, bool useNonQualifiedViewNames)
{
    // Go through all the files in the subfolder to get the view names
    foreach (ProjectItem item in items)
    {
        if (item.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFile)
        {
            // Ignore some extensions that are normally not views
            if (settings.ExcludedViewExtensions.Any(extension => Path.GetExtension(item.Name).Equals(extension, StringComparison.OrdinalIgnoreCase)))
                continue;

            viewsFolder.AddView(item, useNonQualifiedViewNames);
        }
        else if (item.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder)
        {
            string folderName = Path.GetFileName(item.Name);
            if (folderName.Equals("App_LocalResources", StringComparison.OrdinalIgnoreCase))
                continue;
            // Use simple view names if we're already in that mode, or if the folder name is in the collection
            bool folderShouldUseNonQualifiedViewNames = useNonQualifiedViewNames || settings.NonQualifiedViewFolders.Contains(folderName, StringComparer.OrdinalIgnoreCase);
            var subViewFolder = new ViewsFolderInfo() { Name = folderName };
            viewsFolder.SubFolders.Add(subViewFolder);
            AddViewsRecursive(item.ProjectItems, subViewFolder, folderShouldUseNonQualifiedViewNames);
        }
    }
}

void RenderControllerViews(ControllerInfo controller)
{
    PushIndent("            ");
    RenderViewsRecursive(controller.ViewsFolder, controller);
    PopIndent();
}

void RenderViewsRecursive(ViewsFolderInfo viewsFolder, ControllerInfo controller)
{
    if(!viewsFolder.HasNonQualifiedViewNames)
    {
#>
static readonly _ViewNamesClass s_ViewNames = new _ViewNamesClass();
public _ViewNamesClass ViewNames { get { return s_ViewNames; } }
public class _ViewNamesClass
{
<#+
    PushIndent("    ");
    foreach (var viewPair in viewsFolder.Views)
    {
        WriteLine("public readonly string " + EscapeID(Sanitize(viewPair.Key)) + " = \"" + viewPair.Key + "\";");
    }
    PopIndent();
#>
}
<#+}
    // For each view, generate a readonly string
    foreach (var viewPair in viewsFolder.Views)
    {
        WriteLine("public readonly string " + EscapeID(Sanitize(viewPair.Key)) + " = \"" + viewPair.Value + "\";");
    }

    // For each sub folder, generate a class and recurse
    foreach (var subFolder in viewsFolder.SubFolders)
    {
        string name = Sanitize(subFolder.Name);
        string className = "_" + name;

        // If the folder name is the same as the parent, add a modifier to avoid class name conflicts
        // http://mvccontrib.codeplex.com/workitem/7153
        if (name == Sanitize(viewsFolder.Name))
        {
            className += "_";
        }#>
static readonly <#=className#>Class s_<#=name#> = new <#=className#>Class();
public <#=className#>Class <#=EscapeID(name)#> { get { return s_<#=name#>; } }
[<#= GeneratedCode #>, DebuggerNonUserCode]
public partial class <#=className#>Class
{
<#+
PushIndent("    ");
RenderViewsRecursive(subFolder, controller);
PopIndent();

WriteLine("}");
    }
}

IEnumerable<string> GetStaticFilesViewFolders()
{
    if (settings.AddAllViewsFoldersToStaticFilesFolders)
    {
        foreach (var area in Areas)
        {
            if (area.Name == null)
            {
                // project root

                yield return settings.ViewsRootFolder;

                if (area.IsFeatureFolderArea)
                {
                    foreach (var controller in area.Controllers)
                        yield return settings.FeatureFolderRootArea + "\\" + controller.Name;
                }
            }
            else
            {
                // areas

                if (area.IsFeatureFolderArea)
                {
                    foreach (var controller in area.Controllers)
                        yield return settings.AreasFolder + "\\" + area.Name + "\\" + controller.Name;
                }
                else
                {
                    yield return settings.AreasFolder + "\\" + area.Name + "\\" + settings.ViewsRootFolder;
                }
            }
        }
    }
}

void ProcessStaticFiles(Project project, string folder)
{
    ProjectItem folderProjectItem = GetProjectItem(project, folder);
    if (folderProjectItem != null)
    {
        var rootPath = "~";
        if (folder.Contains("\\"))
        {
            rootPath += "/" + folder.Replace("\\", "/");
            rootPath = rootPath.Substring(0, rootPath.LastIndexOf("/"));
        }
        ProcessStaticFilesRecursive(folderProjectItem, rootPath);
    }
}

void ProcessStaticFilesRecursive(ProjectItem projectItem, string path)
{
    int nestedLevel = BuildClassStructureForProvidedPath(path);
    ProcessStaticFilesRecursive(projectItem, path, new HashSet<String>());
    for(int i = 0; i < nestedLevel; ++i) {#>
}
<#+
    PopIndent();
    }
}

void ProcessStaticFilesRecursive(ProjectItem projectItem, string path, HashSet<String> nameSet)
{
    // The passed in HashSet is to guarantee uniqueness with our parent and siblings
    string name = SanitizeWithNoConflicts(projectItem.Name, nameSet);

    // This HashSet is to guarantee uniqueness of our direct children
    // We add our own name to it to avoid class name conflicts (http://mvccontrib.codeplex.com/workitem/7153)
    var childrenNameSet = new HashSet<String>();
    childrenNameSet.Add(name);

    if (IsFolder(projectItem))
    {
        #>
    [<#= GeneratedCode #>, DebuggerNonUserCode]
    public static class <#=EscapeID(name)#> {
        private const string URLPATH = "<#=path#>/<#=projectItem.Name#>";
        public static string Url() { return T4MVCHelpers.ProcessVirtualPath(URLPATH); }
        public static string Url(string fileName) { return T4MVCHelpers.ProcessVirtualPath(URLPATH + "/" + fileName); }
<#+
PushIndent("    ");

// Recurse into all the items in the folder
foreach (ProjectItem item in projectItem.ProjectItems)
{
    ProcessStaticFilesRecursive(
        item,
        path + "/" + projectItem.Name,
        childrenNameSet);
}

PopIndent();
#>
    }

<#+
}
    else { #>
<#+
if (!settings.ExcludedStaticFileExtensions.Any(extension => projectItem.Name.EndsWith(extension, StringComparison.OrdinalIgnoreCase))) {
    // if it's a Typescript file
    if (projectItem.Name.EndsWith(".ts")) {
        string tsJavascriptName =  projectItem.Name.Replace(".ts", ".js");
        string minifiedName = projectItem.Name.Replace(".ts", ".min.js");
        if (AddTimestampToStaticLink(projectItem)) { #>
    public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=minifiedName#>") : Url("<#=tsJavascriptName#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=tsJavascriptName#>");
        <#+} else {#>
    public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>") : Url("<#=tsJavascriptName#>");
<#+}  #>
<#+}
    // if it's a non-minified javascript file
    else if (projectItem.Name.EndsWith(".js") && !projectItem.Name.EndsWith(".min.js")) {
        string minifiedName = projectItem.Name.Replace(".js", ".min.js");
        if (AddTimestampToStaticLink(projectItem)) { #>
    public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=minifiedName#>") : Url("<#=projectItem.Name#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=projectItem.Name#>");
        <#+} else {#>
    public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>") : Url("<#=projectItem.Name#>");
<#+}  #>
<#+}
    else if (projectItem.Name.EndsWith(".css") && !projectItem.Name.EndsWith(".min.css")) { 
        string minifiedName = projectItem.Name.Replace(".css", ".min.css");
        if (AddTimestampToStaticLink(projectItem)) { #>
    public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=minifiedName#>") : Url("<#=projectItem.Name#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=projectItem.Name#>");
        <#+} else {#>
    public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>") : Url("<#=projectItem.Name#>");
        <#+}  #> 
<#+}
    else if (AddTimestampToStaticLink(projectItem)) { #>
    public static readonly string <#=name#> = Url("<#=projectItem.Name#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=projectItem.Name#>");
<#+}
    else { #>
    public static readonly string <#=name#> = Url("<#=projectItem.Name#>");
<#+}
} #>
<#+
// Non folder items may also have children (virtual folders, Class.cs -> Class.Designer.cs, template output)
// Just register them on the same path as their parent item
foreach (ProjectItem item in projectItem.ProjectItems)
{
    ProcessStaticFilesRecursive(item, path, childrenNameSet);
}
    }
}

int BuildClassStructureForProvidedPath(string path)
{
    var folders = path.Split(new char[] {'/', '~'}, StringSplitOptions.RemoveEmptyEntries);
    var parentFolder = String.Empty;
    var currentPath = "~";
    foreach(var folder in folders)
    {
        currentPath += "/" + folder;
        string className = EscapeID(Sanitize(folder));
        // If the folder name is the same as the parent, add a modifier to avoid class name conflicts
        // http://mvccontrib.codeplex.com/workitem/7153
        if (parentFolder == folder)
        {
            className += "_";
        }

        if(!virtualPathesForStaticFiles.Contains(currentPath))
        {
            virtualPathesForStaticFiles.Add(currentPath);#>

    [<#= GeneratedCode #>, DebuggerNonUserCode]
    public static partial class <#=className #> {
        private const string URLPATH = "<#=currentPath#>";
        public static string Url() { return T4MVCHelpers.ProcessVirtualPath(URLPATH); }
        public static string Url(string fileName) { return T4MVCHelpers.ProcessVirtualPath(URLPATH + "/" + fileName); }
<#+     } else {
            #>

    public static partial class <#=className #> {
<#+     }
        PushIndent("    ");
        parentFolder = folder;
    }
    return folders.Length;
}

ProjectItem GetProjectItem(Project project, string name)
{
    return GetProjectItem(project.ProjectItems, name);
}

ProjectItem GetProjectItem(ProjectItems items, string subPath)
{

    ProjectItem current = null;
    foreach (string name in subPath.Split('\\'))
    {
        try
        {
            // ProjectItems.Item() throws when it doesn't exist, so catch the exception
            // to return null instead.
            current = items.Item(name);
        }
        catch
        {
            // If any chunk couldn't be found, fail
            return null;
        }
        items = current.ProjectItems;
    }

    return current;
}

static bool IsController(CodeClass2 type)
{
    // Ignore any class which name doesn't end with "Controller"
    if (!type.FullName.EndsWith(ControllerSuffix)) return false;

    for (; type.FullName != "System.Web.Mvc.Controller"; type = (CodeClass2)type.Bases.Item(1))
    {
        if (type.Bases.Count == 0)
            return false;
    }
    return true;
}

static bool IsAsyncController(CodeClass2 type)
{
    for (; type.FullName != "System.Web.Mvc.AsyncController"; type = (CodeClass2)type.Bases.Item(1))
    {
        if (type.Bases.Count == 0)
            return false;
    }
    return true;
}

static string GetVirtualPath(ProjectItem item)
{
    string fileFullPath = item.get_FileNames(0);

    // Ignore files that are not under the app root (e.g. they could be linked files)
    if (!fileFullPath.StartsWith(AppRoot, StringComparison.OrdinalIgnoreCase))
        return null;

    // Make a virtual path from the physical path
    return "~/" + fileFullPath.Substring(AppRoot.Length).Replace('\\', '/');
}

static string ProcessAreaOrControllerName(string name)
{
    return settings.UseLowercaseRoutes ? name.ToLowerInvariant() : name;
}

// Return all the CodeFunction2 in the CodeElements collection
static IEnumerable<CodeFunction2> GetMethods(CodeClass2 codeClass)
{
    // Only look at regular method (e.g. ignore things like contructors)
    return codeClass.Members.OfType<CodeFunction2>()
        .Where(f => f.FunctionKind == vsCMFunction.vsCMFunctionFunction);
}

// Check if the class has any explicit constructor
static bool HasExplicitConstructor(CodeClass2 codeClass)
{
    return codeClass.Members.OfType<CodeFunction2>().Any(
        f => !f.IsShared && f.FunctionKind == vsCMFunction.vsCMFunctionConstructor);
}

// Check if the class has a default (i.e. no params) constructor
static bool HasExplicitDefaultConstructor(CodeClass2 codeClass)
{
    return codeClass.Members.OfType<CodeFunction2>().Any(
        f => !f.IsShared && f.FunctionKind == vsCMFunction.vsCMFunctionConstructor && f.Parameters.Count == 0);
}

// Find a method with a given name
static CodeFunction2 GetMethod(CodeClass2 codeClass, string name)
{
    return GetMethods(codeClass).FirstOrDefault(f => f.Name == name);
}

// Find an attribute of a given type on an attribute collection
static CodeAttribute2 GetAttribute(CodeElements attributes, string attributeType)
{
    for (int i = 1; i <= attributes.Count; i++)
    {
        try
        {
            var attrib = (CodeAttribute2)attributes.Item(i);
            if (attributeType.Split(',').Contains(attrib.FullName, StringComparer.OrdinalIgnoreCase))
            {
                return attrib;
            }
        }
        catch
        {
            // FullName can throw in some cases, so just ignore those attributes
            continue;
        }
    }
    return null;
}

static CodeAttribute2 GetAttribute(CodeClass2 type, string attributeType)
{
    while(type != null) {
        var attribute = GetAttribute(type.Attributes, attributeType);
        if(attribute != null)
            return attribute;
        if (type.Bases.Count == 0)
            return null;
        type = (CodeClass2)type.Bases.Item(1);
    }
    return null;
}

static string UniqueFullName(CodeTypeRef codeType)
{
	return UniqueFullName(codeType.CodeType);
}

static string UniqueFullName(CodeType codeType)
{
    var uniqueName = codeType.FullName;

    // Match characters not allowed in class names.
    uniqueName = Regex.Replace(uniqueName, @"[^\p{Ll}\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\d]", "_");

    // Remove duplicate '_' characters
    uniqueName = Regex.Replace(uniqueName, @"__+", "_");

    // Remove trailing '_' characters
    uniqueName = uniqueName.TrimEnd('_');

    return uniqueName;
}

// Return whether a ProjectItem is a folder and not a file
static bool IsFolder(ProjectItem item)
{
    return (item.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder);
}

static bool IsFeatureFolderArea(string areaName, ProjectItem areaFolder)
{
    // default-area with regular convention?
    if (areaFolder == null)
        return false;

    if (String.IsNullOrEmpty(areaName))
    {
        // Are we looking at the "Features" folder in the project root?
        return areaFolder.Name == settings.FeatureFolderRootArea;
    }
    else
    {
        // is it a FeatureFolder area?
        return settings.FeatureFolderAreas.Contains("*") || settings.FeatureFolderAreas.Contains(areaFolder.Name);
    }
}

static string MakeClassName(string ns, string classname)
{
    return String.IsNullOrEmpty(ns) ? classname :
        String.IsNullOrEmpty(classname) ? ns : ns + "." + codeProvider.CreateEscapedIdentifier(classname);
}

static string SanitizeWithNoConflicts(string token, HashSet<string> names)
{
    string name = Sanitize(token);

    while (names.Contains(name))
    {
        name += "_";
    }

    names.Add(name);

    return name;
}

static string Sanitize(string token)
{
    if (token == null) return null;

    // Replace all invalid chars by underscores
    token = Regex.Replace(token, @"[\W\b]", "_", RegexOptions.IgnoreCase);

    // If it starts with a digit, prefix it with an underscore
    token = Regex.Replace(token, @"^\d", @"_$0");

    // Check for reserved words
    // TODO: Clean this up and add other reserved words (keywords, etc)
    if (token == "Url") token = "_Url";

    return token;
}

static string EscapeID(string id)
{
    return codeProvider.CreateEscapedIdentifier(id);
}

// Data structure to collect data about an area
class AreaInfo
{
    public AreaInfo()
    {
        Controllers = new HashSet<ControllerInfo>();
    }

    public string Name { get; set; }
    public HashSet<ControllerInfo> Controllers { get; set; }
    public bool IsFeatureFolderArea { get; set; }

    public string Namespace
    {
        get
        {
            // When *not* using an 'Areas' token, we need to disambiguate conflicts
            // between Area names and controller names (from the default Area)
            if (!settings.IncludeAreasToken && DefaultArea.Controllers.Any(c => c.Name == Name))
                return Name + "Area";

            return Name;
        }
    }

    public IEnumerable<ControllerInfo> GetControllers()
    {
        return Controllers.Where(c => !c.IsAbstract);
    }

    public IEnumerable<ControllerInfo> GetAbstractControllers()
    {
        return Controllers.Where(c => c.IsAbstract);
    }
}

// Data structure to collect data about a controller class
class ControllerInfo
{
    public ControllerInfo()
    {
        ActionMethods = new HashSet<ActionMethodInfo>();
        ViewsFolder = new ViewsFolderInfo();
    }

    public AreaInfo Area { get; set; }
    public string AreaName
    {
        get { return Area.Name ?? ""; }
    }

    public string T4MVCControllerFullName
    {
        get
        {
            string name = settings.HelpersPrefix;
            if (!String.IsNullOrEmpty(AreaName))
            {
                if (settings.IncludeAreasToken)
                    name += ".Areas." + EscapeID(Area.Namespace);
                else
                    name += "." + EscapeID(Area.Namespace);
            }
            return name + "." + Name;
        }
    }

    public string ViewPath
    {
        get
        {
            if (string.IsNullOrEmpty(Area.Name))
                return String.Format("~/{0}/{1}/", settings.ViewsRootFolder, Name);
            else
                return String.Format("~/{0}/{1}/{2}/", settings.AreasFolder, settings.ViewsRootFolder, Name);
        }
    }

    // True when this is not a real controller, but a placeholder for views folders that don't match a controller
    public bool NotRealController { get; set; }

    public bool HasExplicitConstructor { get; set; }
    public bool HasExplicitDefaultConstructor { get; set; }
    public bool HasDefaultConstructor { get { return !HasExplicitConstructor || HasExplicitDefaultConstructor; } }
    public bool IsAbstract { get; set; }

    public bool GeneratedCodeIsUpToDate { get; set; }

    public string ClassName { get; set; }
    public string Name
    {
        get
        {
            // Trim the Controller suffix
            return ClassName.Substring(0, ClassName.Length - ControllerSuffix.Length);
        }
    }

    public string Namespace { get; set; }

    public string FullClassName
    {
        get
        {
            return MakeClassName(Namespace, ClassName);
        }
    }

    public string DerivedClassName
    {
        get
        {
            return "T4MVC_" + ClassName;
        }
    }

    public string FullDerivedClassName
    {
        get
        {
            if (NotRealController)
                return FullClassName;
            return MakeClassName(Namespace, DerivedClassName);
        }
    }

    public string GeneratedFileName
    {
        get
        {
            return MakeClassName(AreaName, ClassName + ".generated.cs");
        }
    }

    public string GeneratedFileFullPath
    {
        get
        {
            return Path.Combine(T4Folder, GeneratedFileName);
        }
    }

    public HashSet<ActionMethodInfo> ActionMethods { get; set; }

    public IEnumerable<ActionMethodInfo> CustomActionMethods
    {
        get
        {
            return ActionMethods.Where(m => m.IsCustomReturnType);
        }
    }

    IEnumerable<ActionMethodInfo> ActionMethodsWithNoParameters
    {
        get
        {
            return ActionMethods.Where(m => m.CanBeCalledWithoutParameters);
        }
    }

    public IEnumerable<ActionMethodInfo> ActionMethodsUniqueWithoutParameterlessOverload
    {
        get
        {
            return ActionMethodsWithUniqueNames.Except(ActionMethodsWithNoParameters, new ActionComparer());
        }
    }

    public IEnumerable<ActionMethodInfo> CustomActionMethodsUniqueWithoutParameterlessOverload
    {
        get
        {
            return CustomActionMethodsWithUniqueNames.Except(ActionMethodsWithNoParameters, new ActionComparer());
        }
    }

    // Return a list of actions without duplicate names (even with multiple overloads)
    public IEnumerable<ActionMethodInfo> ActionMethodsWithUniqueNames
    {
        get
        {
            return ActionMethods.Distinct(new ActionComparer());
        }
    }

    // Return a list of actions without duplicate names (even with multiple overloads)
    public IEnumerable<ActionMethodInfo> CustomActionMethodsWithUniqueNames
    {
        get
        {
            return CustomActionMethods.Distinct(new ActionComparer());
        }
    }

    class ActionComparer : IEqualityComparer<ActionMethodInfo>
    {
        public bool Equals(ActionMethodInfo x, ActionMethodInfo y)
        {
            return x.ActionName == y.ActionName;
        }

        public int GetHashCode(ActionMethodInfo obj)
        {
            return obj.ActionName.GetHashCode();
        }
    }

    public IEnumerable<IGrouping<string, MethodParamInfo>> UniqueParameterNamesGroupedByActionName
    {
        get
        {
            var comp = new ActionParameterComparer();
            return ActionMethods
                .SelectMany(m => m.Parameters, (m, p) => new { Method = m, Parameter = p })
                .GroupBy(m => m.Method.ActionName, m => m.Parameter)
                .Select(g => new { g.Key, Items = g.Distinct(comp) })
                .SelectMany(g => g.Items, (g, i) => new { g.Key, Item = i })
                .GroupBy(i => i.Key, i => i.Item); 
        }
    } 

    class ActionParameterComparer : IEqualityComparer<MethodParamInfo>
    {
        public bool Equals(MethodParamInfo x, MethodParamInfo y)
        {
            return x.Name == y.Name;
        }

        public int GetHashCode(MethodParamInfo obj){
            return obj.Name.GetHashCode();
        }
    }

    public ViewsFolderInfo ViewsFolder { get; private set; }

    public override string ToString()
    {
        return Name;
    }

    public override bool Equals(object obj)
    {
        return obj != null && FullClassName == ((ControllerInfo)obj).FullClassName;
    }

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

// Info about a view folder, its views and its sub view folders
class ViewsFolderInfo
{
    public ViewsFolderInfo()
    {
        Views = new Dictionary<string, string>();
        SubFolders = new List<ViewsFolderInfo>();
    }

    public void AddView(ProjectItem item, bool useNonQualifiedViewName)
    {
        string viewName = Path.GetFileName(item.Name);
        string viewFieldName = Path.GetFileNameWithoutExtension(viewName);
 
        // If the simple view name is already in use, include the extension (e.g. foo_ascx instead of just foo)
        if (Views.ContainsKey(viewFieldName))
            viewFieldName = Sanitize(viewName);
 
        HasNonQualifiedViewNames = HasNonQualifiedViewNames | useNonQualifiedViewName;
        string virtualPath = GetVirtualPath(item);
        if (virtualPath != null)
        {
	        Views[viewFieldName] = useNonQualifiedViewName ? Path.GetFileNameWithoutExtension(viewName) : virtualPath;
        }
    }

    public bool HasNonQualifiedViewNames { get; private set; }
    public string Name { get; set; }
    public Dictionary<string, string> Views { get; private set; }
    public List<ViewsFolderInfo> SubFolders { get; set; }
}

// Data structure to collect data about a method
class FunctionInfo
{
    protected CodeFunction2 _method;
    private string _signature;

    public FunctionInfo(CodeFunction2 method)
    {
        Parameters = new List<MethodParamInfo>();

        // Can be null when an custom ActionResult has no ctor
        if (method == null)
            return;

        _method = method;

        // Build a unique signature for the method, used to avoid duplication
        _signature = method.Name;

        CanBeCalledWithoutParameters = true;

        // Process all the parameters
        foreach (var p in method.Parameters.OfType<CodeParameter2>())
        {
            // If any param is not optional, then the method can't be called without parameters
            if (p.ParameterKind != vsCMParameterKind.vsCMParameterKindOptional)
            {
                CanBeCalledWithoutParameters = false;
            }

            // Note: if the param name starts with @ (e.g. to escape a keyword), we need to trim that
            string routeNameExpression = "\"" + p.Name.TrimStart('@') + "\"";

            // If there is a [Bind(Prefix = "someName")] attribute, use it
            if (p.InfoLocation != vsCMInfoLocation.vsCMInfoLocationExternal)
            {
                var attrib = GetAttribute(p.Attributes, "System.Web.Mvc.BindAttribute");
                if (attrib != null)
                {
                    var arg = attrib.Arguments.OfType<CodeAttributeArgument>().FirstOrDefault(a => a.Name == "Prefix");
                    if (arg != null)
                        routeNameExpression = arg.Value;
                }
            }
            
            Parameters.Add(
                new MethodParamInfo()
                {
                    Name = p.Name,
                    RouteNameExpression = routeNameExpression,
                    Type = p.Type.AsString,
                    DefaultValue = p.DefaultValue
                });
            _signature += "," + p.Type.AsString;
        }
    }

    protected virtual CodeTypeRef ReturnTypeImpl { get { return _method.Type; } }

    public string Name { get { return _method.Name; } }
    public string ReturnType { get { return ReturnTypeImpl.AsString; } }
    public string ReturnTypeFullName { get { return ReturnTypeImpl.AsFullName; } }
    public string ReturnTypeUniqueName { get { return IsTaskBased ? "System_Web_Mvc_ActionResult" : UniqueFullName(ReturnTypeImpl); } }
    public bool IsPublic { get { return _method.Access == vsCMAccess.vsCMAccessPublic; } }
    public List<MethodParamInfo> Parameters { get; private set; }
    public bool CanBeCalledWithoutParameters { get; private set; }

    private bool IsTaskBased { get {return ReturnTypeImpl.AsFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>"; } }

    // Write out all the parameters as part of a method declaration
    public void WriteFormalParameters(bool first, bool includeDefaults = false)
    {
        foreach (var p in Parameters)
        {
            if (first)
                first = false;
            else
                TT.Write(", ");

            TT.Write(p.Type + " " + p.Name);
            if(includeDefaults && !string.IsNullOrEmpty(p.DefaultValue))
                TT.Write(" = " + p.DefaultValue);
        }
    }

    // Pass non-empty param values to make sure the ActionResult ctors don't complain
    // REVIEW: this is a bit dirty
    public void WriteNonEmptyParameterValues(bool first)
    {
        foreach (var p in Parameters)
        {
            if (first)
                first = false;
            else
                TT.Write(", ");

            if(!string.IsNullOrEmpty(p.DefaultValue))
                TT.Write(p.DefaultValue);
            else
            {
                switch (p.Type)
                {
                    case "string":
                        TT.Write("\" \"");
                        break;
                    case "byte[]":
                        TT.Write("new byte[0]");
                        break;
                    default:
                        TT.Write("default(" + p.Type + ")");
                        break;
                }
            }
        }
    }

    public override bool Equals(object obj)
    {
        return obj != null && _signature == ((FunctionInfo)obj)._signature;
    }

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

// Data structure to collect data about an action method
class ActionMethodInfo : FunctionInfo
{
    public ActionMethodInfo(CodeFunction2 method, CodeClass2 controller, CodeTypeRef asyncType = null)
        : base(method)
    {
        if(asyncType != null) 
        {
            // Remove the Async from the end of the name to match the actual Action routing would use.
            // This also separates the Action Calls from the implementation
            _actionName = method.Name.Remove(method.Name.Length - 5);
            _returnType = asyncType;
        }
        // Normally, the action name is the method name. But if there is an [ActionName] on
        // the method, get the expression from that instead
        ActionNameValueExpression = '"' + ActionName + '"';
        var attrib = GetAttribute(method.Attributes, "System.Web.Mvc.ActionNameAttribute");
        if (attrib != null)
        {
            var arg = (CodeAttributeArgument)attrib.Arguments.Item(1);
            ActionNameValueExpression = arg.Value;
        }

        if (GetAttribute(method.Attributes, settings.AttributeIndicatingHttps) != null || GetAttribute(controller, settings.AttributeIndicatingHttps) != null)
        {
            ActionUrlHttps = true;
        }
    }

    string _actionName;
    CodeTypeRef _returnType;
    protected override CodeTypeRef ReturnTypeImpl { get { return _returnType ?? base.ReturnTypeImpl; } }

    public string ActionName { get { return _actionName ?? base.Name; } }
    public string ActionNameValueExpression { get; set; }
    public bool ActionUrlHttps {get; set; }

    public bool IsCustomReturnType { get { return _returnType != null; } }

}

// Data about an ActionResult derived type
class ResultTypeInfo
{
    CodeTypeRef _codeType;
    public ResultTypeInfo(CodeTypeRef codeType)
    {
        _codeType = codeType;

        // Use the constructor with the least number of parameters
        var ctor = _codeType.CodeType.Members.OfType<CodeFunction2>()
            .Where(f => f.FunctionKind == vsCMFunction.vsCMFunctionConstructor)
            .OrderBy(f => f.Parameters.Count)
            .FirstOrDefault();
        Constructor = new FunctionInfo(ctor);
    }

    public string Name { get { return _codeType.AsString; } }
    public string FullName { get { return this.IsTaskBased ? "System.Web.Mvc.ActionResult" : _codeType.AsFullName; } }
    public string UniqueName { get { return this.IsTaskBased ?  "System_Web_Mvc_ActionResult" : UniqueFullName(_codeType); } }
    public FunctionInfo Constructor { get; set; }
    public IEnumerable<FunctionInfo> AbstractMethods
    {
        get
        {
            return _codeType.CodeType.Members.OfType<CodeFunction2>().Where(
                f => f.MustImplement).Select(f => new FunctionInfo(f));
        }
    }

    private bool IsTaskBased { get {return _codeType.AsFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>"; } }
}

class MethodParamInfo
{
    public string Name { get; set; }
    public string RouteNameExpression { get; set; }
    public string Type { get; set; }
    public string DefaultValue { get; set; }
}

class MvcSettings : XmlSettings
{
    public static MvcSettings Load(ITextTemplatingEngineHost host)
    {
        return Load<MvcSettings>(host);
    }

    public MvcSettings()
    {
        this.T4MVCNamespace = "T4MVC";
        this.HelpersPrefix = "MVC";
        this.ReferencedNamespaces = new XmlStringArray(new string[] {
        }, "Namespace");
        this.AreasFolder = "Areas";
        this.PortableAreas = new XmlStringArray(new string[] {
        }, "Area");
        this.FeatureFolderAreas = new XmlStringArray(new string[] {
        }, "Area");
        this.IncludeAreasToken = false;
        this.ControllersFolder = "Controllers";
        this.ViewsRootFolder = "Views";
        this.NonQualifiedViewFolders = new XmlStringArray(new string[] {
          "DisplayTemplates",
          "EditorTemplates"
        }, "ViewFolder");
        this.GenerateActionResultInterface = true;
        this.GenerateParamsAsConstantsForActionMethods = false;
        this.GenerateParamsForActionMethods = true;
        this.SupportAsyncActions = false;
        this.UseLowercaseRoutes = false;
        this.LinksNamespace = "Links";
        this.AddTimestampToStaticLinks = false;
        this.StaticFilesFolders = new XmlStringArray(new string[] {
            "Scripts",
            "Content",
        }, "FileFolder");
        this.ExcludedStaticFileExtensions = new XmlStringArray(new string[] {
            ".cs",
            ".cshtml",
            ".aspx",
            ".ascx"
        }, "Extension");
        this.ExcludedViewExtensions = new XmlStringArray(new string[] {
            ".cs",
            ".master",
            ".js",
            ".css"
        }, "Extension");
        this.AttributeIndicatingHttps = "System.Web.Mvc.RequireHttpsAttribute";
        this.GenerateSecureLinksInDebugMode = false;
        this.ParamsPropertySuffix = "Params";
        this.ExplicitHtmlHelpersForPartialsFormat = "Render{0}";
        this.SplitIntoMultipleFiles = true;

    }

    [System.ComponentModel.Description("The namespace used by some of T4MVC's generated code")]
    public string T4MVCNamespace { get; set; }

    [System.ComponentModel.Description("The prefix used for things like MVC.Dinners.Name and MVC.Dinners.Delete(Model.DinnerID)")]
    public string HelpersPrefix { get; set; }

    [System.ComponentModel.Description("Namespaces to be referenced by the generated code")]
    public XmlStringArray ReferencedNamespaces { get; set; }

    [System.ComponentModel.Description("The folder under the project that contains the areas")]
    public string AreasFolder { get; set; }

    [System.ComponentModel.Description("You can list folders containing portable areas here")]
    public IEnumerable<string> PortableAreas { get; set; }

    [System.ComponentModel.Description("Name of folder in your project root that follows the FeatureFolder convention (controllers and views are placed within the same folder)")]
    public string FeatureFolderRootArea { get; set; }

    [System.ComponentModel.Description("Names of areas which follow the FeatureFolder convention (controllers and views are placed within the same folder).\r\nUse <Area>*</Area> if you only use Areas with FeatureFolders")]
    public IEnumerable<string> FeatureFolderAreas { get; set; }

    [System.ComponentModel.Description("Choose whether you want to include an 'Areas' token when referring to areas.\r\ne.g. Assume the Area is called Blog and the Controller is Post:\r\n- When false use MVC.Blog.Post.etc...\r\n- When true use MVC.Areas.Blog.Post.etc...")]
    public bool IncludeAreasToken { get; set; }

    [System.ComponentModel.Description("The folder under the project that contains the controllers")]
    public string ControllersFolder { get; set; }

    [System.ComponentModel.Description("The folder under the project that contains the views")]
    public string ViewsRootFolder { get; set; }

    [System.ComponentModel.Description("Views in DisplayTemplates and EditorTemplates folders shouldn't be fully qualifed\r\nas it breaks the templated helper code")]
    public XmlStringArray  NonQualifiedViewFolders { get; set; }

    [System.ComponentModel.Description("If true, the T4MVC action result interface will be generated\r\nIf false, the namespace of the interface must be referenced in the 'ReferencedNamespaces' setting")]
    public bool GenerateActionResultInterface { get; set; }

    [System.ComponentModel.Description("If true, [new] overrides will be created for async actions on AsyncControllers\r\nThis breaks the Go To Definition function for async actions.")]
    public bool SupportAsyncActions { get; set; }

    [System.ComponentModel.Description("If true, use lower case tokens in routes for the area, controller and action names")]
    public bool UseLowercaseRoutes { get; set; }

    [System.ComponentModel.Description("The namespace that the links are generated in (e.g. \"Links\", as in Links.Content.nerd_jpg)")]
    public string LinksNamespace { get; set; }

    [System.ComponentModel.Description("If true, links to static files include a query string containing the file's last change time.\r\nThis way, when the static file changes, the link changes and guarantees that the client will re-request the resource.\r\ne.g. when true, the link looks like: \"/Content/nerd.jpg?2009-09-04T12:25:48\"\r\nSee http://mvccontrib.codeplex.com/workitem/7163 for potential issues with this feature")]
    public bool AddTimestampToStaticLinks { get; set; }

    [System.ComponentModel.Description("Folders containing static files for which links are generated (e.g. Links.Scripts.Map_js)")]
    public XmlStringArray StaticFilesFolders { get; set; }

    [System.ComponentModel.Description("If true, static file helpers are generated for all view folders. See https://t4mvc.codeplex.com/discussions/445358")]
    public bool AddAllViewsFoldersToStaticFilesFolders { get; set; }

    [System.ComponentModel.Description("Static files to exclude from the generated links")]
    public XmlStringArray ExcludedStaticFileExtensions { get; set; }

    [System.ComponentModel.Description("Files to exclude from the generated views")]
    public XmlStringArray ExcludedViewExtensions { get; set; }

    [System.ComponentModel.Description("When creating links with T4MVC, it can force them to HTTPS if the action method you are linking to requires Http.")]
    public string AttributeIndicatingHttps { get; set; }
    public bool GenerateSecureLinksInDebugMode { get; set; }

    [System.ComponentModel.Description("Set this to false to omit the generation of parameters for action methods.")]
    public bool GenerateParamsForActionMethods { get; set; }

    [System.ComponentModel.Description("Set this to true to omit the generation of parameters for action methods as constants.  Choose this or GenerateParamsForActionMethods.")]
    public bool GenerateParamsAsConstantsForActionMethods { get; set; }

    [System.ComponentModel.Description("The suffix added to action method names for the property containing the parameters, for example ImportParams\r\nfor the Import action method.")]
    public string ParamsPropertySuffix { get; set; }

    [System.ComponentModel.Description("create explicit HtmlHelpers for rendering partials")]
    public bool ExplicitHtmlHelpersForPartials { get; set; }
    public string ExplicitHtmlHelpersForPartialsFormat { get; set; }

    [System.ComponentModel.Description("If true,the template output will be split into multiple files.")]
    public bool SplitIntoMultipleFiles { get; set; }

}

/*
    XmlSettings base classes, if you need to modify the T4MVC properties edit the MvcSettings Class Above
*/

/// Base XmlSettings class, responsible for reading/writing the settigns file contents, all settings other 
/// than string convertable types should decend from this class
abstract class XmlSettingsBase
{
    protected XmlSettingsBase()
    {
        this.NeedsSave = true;
    }

    protected virtual void Init()
    {
    }

    protected bool SaveAsChild { get; private set; }

    protected bool NeedsSave { get; private set; }

    protected static void SetSaveAsChild(XmlSettingsBase settings, bool value)
    {
        settings.SaveAsChild = value;
    }

    protected static void SetNeedsSave(XmlSettingsBase settings, bool value)
    {
        settings.NeedsSave = value;
    }

    protected static void WriteCommentedProperty(System.Xml.XmlWriter writer, string name)
    {
        writer.WriteComment(string.Concat("<", name, "></", name, ">"));
    }

    protected static void WritePropertyDesc(System.Xml.XmlWriter writer, System.ComponentModel.PropertyDescriptor property)
    {
        var desc = property.Attributes.OfType<System.ComponentModel.DescriptionAttribute>().FirstOrDefault();
        if(desc != null)
        {
            writer.WriteComment(desc.Description);
        }
    }

    protected virtual void Load(System.Xml.Linq.XElement xml)
    {
        this.NeedsSave = false;
        int matched = 0;
        int read = 0;
        foreach(System.ComponentModel.PropertyDescriptor property in System.ComponentModel.TypeDescriptor.GetProperties(this))
        {
            object pvalue;
            if(typeof(XmlSettingsBase).IsAssignableFrom(property.PropertyType) || (((pvalue = property.GetValue(this)) != null) && typeof(XmlSettingsBase).IsAssignableFrom(pvalue.GetType())))
            {
                read++;
                var value = xml.Element(property.Name);
                if(value != null)
                {
                    var settings = (XmlSettingsBase)property.GetValue(this);
                    settings.Load(value);
                    if(!settings.NeedsSave)
                        matched++;
                    settings.SaveAsChild = true;
                }
            }
            else if(!property.IsReadOnly)
            {
                read++;
                var value = xml.Element(property.Name);
                if(value != null)
                {
                    if(property.Converter.CanConvertFrom(typeof(string)))
                    {
                        matched++;
                        property.SetValue(this, property.Converter.ConvertFromString(value.Value));
                    }
                    else
                    {
                        System.Reflection.MethodBase parser = property.PropertyType.GetMethod("Parse", new Type[] { typeof(string) });
                        if(parser == null)
                            parser = property.PropertyType.GetConstructor(new Type[] { typeof(string) });

                        if(parser != null)
                        {
                            matched++;
                            property.SetValue(this, parser.Invoke(null, new Object[] { value.Value }));
                        }
                    }
                }
            }
        }
        this.NeedsSave = this.NeedsSave || (matched < read);
    }

    protected virtual void Save(System.Xml.XmlWriter writer)
    {
        foreach(System.ComponentModel.PropertyDescriptor property in System.ComponentModel.TypeDescriptor.GetProperties(this))
        {
            var value = property.GetValue(this);
            WritePropertyDesc(writer, property);
            if(value != null)
            {
                if(typeof(XmlSettingsBase).IsAssignableFrom(value.GetType()))
                {
                    var settings = (XmlSettingsBase)property.GetValue(this);
                    if((settings != null) && settings.SaveAsChild)
                    {
                        writer.WriteStartElement(property.Name);
                        settings.Save(writer);
                        writer.WriteEndElement();
                    }
                } else if(!property.IsReadOnly)
                {
                    writer.WriteElementString(property.Name, property.Converter.ConvertToString(value));
                }
            }
            else
            {
                WriteCommentedProperty(writer, property.Name);
            }
        }
    }
}

/// Custom class to allow string arrays to be read and written to/from settings
class XmlStringArray : XmlSettingsBase, IEnumerable<string>
{
    public XmlStringArray(IEnumerable<string> items, string name)
    {
        this._items = items;
        this._name = name;
        SetSaveAsChild(this, true);
    }

    string _name;
    IEnumerable<string> _items;

    protected override void Load(System.Xml.Linq.XElement xml)
    {
        var items = new List<string>();
        foreach(var item in xml.Elements(this._name))
        {
            items.Add(item.Value);
        }
        this._items = items;
        SetNeedsSave(this, false);
    }

    protected override void Save(System.Xml.XmlWriter writer)
    {
        if(this._items == null || !this._items.Any())
        {
            WriteCommentedProperty(writer, this._name);
            return;
        }

        foreach(var item in this._items)
        {
            writer.WriteElementString(this._name, item);
        }
    }

    public IEnumerator<string> GetEnumerator()
    {
        return this._items.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

/// This is the base class for the standard settings, the main settigns class should inherit from this
/// one since it provides the methods to interact with the T4 system and EnvDTE. Sub-properties can
/// just inherit from XmlSettingsBase.
abstract class XmlSettings : XmlSettingsBase
{
    protected static T Load<T>(ITextTemplatingEngineHost host) where T : XmlSettings, new()
    {
        T settings = new T();
        settings.Init(host);
        return settings;
    }

    void Init(ITextTemplatingEngineHost host)
    {

        this.TemplateFile = Path.GetFileName(host.TemplateFile);
        this.TemplateFolder = Path.GetDirectoryName(host.TemplateFile);

        // Get the DTE service from the host
        var serviceProvider = host as IServiceProvider;
        if (serviceProvider != null)
        {
            this.DTE = (EnvDTE.DTE)serviceProvider.GetService(typeof(EnvDTE.DTE));
        }

        // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe
        if (this.DTE == null)
        {
            throw new Exception("T4Build can only execute through the Visual Studio host");
        }

        this.ProjectItem = this.DTE.Solution.FindProjectItem(host.TemplateFile);

        // If the .tt file is not opened, open it
        if (this.ProjectItem.Document == null)
            this.ProjectItem.Open(EnvDTE.Constants.vsViewKindCode);

        this.Project = this.ProjectItem.ContainingProject;

        if (Project == null)
        {
            throw new Exception("Could not find the VS Project containing the T4 file.");
        }

        this.Load();
        this.Init();
    }

    public string TemplateFile { get; private set; }

    public string TemplateFolder { get; private set; }

    public DTE DTE { get; private set; }

    public ProjectItem ProjectItem { get; private set; }

    public Project Project { get; private set; }

    ProjectItem FindProjectItemRecursive(ProjectItems items, string name)
    {
        if(items == null)
            return null;

        foreach(ProjectItem item in items)
        {
            if(item.Name.Equals(name) || item.Name.StartsWith(name + "."))
                return item;
            var found = FindProjectItemRecursive(item.ProjectItems, name);
            if(found != null)
                return found;
        }

        return null;
    }

    protected ProjectItem FindProjectItem(string name)
    {
        return this.FindProjectItemRecursive(this.Project.ProjectItems, name);
    }

    protected string SettingsFile
    {
        get
        {
            return Path.Combine(this.TemplateFolder, string.Concat(this.TemplateFile, ".settings.xml"));
        }
    }

    void Load()
    {
        if(System.IO.File.Exists(this.SettingsFile))
            try
            {
                this.Load(System.Xml.Linq.XElement.Load(this.SettingsFile));
            } catch { throw; }
    }

    public void SaveChanges(Manager manager)
    {
        // Avoid saving if we dont need to;
        if(!this.NeedsSave)
            return;

        if(manager.FileOkToWrite(this.SettingsFile))
        {
            var settings = new System.Xml.XmlWriterSettings
            {
                Indent = true
            };
            using(var writer = System.Xml.XmlWriter.Create(this.SettingsFile, settings))
            {
                writer.WriteStartDocument();
                writer.WriteStartElement(this.GetType().Name);
                this.Save(writer);
                writer.WriteEndElement();
                writer.WriteEndDocument();
            }

            var item = this.ProjectItem.Collection.AddFromFile(this.SettingsFile);
            item.Properties.Item("ItemType").Value = "None";
        } else
            TT.Error("Cannot save settings file! " + this.SettingsFile);
    }
}

/*
    Manager.tt from Damien Guard: http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited
*/


// Manager class records the various blocks so it can split them up
class Manager
{
    private class Block
    {
        public String Name;
        public int Start, Length;
    }

    private Block currentBlock;
    private List<Block> files = new List<Block>();
    private Block footer = new Block();
    private Block header = new Block();
    private ITextTemplatingEngineHost host;
    private StringBuilder template;
    protected List<String> generatedFileNames = new List<String>();

    public static Manager Create(ITextTemplatingEngineHost host, StringBuilder template)
    {
        return (host is IServiceProvider) ? new VSManager(host, template) : new Manager(host, template);
    }

    public virtual bool FileOkToWrite(String fileName)
    {
        return true;
    }

    public void KeepGeneratedFile(String name)
    {
        name = Path.Combine(Path.GetDirectoryName(host.TemplateFile), name);
        generatedFileNames.Add(name);
    }
    
    public void StartNewFile(String name)
    {
        if (name == null)
            throw new ArgumentNullException("name");
        CurrentBlock = new Block { Name = name };
    }

    public void StartFooter()
    {
        CurrentBlock = footer;
    }

    public void StartHeader()
    {
        CurrentBlock = header;
    }

    public void EndBlock()
    {
        if (CurrentBlock == null)
            return;
        CurrentBlock.Length = template.Length - CurrentBlock.Start;
        if (CurrentBlock != header && CurrentBlock != footer)
            files.Add(CurrentBlock);
        currentBlock = null;
    }

    public virtual void Process(bool split)
    {
        if (split)
        {
            EndBlock();
            String headerText = template.ToString(header.Start, header.Length);
            String footerText = template.ToString(footer.Start, footer.Length);
            String outputPath = Path.GetDirectoryName(host.TemplateFile);
            files.Reverse();
            foreach (Block block in files)
            {
                String fileName = Path.Combine(outputPath, block.Name);
                String content = headerText + template.ToString(block.Start, block.Length) + footerText;
                generatedFileNames.Add(fileName);
                CreateFile(fileName, content);
                template.Remove(block.Start, block.Length);
            }
        }
    }

    protected virtual void CreateFile(String fileName, String content)
    {
        if (IsFileContentDifferent(fileName, content))
            File.WriteAllText(fileName, content);
    }

    public virtual String GetCustomToolNamespace(String fileName)
    {
        return null;
    }

    public virtual String DefaultProjectNamespace
    {
        get { return null; }
    }

    protected bool IsFileContentDifferent(String fileName, String newContent)
    {
        return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent);
    }

    private Manager(ITextTemplatingEngineHost host, StringBuilder template)
    {
        this.host = host;
        this.template = template;
    }

    private Block CurrentBlock
    {
        get { return currentBlock; }
        set
        {
            if (CurrentBlock != null)
                EndBlock();
            if (value != null)
                value.Start = template.Length;
            currentBlock = value;
        }
    }

    private class VSManager : Manager
    {
        private EnvDTE.ProjectItem templateProjectItem;
        private EnvDTE.DTE dte;
        private Action<String> checkOutAction;
        private Action<IEnumerable<String>> projectSyncAction;
        private IVsQueryEditQuerySave2 queryEditSave;

        public override String DefaultProjectNamespace
        {
            get
            {
                return templateProjectItem.ContainingProject.Properties.Item("DefaultNamespace").Value.ToString();
            }
        }

        public override String GetCustomToolNamespace(string fileName)
        {
            return dte.Solution.FindProjectItem(fileName).Properties.Item("CustomToolNamespace").Value.ToString();
        }

        public override void Process(bool split)
        {
            if (templateProjectItem.ProjectItems == null)
                return;
            base.Process(split);
            projectSyncAction.EndInvoke(projectSyncAction.BeginInvoke(generatedFileNames, null, null));
        }

        public override bool FileOkToWrite(String fileName)
        {
            CheckoutFileIfRequired(fileName);
            return base.FileOkToWrite(fileName);
        }

        protected override void CreateFile(String fileName, String content)
        {
            if (IsFileContentDifferent(fileName, content))
            {
                CheckoutFileIfRequired(fileName);
                File.WriteAllText(fileName, content);
            }
        }

        internal VSManager(ITextTemplatingEngineHost host, StringBuilder template)
            : base(host, template)
        {
            var hostServiceProvider = (IServiceProvider)host;
            if (hostServiceProvider == null)
                throw new ArgumentNullException("Could not obtain IServiceProvider");
            dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
            if (dte == null)
                throw new ArgumentNullException("Could not obtain DTE from host");
            templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile);
            checkOutAction = (String fileName) => dte.SourceControl.CheckOutItem(fileName);
            projectSyncAction = (IEnumerable<String> keepFileNames) => ProjectSync(templateProjectItem, keepFileNames);
            queryEditSave = (IVsQueryEditQuerySave2)hostServiceProvider.GetService(typeof(SVsQueryEditQuerySave));
        }

        private static void ProjectSync(EnvDTE.ProjectItem templateProjectItem, IEnumerable<String> keepFileNames)
        {
            var keepFileNameSet = new HashSet<String>(keepFileNames);
            var projectFiles = new Dictionary<String, EnvDTE.ProjectItem>();
            var originalFilePrefix = Path.GetFileNameWithoutExtension(templateProjectItem.get_FileNames(0)) + ".";
            foreach (EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems)
                projectFiles.Add(projectItem.get_FileNames(0), projectItem);

            // Remove unused items from the project
            foreach (var pair in projectFiles)
                if (!keepFileNames.Contains(pair.Key) && !(Path.GetFileNameWithoutExtension(pair.Key) + ".").StartsWith(originalFilePrefix))
                    pair.Value.Delete();

            // Add missing files to the project
            foreach (String fileName in keepFileNameSet)
                if (!projectFiles.ContainsKey(fileName))
                    templateProjectItem.ProjectItems.AddFromFile(fileName);
        }

        private void CheckoutFileIfRequired(String fileName)
        {
            if (queryEditSave != null)
            {
                uint pfEditVerdict;
                queryEditSave.QuerySaveFile(fileName, 0, null, out pfEditVerdict);
            }
            else
            {
                var sc = dte.SourceControl;
                if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName)) 
                    checkOutAction.EndInvoke(checkOutAction.BeginInvoke(fileName, null, null));
            }
        }
    }
}

/*
    End of Manager.tt
*/
#>
