// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

//---------------------------------------------------------------------------
//
// Description: This is a MSBuild task which generates a temporary target assembly
//              if current project contains a xaml file with local-type-reference.
//
//              It generates a temporary project file and then call build-engine
//              to compile it.
//
//              The new project file will replace all the Reference Items with the
//              resolved ReferencePath, add all the generated code file into Compile
//              Item list.
//
//---------------------------------------------------------------------------

using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using System.Globalization;
using System.Diagnostics;
using System.Reflection;
using System.Resources;
using System.Xml;

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

using MS.Utility;
using MS.Internal.Tasks;

// Since we disable PreSharp warnings in this file, PreSharp warning is unknown to C# compiler.
// We first need to disable warnings about unknown message numbers and unknown pragmas.
#pragma warning disable 1634, 1691

namespace Microsoft.Build.Tasks.Windows
{
    #region GenerateTemporaryTargetAssembly Task class

    /// <summary>
    ///   This task is used to generate a temporary target assembly. It generates
    ///   a temporary project file and then compile it.
    ///
    ///   The generated project file is based on current project file, with below
    ///   modification:
    ///
    ///       A:  Add the generated code files (.g.cs) to Compile Item list.
    ///       B:  Replace Reference Item list with ReferenctPath item list.
    ///           So that it doesn't need to rerun time-consuming task
    ///           ResolveAssemblyReference (RAR) again.
    ///
    /// </summary>
    public sealed class GenerateTemporaryTargetAssembly : Task
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------

        #region Constructors

        /// <summary>
        /// Constructor
        /// </summary>
        public GenerateTemporaryTargetAssembly()
            : base(SR.SharedResourceManager)
        {
        }

        #endregion Constructors

        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------

        #region Public Methods

        /// <summary>
        /// ITask Execute method
        /// </summary>
        /// <returns></returns>
        /// <remarks>Catching all exceptions in this method is appropriate - it will allow the build process to resume if possible after logging errors</remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public override bool Execute()
        {
            if (string.Compare(IncludePackageReferencesDuringMarkupCompilation, "false", StringComparison.OrdinalIgnoreCase) != 0)
            {
                return ExecuteGenerateTemporaryTargetAssemblyWithPackageReferenceSupport();
            }
            else
            {
                return ExecuteLegacyGenerateTemporaryTargetAssembly();
            }
        }

        /// <summary>
        /// ExecuteLegacyGenerateTemporaryTargetAssembly
        ///
        /// Creates a project file based on the parent project and compiles a temporary assembly.
        ///
        /// Passes IntermediateOutputPath, AssemblyName, and TemporaryTargetAssemblyName as global properties.
        ///
        /// </summary>
        /// <returns></returns>
        /// <remarks>Catching all exceptions in this method is appropriate - it will allow the build process to resume if possible after logging errors</remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private bool ExecuteLegacyGenerateTemporaryTargetAssembly()
        {
            bool retValue = true;

            // Verification
            try
            {
                XmlDocument xmlProjectDoc = null;

                xmlProjectDoc = new XmlDocument( );
                //Bugfix for GB chars, exception thrown when using Load(CurrentProject), when project name has GB characters in it.
                //Using a filestream instead of using string path to avoid the need to properly compose Uri (which is another way of fixing - but more complicated).
                using(FileStream fs = File.OpenRead(CurrentProject))
                {
                    xmlProjectDoc.Load(fs);
                }
                //
                // remove all the WinFX specific item lists
                // ApplicationDefinition, Page, MarkupResource and Resource
                //

                RemoveItemsByName(xmlProjectDoc, APPDEFNAME);
                RemoveItemsByName(xmlProjectDoc, PAGENAME);
                RemoveItemsByName(xmlProjectDoc, MARKUPRESOURCENAME);
                RemoveItemsByName(xmlProjectDoc, RESOURCENAME);

                // Replace the Reference Item list with ReferencePath

                RemoveItemsByName(xmlProjectDoc, REFERENCETYPENAME);
                AddNewItems(xmlProjectDoc, ReferencePathTypeName, ReferencePath);

                // Add GeneratedCodeFiles to Compile item list.
                AddNewItems(xmlProjectDoc, CompileTypeName, GeneratedCodeFiles);

                string currentProjectName = Path.GetFileNameWithoutExtension(CurrentProject);
                string currentProjectExtension = Path.GetExtension(CurrentProject);

                // Create a random file name
                // This can fix the problem of project cache in VS.NET environment.
                //
                // GetRandomFileName( ) could return any possible file name and extension
                // Since this temporary file will be used to represent an MSBUILD project file,
                // we will use the same extension as that of the current project file
                //
                string randomFileName = Path.GetFileNameWithoutExtension(Path.GetRandomFileName());

                // Don't call Path.ChangeExtension to append currentProjectExtension. It will do
                // odd things with project names that already contains a period (like System.Windows.
                // Contols.Ribbon.csproj). Instead, just append the extension - after all, we already know
                // for a fact that this name (i.e., tempProj) lacks a file extension.
                string tempProjPrefix = string.Join("_", currentProjectName, randomFileName, WPFTMP);
                string tempProj = tempProjPrefix  + currentProjectExtension;


                // Save the xmlDocument content into the temporary project file.
                xmlProjectDoc.Save(tempProj);

                //
                // Invoke MSBUILD engine to build this temporary project file.
                //

                Hashtable globalProperties = new Hashtable(3);

                // Add AssemblyName, IntermediateOutputPath and _TargetAssemblyProjectName to the global property list
                // Note that _TargetAssemblyProjectName is not defined as a property with Output attribute - that doesn't do us much
                // good here. We need _TargetAssemblyProjectName to be a well-known property in the new (temporary) project
                // file, and having it be available in the current MSBUILD process is not useful.
                globalProperties[intermediateOutputPathPropertyName] = IntermediateOutputPath;

                globalProperties[assemblyNamePropertyName] = AssemblyName;
                globalProperties[targetAssemblyProjectNamePropertyName] = currentProjectName;
                globalProperties["EmbedUntrackedSources"] = "false";
                Dictionary<string, ITaskItem[]> targetOutputs = new Dictionary<string, ITaskItem[]>();
                retValue = BuildEngine.BuildProjectFile(tempProj, new string[] { CompileTargetName }, globalProperties, targetOutputs);

                // If the inner build succeeds, retrieve the path to the local type assembly from the task's TargetOutputs.
                if (retValue)
                {
                    // See Microsoft.WinFX.targets: TargetOutputs from '_CompileTemporaryAssembly' will always contain one item.
                    // <Target Name="_CompileTemporaryAssembly"  DependsOnTargets="$(_CompileTemporaryAssemblyDependsOn)" Returns="$(IntermediateOutputPath)$(TargetFileName)"/>
                    Debug.Assert(targetOutputs.ContainsKey(CompileTargetName));
                    Debug.Assert(targetOutputs[CompileTargetName].Length == 1);
                    TemporaryAssemblyForLocalTypeReference = targetOutputs[CompileTargetName][0].ItemSpec;
                }

                // Delete the temporary project file and generated files unless diagnostic mode has been requested
                if (!GenerateTemporaryTargetAssemblyDebuggingInformation)
                {
                    try
                    {
                        File.Delete(tempProj);

                        DirectoryInfo intermediateOutputPath = new DirectoryInfo(IntermediateOutputPath);
                        foreach (FileInfo temporaryProjectFile in intermediateOutputPath.EnumerateFiles($"{tempProjPrefix}*"))
                        {
                            temporaryProjectFile.Delete();
                        }
                    }
                    catch (IOException e)
                    {
                        // Failure to delete the file is a non fatal error
                        Log.LogWarningFromException(e);
                    }
                }
            }
            catch (Exception e)
            {
                Log.LogErrorFromException(e);
                retValue = false;
            }

            return retValue;
        }

        /// <summary>
        /// ExecuteGenerateTemporaryTargetAssemblyWithPackageReferenceSupport
        ///
        /// Creates a project file based on the parent project and compiles a temporary assembly.
        ///
        /// Receives the temporary project name as a parameter and writes properties in to the project file itself.
        ///
        /// No global properties are set.
        ///
        /// </summary>
        /// <returns></returns>
        /// <remarks>Catching all exceptions in this method is appropriate - it will allow the build process to resume if possible after logging errors</remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private bool ExecuteGenerateTemporaryTargetAssemblyWithPackageReferenceSupport()
        {
            bool retValue = true;

            //
            // Create the temporary target assembly project
            //
            try
            {
                XmlDocument xmlProjectDoc = null;

                xmlProjectDoc = new XmlDocument( );
                //Bugfix for GB chars, exception thrown when using Load(CurrentProject), when project name has GB characters in it.
                //Using a filestream instead of using string path to avoid the need to properly compose Uri (which is another way of fixing - but more complicated).
                using(FileStream fs = File.OpenRead(CurrentProject))
                {
                    xmlProjectDoc.Load(fs);
                }
                // remove all the WinFX specific item lists
                // ApplicationDefinition, Page, MarkupResource and Resource
                RemoveItemsByName(xmlProjectDoc, APPDEFNAME);
                RemoveItemsByName(xmlProjectDoc, PAGENAME);
                RemoveItemsByName(xmlProjectDoc, MARKUPRESOURCENAME);
                RemoveItemsByName(xmlProjectDoc, RESOURCENAME);

                // Replace the Reference Item list with ReferencePath
                RemoveItemsByName(xmlProjectDoc, REFERENCETYPENAME);
                AddNewItems(xmlProjectDoc, ReferencePathTypeName, ReferencePath);

                // Add GeneratedCodeFiles to Compile item list.
                AddNewItems(xmlProjectDoc, CompileTypeName, GeneratedCodeFiles);

                // Add Analyzers to Analyzer item list.
                AddNewItems(xmlProjectDoc, AnalyzerTypeName, Analyzers);

                // Replace implicit SDK imports with explicit SDK imports
                ReplaceImplicitImports(xmlProjectDoc);

                // Add properties required for temporary assembly compilation
                var properties = new List<(string PropertyName, string PropertyValue)>
                {
                    ( nameof(AssemblyName), AssemblyName ),
                    ( nameof(IntermediateOutputPath), IntermediateOutputPath ),
                    ( nameof(BaseIntermediateOutputPath), BaseIntermediateOutputPath ),
                    ( nameof(MSBuildProjectExtensionsPath), MSBuildProjectExtensionsPath ),
                    ( "_TargetAssemblyProjectName", Path.GetFileNameWithoutExtension(CurrentProject) ),
                    ( nameof(RootNamespace), RootNamespace ),
                };

                //Removing duplicate AssemblyName
                RemovePropertiesByName(xmlProjectDoc, nameof(AssemblyName));

                AddNewProperties(xmlProjectDoc, properties);

                // Save the xmlDocument content into the temporary project file.
                xmlProjectDoc.Save(TemporaryTargetAssemblyProjectName);

                //
                //  Compile the temporary target assembly project
                //
                Dictionary<string, ITaskItem[]> targetOutputs = new Dictionary<string, ITaskItem[]>();
                retValue = BuildEngine.BuildProjectFile(TemporaryTargetAssemblyProjectName, new string[] { CompileTargetName }, null, targetOutputs);

                // If the inner build succeeds, retrieve the path to the local type assembly from the task's TargetOutputs.
                if (retValue)
                {
                    // See Microsoft.WinFX.targets: TargetOutputs from '_CompileTemporaryAssembly' will always contain one item.
                    // <Target Name="_CompileTemporaryAssembly"  DependsOnTargets="$(_CompileTemporaryAssemblyDependsOn)" Returns="$(IntermediateOutputPath)$(TargetFileName)"/>
                    Debug.Assert(targetOutputs.ContainsKey(CompileTargetName));
                    Debug.Assert(targetOutputs[CompileTargetName].Length == 1);
                    TemporaryAssemblyForLocalTypeReference = targetOutputs[CompileTargetName][0].ItemSpec;
                }

                // Delete the temporary project file and generated files unless diagnostic mode has been requested
                if (!GenerateTemporaryTargetAssemblyDebuggingInformation)
                {
                    try
                    {
                        File.Delete(TemporaryTargetAssemblyProjectName);

                        DirectoryInfo intermediateOutputPath = new DirectoryInfo(IntermediateOutputPath);
                        foreach (FileInfo temporaryProjectFile in intermediateOutputPath.EnumerateFiles($"{Path.GetFileNameWithoutExtension(TemporaryTargetAssemblyProjectName)}*"))
                        {
                            temporaryProjectFile.Delete();
                        }
                    }
                    catch (IOException e)
                    {
                        // Failure to delete the file is a non fatal error
                        Log.LogWarningFromException(e);
                    }
                }

            }
            catch (Exception e)
            {
                Log.LogErrorFromException(e);
                retValue = false;
            }

            return retValue;
        }

        #endregion Public Methods

        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------

        #region Public Properties

        /// <summary>
        /// CurrentProject
        ///    The full path of current project file.
        /// </summary>
        [Required]
        public string  CurrentProject
        {
            get { return _currentProject; }
            set { _currentProject = value; }
        }

        /// <summary>
        /// MSBuild Binary Path.
        ///   This is required for Project to work correctly.
        /// </summary>
        [Required]
        public string MSBuildBinPath
        {
            get { return _msbuildBinPath; }
            set { _msbuildBinPath = value; }
        }

        /// <summary>
        /// GeneratedCodeFiles
        ///    A list of generated code files, it could be empty.
        ///    The list will be added to the Compile item list in new generated project file.
        /// </summary>
        public ITaskItem[] GeneratedCodeFiles
        {
            get { return _generatedCodeFiles; }
            set { _generatedCodeFiles = value; }
        }

        /// <summary>
        /// CompileTypeName
        ///   The appropriate item name which can be accepted by managed compiler task.
        ///   It is "Compile" for now.
        ///
        ///   Adding this property is to make the type name configurable, if it is changed,
        ///   No code is required to change in this task, but set a new type name in project file.
        /// </summary>
        [Required]
        public string CompileTypeName
        {
            get { return _compileTypeName; }
            set { _compileTypeName = value; }
        }


        /// <summary>
        /// ReferencePath
        ///    A list of resolved reference assemblies.
        ///    The list will replace the original Reference item list in generated project file.
        /// </summary>
        public ITaskItem[] ReferencePath
        {
            get { return _referencePath; }
            set { _referencePath = value; }
        }

        /// <summary>
        /// ReferencePathTypeName
        ///   The appropriate item name which is used to keep the Reference list in managed compiler task.
        ///   It is "ReferencePath" for now.
        ///
        ///   Adding this property is to make the type name configurable, if it is changed,
        ///   No code is required to change in this task, but set a new type name in project file.
        /// </summary>
        [Required]
        public string ReferencePathTypeName
        {
            get { return _referencePathTypeName; }
            set { _referencePathTypeName = value; }
        }


        /// <summary>
        /// IntermediateOutputPath
        ///
        /// The value which is set to IntermediateOutputPath property in current project file.
        ///
        /// Passing this value explicitly is to make sure to generate temporary target assembly
        /// in expected directory.
        /// </summary>
        [Required]
        public string IntermediateOutputPath
        {
            get { return _intermediateOutputPath; }
            set { _intermediateOutputPath = value; }
        }

        /// <summary>
        /// AssemblyName
        ///
        /// The value which is set to AssemblyName property in current project file.
        /// Passing this value explicitly is to make sure to generate the expected
        /// temporary target assembly.
        ///
        /// </summary>
        [Required]
        public string AssemblyName
        {
            get { return _assemblyName; }
            set { _assemblyName = value; }
        }

        /// <summary>
        /// CompileTargetName
        ///
        /// The msbuild target name which is used to generate assembly from source code files.
        /// Usually it is "CoreCompile"
        ///
        /// </summary>
        [Required]
        public string CompileTargetName
        {
            get { return _compileTargetName; }
            set { _compileTargetName = value; }
        }

        /// <summary>
        /// Optional <see cref="Boolean"/> task parameter
        ///
        /// When <code>true</code>, debugging information is enabled for the <see cref="GenerateTemporaryTargetAssembly"/>
        /// Task. At this time, the only debugging information that is generated consists of the temporary project that is
        /// created to generate the temporary target assembly. This temporary project is normally deleted at the end of this
        /// MSBUILD task; when <see cref="GenerateTemporaryTargetAssemblyDebuggingInformation"/> is enable, this temporary project
        /// will be retained for inspection by the developer.
        ///
        /// This is a diagnostic parameter, and it defaults to <code>false</code>.
        /// </summary>
        public bool GenerateTemporaryTargetAssemblyDebuggingInformation
        {
            get { return _generateTemporaryTargetAssemblyDebuggingInformation; }
            set { _generateTemporaryTargetAssemblyDebuggingInformation = value; }
        }

        /// <summary>
        /// Analyzers
        ///
        /// Required for Source Generator support. May be null.
        ///
        /// </summary>
        public ITaskItem[] Analyzers
        { get; set; }

        /// <summary>
        /// AnalyzerTypeName
        ///   The appropriate item name which can be accepted by managed compiler task.
        ///   It is "Analyzer" for now.
        ///
        ///   Adding this property is to make the type name configurable, if it is changed,
        ///   No code is required to change in this task, but set a new type name in project file.
        /// </summary>
        [Required]
        public string AnalyzerTypeName { get; set; }

        /// <summary>
        /// RootNamespace
        ///
        /// Required for Source Generator support. May be null.
        ///
        /// </summary>
        public string RootNamespace { get; set; }

        /// <summary>
        /// BaseIntermediateOutputPath
        ///
        /// Required for Source Generator support. May be null.
        ///
        /// </summary>
        public string BaseIntermediateOutputPath
        {
            get; set;
        }

        /// <summary>
        /// IncludePackageReferencesDuringMarkupCompilation
        ///
        /// Required for Source Generator support. May be null.
        ///
        /// Set this property to 'false' to use the .NET Core 3.0 behavior for this task.
        ///
        /// </summary>
        public string IncludePackageReferencesDuringMarkupCompilation
        { get; set; }

        /// <summary>
        /// TemporaryTargetAssemblyProjectName
        ///
        /// Required for PackageReference support.
        ///
        /// This property may be null if 'IncludePackageReferencesDuringMarkupCompilation' is 'false'.
        ///
        /// The file name with extension of the randomly generated project name for the temporary assembly
        ///
        /// </summary>
        public string TemporaryTargetAssemblyProjectName
        { get; set; }

        /// <summary>
        ///
        /// MSBuildProjectExtensionsPath
        ///
        /// Required for PackageReference support.
        ///
        /// MSBuildProjectExtensionsPath may be overridden and must be passed into the temporary project.
        ///
        /// This is required for some VS publishing scenarios.
        ///
        /// </summary>
        public string MSBuildProjectExtensionsPath
        { get; set; }

        /// <summary>
        ///
        /// TemporaryAssemblyForLocalTypeReference
        ///
        /// The path of the generated temporary local type assembly.
        ///
        /// </summary>
        [Output]
        public string TemporaryAssemblyForLocalTypeReference
        { get; set; }

        #endregion Public Properties

        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------

        #region Private Methods

        //
        // Remove specific entity from project file.
        //
        private void RemoveEntityByName(XmlDocument xmlProjectDoc, string sItemName, string groupName)
        {

            if (xmlProjectDoc == null || String.IsNullOrEmpty(sItemName))
            {
                // When the parameters are not valid, simply return it, instead of throwing exceptions.
                return;
            }

            //
            // The project file format is always like below:
            //
            //  <Project  xmlns="...">
            //     <ProjectGroup>
            //         ......
            //     </ProjectGroup>
            //
            //     ...
            //     <ItemGroup>
            //         <ItemNameHere ..../>
            //         ....
            //     </ItemGroup>
            //
            //     ....
            //     <Import ... />
            //     ...
            //     <Target Name="xxx" ..../>
            //
            //      ...
            //
            //  </Project>
            //
            //
            // The order of children nodes under Project Root element is random
            //

            XmlNode root = xmlProjectDoc.DocumentElement;

            if (root.HasChildNodes == false)
            {
                // If there is no child element in this project file, just return immediatelly.
                return;
            }

            for (int i = 0; i < root.ChildNodes.Count; i++)
            {
                XmlElement nodeGroup = root.ChildNodes[i] as XmlElement;

                if (nodeGroup != null && String.Compare(nodeGroup.Name, groupName, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    //
                    // This is ItemGroup element.
                    //
                    if (nodeGroup.HasChildNodes)
                    {
                        ArrayList itemToRemove = new ArrayList();

                        for (int j = 0; j < nodeGroup.ChildNodes.Count; j++)
                        {
                            XmlElement nodeItem = nodeGroup.ChildNodes[j] as XmlElement;

                            if (nodeItem != null && String.Compare(nodeItem.Name, sItemName, StringComparison.OrdinalIgnoreCase) == 0)
                            {
                                // This is the item that need to remove.
                                // Add it into the temporary array list.
                                // Don't delete it here, since it would affect the ChildNodes of parent element.
                                //
                                itemToRemove.Add(nodeItem);
                            }
                        }

                        //
                        // Now it is the right time to delete the elements.
                        //
                        if (itemToRemove.Count > 0)
                        {
                            foreach (object node in itemToRemove)
                            {
                                XmlElement item = node as XmlElement;

                                //
                                // Remove this item from its parent node.
                                // the parent node should be nodeGroup.
                                //
                                if (item != null)
                                {
                                    nodeGroup.RemoveChild(item);
                                }
                            }
                        }
                    }

                    //
                    // Removed all the items with given name from this item group.
                    //
                    // Continue the loop for the next ItemGroup.
                }

            }   // end of "for i" statement.
        }

        //
        // Remove specific items from project file. Every item should be under an ItemGroup.
        //
        private void RemoveItemsByName(XmlDocument xmlProjectDoc, string sItemName)
        {
            RemoveEntityByName(xmlProjectDoc, sItemName, ITEMGROUP_NAME);
        }

        //
        // Remove specific property from project file. Every property should be under an PropertyGroup.
        //
        private void RemovePropertiesByName(XmlDocument xmlProjectDoc, string sPropertyName)
        {
            RemoveEntityByName(xmlProjectDoc, sPropertyName, PROPERTYGROUP_NAME);
        }

        //
        // Add a list of files into an Item in the project file, the ItemName is specified by sItemName.
        //
        private void AddNewItems(XmlDocument xmlProjectDoc, string sItemName, ITaskItem[] pItemList)
        {
            if (xmlProjectDoc == null || String.IsNullOrEmpty(sItemName) || pItemList == null)
            {
                // When the parameters are not valid, simply return it, instead of throwing exceptions.
                return;
            }

            XmlNode root = xmlProjectDoc.DocumentElement;

            // Create a new ItemGroup element
            XmlNode nodeItemGroup = xmlProjectDoc.CreateElement(ITEMGROUP_NAME, root.NamespaceURI);

            // Append this new ItemGroup item into the list of children of the document root.
            root.AppendChild(nodeItemGroup);

            XmlElement embedItem = null;

            for (int i = 0; i < pItemList.Length; i++)
            {
                // Create an element for the given sItemName
                XmlElement nodeItem = xmlProjectDoc.CreateElement(sItemName, root.NamespaceURI);

                // Create an Attribute "Include"
                XmlAttribute attrInclude = xmlProjectDoc.CreateAttribute(INCLUDE_ATTR_NAME);

                ITaskItem pItem = pItemList[i];

                // Set the value for Include attribute.
                attrInclude.Value = pItem.ItemSpec;

                // Add the attribute to current item node.
                nodeItem.SetAttributeNode(attrInclude);

                string embedInteropTypesMetadata = pItem.GetMetadata(EMBEDINTEROPTYPES);
                if (!String.IsNullOrEmpty(embedInteropTypesMetadata))
                {
                    embedItem = xmlProjectDoc.CreateElement(EMBEDINTEROPTYPES, root.NamespaceURI);
                    embedItem.InnerText = embedInteropTypesMetadata;
                    nodeItem.AppendChild(embedItem);
                }

                string aliases = pItem.GetMetadata(ALIASES);
                if (!String.IsNullOrEmpty(aliases))
                {
                    embedItem = xmlProjectDoc.CreateElement(ALIASES, root.NamespaceURI);
                    embedItem.InnerText = aliases;
                    nodeItem.AppendChild(embedItem);
                }

                // Add current item node into the children list of ItemGroup
                nodeItemGroup.AppendChild(nodeItem);
            }
        }

        private void AddNewProperties(XmlDocument xmlProjectDoc, List<(string PropertyName, string PropertyValue)> properties )
        {
            if (xmlProjectDoc == null || properties == null )
            {
                // When the parameters are not valid, simply return it, instead of throwing exceptions.
                return;
            }

            XmlNode root = xmlProjectDoc.DocumentElement;

            // Create a new PropertyGroup element
            XmlNode nodeItemGroup = xmlProjectDoc.CreateElement("PropertyGroup", root.NamespaceURI);
            root.PrependChild(nodeItemGroup);

            // Append this new ItemGroup item into the list of children of the document root.
            foreach(var property in properties)
            {
                // Skip empty properties
                if (!string.IsNullOrEmpty(property.PropertyValue))
                {
                    // Create an element for the given propertyName
                    XmlElement nodeItem = xmlProjectDoc.CreateElement(property.PropertyName, root.NamespaceURI);
                    nodeItem.InnerText = property.PropertyValue;

                    // Add current item node into the PropertyGroup
                    nodeItemGroup.AppendChild(nodeItem);
                }
            }
        }

        //
        // Replace implicit SDK imports with explicit imports
        //
        private static void ReplaceImplicitImports(XmlDocument xmlProjectDoc)
        {
            if (xmlProjectDoc == null)
            {
                // When the parameters are not valid, simply return it, instead of throwing exceptions.
                return;
            }

            XmlNode root = xmlProjectDoc.DocumentElement;

            for (int i = 0; i < root.Attributes.Count; i++)
            {
                XmlAttribute xmlAttribute = root.Attributes[i];

                if (xmlAttribute.Name.Equals("Sdk", StringComparison.OrdinalIgnoreCase))
                {
                    string sdks = xmlAttribute.Value;

                    bool removedSdkAttribute = false;
                    XmlNode previousNodeImportProps = null;
                    XmlNode previousNodeImportTargets = null;

                    foreach (string sdk in sdks.Split(s_semicolonChar).Select(i => i.Trim()))
                    {
                        //  <Project Sdk="Microsoft.NET.Sdk">
                        //  <Project Sdk="My.Custom.Sdk/1.0.0">
                        //  <Project Sdk="My.Custom.Sdk/min=1.0.0">
                        if (!SdkReference.TryParse(sdk, out SdkReference sdkReference))
                            break;

                        // Remove Sdk attribute
                        if (!removedSdkAttribute)
                        {
                            root.Attributes.Remove(xmlAttribute);

                            removedSdkAttribute = true;
                        }

                        //
                        // Add explicit top import
                        //
                        //  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
                        //  <Import Project="Sdk.props" Sdk="My.Custom.Sdk" Version="1.0.0" />
                        //  <Import Project="Sdk.props" Sdk="My.Custom.Sdk" MinimumVersion="1.0.0" />
                        //
                        XmlNode nodeImportProps = CreateImportProjectSdkNode(xmlProjectDoc, "Sdk.props", sdkReference);

                        // Prepend this Import to the root of the XML document
                        if (previousNodeImportProps == null)
                        {
                            previousNodeImportProps = root.PrependChild(nodeImportProps);
                        }
                        else
                        {
                            previousNodeImportProps = root.InsertAfter(nodeImportProps, previousNodeImportProps);
                        }

                        //
                        // Add explicit bottom import
                        //
                        //  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
                        //  <Import Project="Sdk.targets" Sdk="My.Custom.Sdk" Version="1.0.0" />
                        //  <Import Project="Sdk.targets" Sdk="My.Custom.Sdk" MinimumVersion="1.0.0" />
                        //
                        XmlNode nodeImportTargets = CreateImportProjectSdkNode(xmlProjectDoc, "Sdk.targets", sdkReference);

                        // Append this Import to the end of the XML document
                        if (previousNodeImportTargets == null)
                        {
                            previousNodeImportTargets = root.AppendChild(nodeImportTargets);
                        }
                        else
                        {
                            previousNodeImportTargets = root.InsertAfter(nodeImportTargets, previousNodeImportTargets);
                        }
                    }
                }
            }
        }

        // Creates an XmlNode that contains an Import Project element
        //
        //  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
        static XmlNode CreateImportProjectSdkNode(XmlDocument xmlProjectDoc, string projectAttributeValue, SdkReference sdkReference)
        {
            XmlNode nodeImport = xmlProjectDoc.CreateElement("Import", xmlProjectDoc.DocumentElement.NamespaceURI);
            XmlAttribute projectAttribute = xmlProjectDoc.CreateAttribute("Project");
            projectAttribute.Value = projectAttributeValue;
            XmlAttribute sdkAttributeProps = xmlProjectDoc.CreateAttribute("Sdk");
            sdkAttributeProps.Value = sdkReference.Name;
            nodeImport.Attributes.Append(projectAttribute);
            nodeImport.Attributes.Append(sdkAttributeProps);

            if (!string.IsNullOrEmpty(sdkReference.Version))
            {
                XmlAttribute sdkVersionAttributeProps = xmlProjectDoc.CreateAttribute("Version");
                sdkVersionAttributeProps.Value = sdkReference.Version;
                nodeImport.Attributes.Append(sdkVersionAttributeProps);
            }

            if (!string.IsNullOrEmpty(sdkReference.MinimumVersion))
            {
                XmlAttribute sdkVersionAttributeProps = xmlProjectDoc.CreateAttribute("MinimumVersion");
                sdkVersionAttributeProps.Value = sdkReference.MinimumVersion;
                nodeImport.Attributes.Append(sdkVersionAttributeProps);
            }

            return nodeImport;
        }

        #endregion Private Methods


        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------

        #region Private Fields

        private string _currentProject = String.Empty;

        private ITaskItem[] _generatedCodeFiles;
        private ITaskItem[] _referencePath;

        private string _referencePathTypeName;
        private string _compileTypeName;

        private string _msbuildBinPath;

        private string  _intermediateOutputPath;
        private string  _assemblyName;
        private string  _compileTargetName;
        private bool _generateTemporaryTargetAssemblyDebuggingInformation = false;

        private const string intermediateOutputPathPropertyName = "IntermediateOutputPath";
        private const string assemblyNamePropertyName = "AssemblyName";
        private const string targetAssemblyProjectNamePropertyName = "_TargetAssemblyProjectName";

        private const string ALIASES = "Aliases";
        private const string REFERENCETYPENAME = "Reference";
        private const string EMBEDINTEROPTYPES = "EmbedInteropTypes";
        private const string APPDEFNAME = "ApplicationDefinition";
        private const string PAGENAME = "Page";
        private const string MARKUPRESOURCENAME = "MarkupResource";
        private const string RESOURCENAME = "Resource";

        private const string ITEMGROUP_NAME = "ItemGroup";
        private const string PROPERTYGROUP_NAME = "PropertyGroup";
        private const string INCLUDE_ATTR_NAME = "Include";

        private const string WPFTMP = "wpftmp";

        private static readonly char[] s_semicolonChar = [';'];

        #endregion Private Fields

    }

    #endregion GenerateProjectForLocalTypeReference Task class
}


