/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt VS Tools.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

using EnvDTE;
using Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TemplateWizard;

using QtVsTools.Common;
using QtVsTools.VisualStudio;

using QtVsTools.Core;
using QtVsTools.Core.QtMsBuild;

namespace QtVsTools.Wizards.ProjectWizard
{
    using static EnumExt;
    using WhereConfig = Func<IWizardConfiguration, bool>;

    public interface IWizardConfiguration
    {
        string Name { get; }
        VersionInformation QtVersion { get; }
        string QtVersionName { get; }
        string QtVersionPath { get; }
        string Target { get; }
        string Platform { get; }
        bool IsDebug { get; }
        IEnumerable<string> Modules { get; }
    }

    public enum ProjectTargets
    {
        Windows,
        [String("Windows Store")] WindowsStore,
        [String("Linux (SSH)")] LinuxSSH,
        [String("Linux (WSL)")] LinuxWSL
    }

    public enum ProjectPlatforms
    {
        [String("x64")] X64,
        Win32,
        ARM64,
        ARM,
    }

    public abstract class ProjectTemplateWizard : IWizard
    {
        protected readonly WhereConfig WhereConfig_SelectAll = (x => true);

        protected struct ItemProperty
        {
            public string Key { get; }
            public string Value { get; }
            public WhereConfig WhereConfig { get; }

            public ItemProperty(string key, string value, WhereConfig whereConfig = null)
            {
                Key = key;
                Value = value;
                WhereConfig = whereConfig;
            }

            public static implicit operator ItemProperty[](ItemProperty that)
            {
                return new[] { that };
            }
        }

        protected class ItemGlobalDef
        {
            public string ItemType { get; set; }
            public ItemProperty[] Properties { get; set; }
        }

        protected class ItemDef
        {
            public string ItemType { get; set; }
            public string Include { get; set; }
            public ItemProperty[] Properties { get; set; }
            public string Filter { get; set; }
            public WhereConfig WhereConfig { get; set; }
        }

        // keep in sync with QtVsTools.Core.TemplateType

        [Flags]
        protected enum Options : uint
        {
            Application = 0x000,
            DynamicLibrary = 0x001,
            StaticLibrary = 0x002,
            GUISystem = 0x004,
            ConsoleSystem = 0x008,
            PluginProject = 0x100
        }

        protected abstract Options TemplateType { get; }
        protected abstract WizardData WizardData { get; }
        protected abstract WizardWindow WizardWindow { get; }

        protected virtual IDictionary<string, ItemGlobalDef> ItemGlobals => null;
        protected virtual IEnumerable<ItemDef> ExtraItems => Enumerable.Empty<ItemDef>();
        protected virtual IEnumerable<string> ExtraModules => Enumerable.Empty<string>();
        protected virtual IEnumerable<string> ExtraDefines => Enumerable.Empty<string>();

        protected virtual IEnumerable<IWizardConfiguration> Configurations => WizardData.Configs;
        protected virtual bool UsePrecompiledHeaders => WizardData.UsePrecompiledHeader;

        protected Dictionary<string, string> ParameterValues { get; private set; }
        protected EnvDTE.DTE Dte { get; private set; }

        ItemDef _PrecompiledHeaderFile;
        protected virtual ItemDef PrecompiledHeader => _PrecompiledHeaderFile
            ?? (_PrecompiledHeaderFile = new ItemDef
            {
                ItemType = "ClInclude",
                Include = "stdafx.h",
                Filter = "Header Files"
            });

        ItemDef _PrecompiledHeaderSourceFile;
        protected virtual ItemDef PrecompiledHeaderSource => _PrecompiledHeaderSourceFile
            ?? (_PrecompiledHeaderSourceFile = new ItemDef
            {
                ItemType = "ClCompile",
                Include = "stdafx.cpp",
                Properties = new ItemProperty("PrecompiledHeader", "Create"),
                Filter = "Source Files",
            });

        protected class TemplateParameters
        {
            public ProjectTemplateWizard Template { get; set; }

            string ParamKey(Enum param)
            {
                return string.Format("${0}$", param.Cast<string>());
            }

            public string this[Enum param]
            {
                get => Template.ParameterValues[ParamKey(param)];
                set => Template.ParameterValues[ParamKey(param)] = value;
            }
        }

        protected enum NewProject
        {
            // Read-only parameters
            [String("projectname")] Name,
            [String("safeprojectname")] SafeName,
            [String("destinationdirectory")] DestinationDirectory,
            [String("solutiondirectory")] SolutionDirectory,

            // Custom parameters
            ToolsVersion,
            ProjectConfigurations,
            Properties,
            ProjectGuid,
            Keyword,
            Globals,
            Configurations,
            PropertySheets,
            QtSettings,
            BuildSettings,
            ProjectItems,
            FilterItems,
        }

        TemplateParameters _Parameter;
        protected TemplateParameters Parameter => _Parameter
            ?? (_Parameter = new TemplateParameters { Template = this });

        protected QtVersionManager VersionManager => QtVersionManager.The();

        public virtual void ProjectItemFinishedGenerating(ProjectItem projectItem) { }
        public virtual void BeforeOpeningFile(ProjectItem projectItem) { }
        public virtual void RunFinished() { }
        public virtual bool ShouldAddProjectItem(string filePath) => true;

        protected virtual void BeforeWizardRun() { }
        protected virtual void BeforeTemplateExpansion() { }
        protected virtual void OnProjectGenerated(Project project) { }

        public virtual void RunStarted(
            object automationObject,
            Dictionary<string, string> parameterValues,
            WizardRunKind runKind,
            object[] customParams)
        {
            Dte = automationObject as DTE;
            ParameterValues = parameterValues;

            Debug.Assert(WizardWindow != null);

            BeforeWizardRun();

            var iVsUIShell = VsServiceProvider.GetService<SVsUIShell, IVsUIShell>();
            if (iVsUIShell == null)
                throw new NullReferenceException("IVsUIShell");

            try {
                IntPtr hwnd;
                iVsUIShell.EnableModeless(0);
                iVsUIShell.GetDialogOwnerHwnd(out hwnd);
                WindowHelper.ShowModal(WizardWindow, hwnd);
            } catch (QtVSException exception) {
                Messages.DisplayErrorMessage(exception.Message);
                throw;
            } finally {
                iVsUIShell.EnableModeless(1);
            }
            if (!WizardWindow.DialogResult ?? false) {
                try {
                    Directory.Delete(Parameter[NewProject.DestinationDirectory]);
                    Directory.Delete(Parameter[NewProject.SolutionDirectory]);
                } catch { }
                throw new WizardBackoutException();
            }

            BeforeTemplateExpansion();
            Expand();
        }

        public virtual void ProjectFinishedGenerating(Project project)
        {
            OnProjectGenerated(project);
        }

        protected static bool IsLinux(IWizardConfiguration wizConfig)
        {
            return wizConfig.Target.EqualTo(ProjectTargets.LinuxSSH)
                || wizConfig.Target.EqualTo(ProjectTargets.LinuxWSL);
        }

        protected static string GetLinuxCompilerPath(IWizardConfiguration wizConfig)
        {
            if (!IsLinux(wizConfig))
                return string.Empty;
            if (string.IsNullOrEmpty(wizConfig.QtVersionPath))
                return string.Empty;
            string[] linuxPaths = wizConfig.QtVersionPath.Split(':');
            if (linuxPaths == null || linuxPaths.Length <= 2)
                return string.Empty;
            return linuxPaths[2];
        }

        protected virtual void Expand()
        {
            Debug.Assert(ParameterValues != null);
            Debug.Assert(Dte != null);
            Debug.Assert(Configurations != null);
            Debug.Assert(ExtraItems != null);

            StringBuilder xml;

            ///////////////////////////////////////////////////////////////////////////////////////
            // Tools version = VS version
            //
            Parameter[NewProject.ToolsVersion] = Dte.Version;

            ///////////////////////////////////////////////////////////////////////////////////////
            // Configurations
            //
            xml = new StringBuilder();
            foreach (IWizardConfiguration c in Configurations) {
                xml.AppendLine(string.Format(@"
    <ProjectConfiguration Include=""{0}|{1}"">
      <Configuration>{0}</Configuration>
      <Platform>{1}</Platform>
    </ProjectConfiguration>",
                    /*{0}*/ c.Name,
                    /*{1}*/ c.Platform));
            }
            Parameter[NewProject.ProjectConfigurations] = FormatParam(xml);

            ///////////////////////////////////////////////////////////////////////////////////////
            // Properties
            //
            xml = new StringBuilder();
            foreach (IWizardConfiguration c in Configurations) {
                xml.AppendLine(string.Format(@"
  <PropertyGroup Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">",
                    /*{0}*/ c.Name,
                    /*{1}*/ c.Platform));
                if (IsLinux(c)) {
                    var compilerPath = GetLinuxCompilerPath(c);
                    if (!string.IsNullOrEmpty(compilerPath))
                        xml.AppendLine(string.Format(@"
      <RemoteCCompileToolExe>{0}</RemoteCCompileToolExe>
      <RemoteCppCompileToolExe>{0}</RemoteCppCompileToolExe>
      <RemoteLdToolExe>{0}</RemoteLdToolExe>",
                            /*{0}*/ compilerPath));
                }
                xml.AppendLine(@"
  </PropertyGroup>");
            }
            Parameter[NewProject.Properties] = FormatParam(xml);

            ///////////////////////////////////////////////////////////////////////////////////////
            // Globals
            //
            xml = new StringBuilder();
            Parameter[NewProject.ProjectGuid] = HelperFunctions.NewProjectGuid();
            Parameter[NewProject.Keyword] = Resources.QtVSVersionTag;

            ///////////////////////////////////////////////////////////////////////////////////////
            // Globals: Windows
            //
            foreach (IWizardConfiguration c in Configurations
                .Where(c => c.Target.EqualTo(ProjectTargets.Windows))) {
                if (!string.IsNullOrEmpty(c.QtVersion.VC_WindowsTargetPlatformVersion)) {
                    xml.AppendLine(string.Format(@"
    <WindowsTargetPlatformVersion Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">{2}</WindowsTargetPlatformVersion>",
                        /*{0}*/ c.Name,
                        /*{1}*/ c.Platform,
                        /*{2}*/ c.QtVersion.VC_WindowsTargetPlatformVersion));
                }
            }

            ///////////////////////////////////////////////////////////////////////////////////////
            // Globals: Windows Store
            //
            foreach (IWizardConfiguration c in Configurations
                .Where(c => c.Target.EqualTo(ProjectTargets.WindowsStore))) {
                xml.AppendLine(string.Format(@"
    <ApplicationType Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">Windows Store</ApplicationType>
    <WindowsTargetPlatformVersion Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">{2}</WindowsTargetPlatformVersion>
    <WindowsTargetPlatformMinVersion Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">{3}</WindowsTargetPlatformMinVersion>
    <MinimumVisualStudioVersion Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">{4}</MinimumVisualStudioVersion>
    <ApplicationTypeRevision Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">{5}</ApplicationTypeRevision>
    <DefaultLanguage Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">en</DefaultLanguage>
    <AppContainerApplication Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">true</AppContainerApplication>",
                    /*{0}*/ c.Name,
                    /*{1}*/ c.Platform,
                    /*{2}*/ c.QtVersion.VC_WindowsTargetPlatformVersion,
                    /*{3}*/ c.QtVersion.VC_WindowsTargetPlatformMinVersion,
                    /*{4}*/ c.QtVersion.VC_MinimumVisualStudioVersion,
                    /*{5}*/ c.QtVersion.VC_ApplicationTypeRevision));
            }

            ///////////////////////////////////////////////////////////////////////////////////////
            // Globals: Linux
            //
            foreach (IWizardConfiguration c in Configurations.Where(c => IsLinux(c))) {
                xml.AppendLine(string.Format(@"
    <ApplicationType Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">Linux</ApplicationType>
    <ApplicationTypeRevision Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">1.0</ApplicationTypeRevision>
    <TargetLinuxPlatform Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">Generic</TargetLinuxPlatform>
    <LinuxProjectType Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">{{D51BCBC9-82E9-4017-911E-C93873C4EA2B}}</LinuxProjectType>",
                    /*{0}*/ c.Name,
                    /*{1}*/ c.Platform));
            }

            Parameter[NewProject.Globals] = FormatParam(xml);

            ///////////////////////////////////////////////////////////////////////////////////////
            // VC Configurations
            //
            xml = new StringBuilder();
            foreach (IWizardConfiguration c in Configurations) {
                ProjectTargets target;
                if (!c.Target.TryCast(out target))
                    continue;
                xml.AppendLine(string.Format(@"
  <PropertyGroup Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"" Label=""Configuration"">",
                    /*{0}*/ c.Name,
                    /*{1}*/ c.Platform));
                if (TemplateType.HasFlag(Options.DynamicLibrary)) {
                    xml.AppendLine(@"
    <ConfigurationType>DynamicLibrary</ConfigurationType>");
                } else if (TemplateType.HasFlag(Options.StaticLibrary)) {
                    xml.AppendLine(@"
    <ConfigurationType>StaticLibrary</ConfigurationType>");
                } else {
                    xml.AppendLine(@"
    <ConfigurationType>Application</ConfigurationType>");
                }
                switch (target) {
                case ProjectTargets.Windows:
                case ProjectTargets.WindowsStore:
                    xml.AppendLine(string.Format(@"
    <PlatformToolset>v{0}</PlatformToolset>",
                        /*{0}*/ BuildConfig.PlatformToolset));
                    break;
                case ProjectTargets.LinuxSSH:
                    xml.AppendLine(@"
    <PlatformToolset>Remote_GCC_1_0</PlatformToolset>");
                    break;
                case ProjectTargets.LinuxWSL:
                    xml.AppendLine(@"
    <PlatformToolset>WSL_1_0</PlatformToolset>");
                    break;
                }
                if (IsLinux(c)) {
                    xml.AppendLine(string.Format(@"
    <UseDebugLibraries>{0}</UseDebugLibraries>",
                        /*{0}*/ c.IsDebug ? "true" : "false"));
                } else if (target == ProjectTargets.WindowsStore) {
                    xml.AppendLine(@"
    <GenerateManifest>false</GenerateManifest>
    <EmbedManifest>false</EmbedManifest>");
                }
                xml.AppendLine(@"
  </PropertyGroup>");
            }
            Parameter[NewProject.Configurations] = FormatParam(xml);

            ///////////////////////////////////////////////////////////////////////////////////////
            // Property sheets
            //
            xml = new StringBuilder();
            foreach (IWizardConfiguration c in Configurations) {
                xml.AppendLine(string.Format(@"
  <ImportGroup Label=""PropertySheets"" Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">
    <Import Project=""$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props"" Condition=""exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')"" Label=""LocalAppDataPlatform"" />
    <Import Project=""$(QtMsBuild)\Qt.props"" />
  </ImportGroup>",
                    /*{0}*/ c.Name,
                    /*{1}*/ c.Platform));
            }
            Parameter[NewProject.PropertySheets] = FormatParam(xml);

            ///////////////////////////////////////////////////////////////////////////////////////
            // Qt settings
            //
            xml = new StringBuilder();
            foreach (IWizardConfiguration c in Configurations) {
                xml.AppendLine(string.Format(@"
  <PropertyGroup Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"" Label=""QtSettings"">
    <QtInstall>{2}</QtInstall>
    <QtModules>{3}</QtModules>
    <QtBuildConfig>{4}</QtBuildConfig>",
                    /*{0}*/ c.Name,
                    /*{1}*/ c.Platform,
                    /*{2}*/ c.QtVersionName,
                    /*{3}*/ string.Join(";", c.Modules.Union(ExtraModules)),
                    /*{4}*/ c.IsDebug ? "debug" : "release"));
                if (c.Target.EqualTo(ProjectTargets.WindowsStore)) {
                    xml.AppendLine(@"
    <QtDeploy>true</QtDeploy>
    <QtDeployToProjectDir>true</QtDeployToProjectDir>
    <QtDeployVsContent>true</QtDeployVsContent>");
                }
                xml.AppendLine(@"
  </PropertyGroup>");
            }
            Parameter[NewProject.QtSettings] = FormatParam(xml);

            ///////////////////////////////////////////////////////////////////////////////////////
            // Build settings
            //
            IEnumerable<ItemProperty> mocProperties
                = ItemGlobals?[QtMoc.ItemTypeName]?.Properties ?? Enumerable.Empty<ItemProperty>();
            IEnumerable<ItemProperty> clProperties
                = ItemGlobals?["ClCompile"]?.Properties ?? Enumerable.Empty<ItemProperty>();
            IEnumerable<ItemProperty> linkProperties
                = ItemGlobals?["Link"]?.Properties ?? Enumerable.Empty<ItemProperty>();

            xml = new StringBuilder();
            foreach (IWizardConfiguration c in Configurations) {
                xml.AppendLine(string.Format(@"
  <ItemDefinitionGroup Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"" Label=""Configuration"">",
                    /*{0}*/ c.Name,
                    /*{1}*/ c.Platform));


                ///////////////////////////////////////////////////////////////////////////////////
                // Build settings: C++ compiler
                //
                if (!IsLinux(c)) {
                    // Windows
                    xml.AppendLine(@"
    <ClCompile>
      <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType>
      <MultiProcessorCompilation>true</MultiProcessorCompilation>");
                    if (c.IsDebug) {
                        xml.AppendLine(@"
      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
      <Optimization>Disabled</Optimization>
      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>");
                    } else {
                        xml.AppendLine(@"
      <DebugInformationFormat>None</DebugInformationFormat>
      <Optimization>MaxSpeed</Optimization>
      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>");
                    }
                    if (c.Target.EqualTo(ProjectTargets.WindowsStore)) {
                        xml.AppendLine(@"
      <CompileAsWinRT>false</CompileAsWinRT>
      <PrecompiledHeader>NotUsing</PrecompiledHeader>
      <RuntimeTypeInfo>true</RuntimeTypeInfo>");
                    }
                    if (UsePrecompiledHeaders) {
                        xml.AppendLine(string.Format(@"
      <UsePrecompiledHeader>Use</UsePrecompiledHeader>
      <PrecompiledHeaderFile>{0}</PrecompiledHeaderFile>",
                            /*{0}*/ PrecompiledHeader.Include));
                    }
                    if (ExtraDefines?.Any() == true) {
                        xml.AppendLine(string.Format(@"
      <PreprocessorDefinitions>{0};%(PreprocessorDefinitions)</PreprocessorDefinitions>",
                            /*{0}*/ string.Join(";", ExtraDefines)));
                    }
                    foreach (ItemProperty p in clProperties) {
                        xml.AppendLine(string.Format(@"
      <{0}>{1}</{0}>",
                            /*{0}*/ p.Key,
                            /*{1}*/ p.Value));
                    }
                    xml.AppendLine(@"
    </ClCompile>");
                } else {
                    // Linux
                    xml.AppendLine(@"
    <ClCompile>
      <PositionIndependentCode>true</PositionIndependentCode>
    </ClCompile>");
                }

                ///////////////////////////////////////////////////////////////////////////////////
                // Build settings: Linker
                //
                if (!IsLinux(c)) {
                    // Windows
                    xml.AppendLine(string.Format(@"
    <Link>
      <SubSystem>{0}</SubSystem>
      <GenerateDebugInformation>{1}</GenerateDebugInformation>",
                        /*{0}*/ TemplateType.HasFlag(Options.ConsoleSystem) ? "Console" : "Windows",
                        /*{1}*/ c.IsDebug ? "true" : "false"));
                    if (c.Target.EqualTo(ProjectTargets.WindowsStore)) {
                        xml.AppendLine(string.Format(@"
      <AdditionalOptions>/APPCONTAINER %(AdditionalOptions)</AdditionalOptions>
      <GenerateManifest>false</GenerateManifest>
      <GenerateWindowsMetadata>false</GenerateWindowsMetadata>
      <TargetMachine>{0}</TargetMachine>",
                            /*{0}*/ c.QtVersion.VC_Link_TargetMachine));
                    }
                    foreach (ItemProperty p in linkProperties) {
                        xml.AppendLine(string.Format(@"
      <{0}>{1}</{0}>",
                            /*{0}*/ p.Key,
                            /*{1}*/ p.Value));
                    }
                    xml.AppendLine(@"
    </Link>");
                }

                ///////////////////////////////////////////////////////////////////////////////////
                // Build settings: moc
                //
                if (UsePrecompiledHeaders || mocProperties.Any()) {
                    xml.AppendLine(string.Format(@"
    <{0}>", QtMoc.ItemTypeName));
                    foreach (ItemProperty p in mocProperties) {
                        xml.AppendLine(string.Format(@"
      <{0}>{1}</{0}>",
                            /*{0}*/ p.Key,
                            /*{1}*/ p.Value));
                    }
                    if (UsePrecompiledHeaders) {
                        xml.AppendLine(string.Format(@"
      <{0}>{1};%({0})</{0}>",
                            /*{0}*/ QtMoc.Property.PrependInclude,
                            /*{1}*/ PrecompiledHeader.Include));
                    }
                    xml.AppendLine(string.Format(@"
    </{0}>", QtMoc.ItemTypeName));
                }

                ///////////////////////////////////////////////////////////////////////////////////
                // Build settings: remaining item types
                //
                if (ItemGlobals != null) {
                    foreach (string itemType in ItemGlobals.Keys
                        .Except(new[] { "ClCompile", "Link", QtMoc.ItemTypeName })) {
                        xml.AppendLine(string.Format(@"
    <{0}>",
                            /*{0}*/ itemType));
                        foreach (ItemProperty p in ItemGlobals[itemType].Properties) {
                            xml.AppendLine(string.Format(@"
      <{0}>{1}</{0}>",
                                /*{0}*/ p.Key,
                                /*{1}*/ p.Value));
                        }
                        xml.AppendLine(string.Format(@"
    </{0}>",
                            /*{0}*/ itemType));
                    }
                }
                xml.AppendLine(@"
  </ItemDefinitionGroup>");
            }

            Parameter[NewProject.BuildSettings] = FormatParam(xml);

            ///////////////////////////////////////////////////////////////////////////////////////
            // Project items
            //
            IEnumerable<ItemDef> projectItems = ExtraItems
                .Where((ItemDef item) => item.WhereConfig == null
                    || Configurations.Where(item.WhereConfig).Any())
                .Union(UsePrecompiledHeaders
                    ? new[] { PrecompiledHeader, PrecompiledHeaderSource }
                    : Enumerable.Empty<ItemDef>());

            xml = new StringBuilder();
            foreach (ItemDef item in projectItems) {
                bool itemHasProperties = (item.WhereConfig != null || item.Properties != null);
                xml.Append(string.Format(@"
    <{0} Include=""{1}""{2}",
                    /*{0}*/ item.ItemType,
                    /*{1}*/ item.Include,
                    /*{2}*/ itemHasProperties ? ">" : " />"));

                if (item.Properties != null) {
                    foreach (ItemProperty property in item.Properties) {
                        IEnumerable<IWizardConfiguration> configs = Configurations
                            .Where(property.WhereConfig ?? WhereConfig_SelectAll);
                        foreach (IWizardConfiguration config in configs) {
                            xml.AppendLine(string.Format(@"
      <{0} Condition=""'$(Configuration)|$(Platform)' == '{1}|{2}'"">{3}</{0}>",
                                /*{0}*/ property.Key,
                                /*{1}*/ config.Name,
                                /*{2}*/ config.Platform,
                                /*{3}*/ property.Value));
                        }
                    }
                }

                if (item.WhereConfig != null) {
                    IEnumerable<IWizardConfiguration> excludedConfigs = Configurations
                        .Where(config => !item.WhereConfig(config));
                    foreach (var excludedConfig in excludedConfigs) {
                        xml.AppendLine(string.Format(@"
      <ExcludedFromBuild Condition=""'$(Configuration)|$(Platform)' == '{0}|{1}'"">true</ExcludedFromBuild>",
                            /*{0}*/ excludedConfig.Name,
                            /*{1}*/ excludedConfig.Platform));
                    }
                }

                if (itemHasProperties) {
                    xml.AppendLine(string.Format(@"
    </{0}>",
                        /*{0}*/ item.ItemType));
                }
            }
            Parameter[NewProject.ProjectItems] = FormatParam(xml);

            ///////////////////////////////////////////////////////////////////////////////////////
            // Project items: filters
            //
            xml = new StringBuilder();
            foreach (ItemDef item in projectItems) {
                xml.Append(string.Format(@"
    <{0} Include=""{1}"">",
                    /*{0}*/ item.ItemType,
                    /*{1}*/ item.Include));
                xml.AppendLine(string.Format(@"
      <Filter>{0}</Filter>",
                    /*{0}*/ item.Filter));
                xml.AppendLine(string.Format(@"
    </{0}>",
                    /*{0}*/ item.ItemType));
            }
            Parameter[NewProject.FilterItems] = FormatParam(xml);
        }

        // Matches empty lines; captures first newline
        static readonly Regex patternEmptyLines
            = new Regex(@"(?:^|(?<FIRST_NL>\r\n))(?:\r\n)+(?![\r\n]|$)|(?:\r\n)+$");

        protected static string FormatParam(StringBuilder paramValue)
        {
            return FormatParam(paramValue.ToString());
        }

        protected static string FormatParam(string paramValue)
        {
            // Remove empty lines; replace with first newline (if any)
            paramValue = patternEmptyLines.Replace(paramValue,
                (Match m) => m.Groups["FIRST_NL"].Value);

            return paramValue;
        }
    }
}