// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

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

namespace HoloToolkit.Unity
{
    /// <summary>
    /// This class is designed to post process the UWP Assembly-CSharp projects to ensure that the defaults and defines are set correctly.
    /// </summary>
    public static class UwpProjectPostProcess
    {
        private static readonly Regex PlatformRegex = new Regex(@"'\$\(Configuration\)\|\$\(Platform\)' == '(?<Configuration>.*)\|(?<Platform>.*)'");

        /// <summary>
        /// Executes the Post Processes on the C# Projects generated as part of the UWP build.
        /// </summary>
        /// <param name="buildRootPath">The root path of the UWP build output.</param>
        public static void Execute(string buildRootPath)
        {
            UpdateProjectFile(Path.Combine(buildRootPath, @"GeneratedProjects\UWP\Assembly-CSharp\Assembly-CSharp.csproj"));
            UpdateProjectFile(Path.Combine(buildRootPath, @"GeneratedProjects\UWP\Assembly-CSharp-firstpass\Assembly-CSharp-firstpass.csproj"));
        }

        /// <summary>
        /// Updates the project file to ensure that certain requirements are met.
        /// </summary>
        /// <param name="filename">The filename of the project to update.</param>
        /// <remarks>This is manually parsing the Unity generated MSBuild projects, which means it will be fragile to changes.</remarks>
        private static void UpdateProjectFile(string filename)
        {
            if (!File.Exists(filename))
            {
                UnityEngine.Debug.LogWarningFormat("Unable to find file \"{0}\", double check that the build succeeded and that the C# Projects are set to be generated.", filename);
                return;
            }

            var projectDocument = new XmlDocument();
            projectDocument.Load(filename);

            if (projectDocument.DocumentElement == null)
            {
                UnityEngine.Debug.LogWarningFormat("Unable to load file \"{0}\", double check that the build succeeded and that the C# Projects are set to be generated.", filename);
                return;
            }

            if (projectDocument.DocumentElement.Name != "Project")
            {
                UnityEngine.Debug.LogWarningFormat("The loaded project \"{0}\", does not appear to be a MSBuild Project file.", filename);
                return;
            }

            foreach (XmlNode node in projectDocument.DocumentElement.ChildNodes)
            {
                // Everything we are looking for is inside a PropertyGroup...
                if (node.Name != "PropertyGroup" || node.Attributes == null)
                {
                    continue;
                }

                if (node.Attributes.Count == 0 && node["Configuration"] != null && node["Platform"] != null)
                {
                    // Update the defaults to Release and x86 so that we can run NuGet restore.
                    node["Configuration"].InnerText = "Release";
                    node["Platform"].InnerText = "x86";
                }
                else if (node.Attributes["Condition"] != null)
                {
                    // Update the DefineConstants to include the configuration allowing us to conditionally compile code based on the configuration.
                    Match match = PlatformRegex.Match(node.Attributes["Condition"].InnerText);

                    if (match.Success)
                    {
                        UpdateDefineConstants(node["DefineConstants"], match.Groups["Configuration"].Value, match.Groups["Platform"].Value);
                    }
                }
            }

            WriteXmlDocumentToFile(projectDocument, filename);
        }

        private static void UpdateDefineConstants(XmlNode defineConstants, string configuration, string platform)
        {
            if (defineConstants == null)
            {
                return;
            }

            IEnumerable<string> symbols = defineConstants.InnerText.Split(';').Except(new[]
            {
                string.Empty,
                BuildSLNUtilities.BuildSymbolDebug,
                BuildSLNUtilities.BuildSymbolRelease,
                BuildSLNUtilities.BuildSymbolMaster
            }).Union(new[] { configuration.ToUpperInvariant() });

            defineConstants.InnerText = string.Join(";", symbols.ToArray());
            //UnityEngine.Debug.LogFormat("Updating defines for Configuration|Platform: {0}|{1} => {2}", configuration, platform, defineConstants.InnerText);
        }

        private static void WriteXmlDocumentToFile(XmlNode document, string fullPath)
        {
            FileStream fileStream = null;
            try
            {
                fileStream = File.Open(fullPath, FileMode.Create);

                var settings = new XmlWriterSettings
                {
                    Indent = true,
                    CloseOutput = true
                };

                using (XmlWriter writer = XmlWriter.Create(fileStream, settings))
                {
                    fileStream = null;
                    document.WriteTo(writer);
                }
            }
            finally
            {
                if (fileStream != null)
                {
                    fileStream.Dispose();
                }
            }
        }
    }
}
