// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Xml;

using Microsoft.Build.Shared;

#nullable disable

namespace Microsoft.Build.Tasks
{
    /// <summary>
    /// Read information from application .config files.
    /// </summary>
    internal sealed class AppConfig
    {
        /// <summary>
        /// Read the .config from a file.
        /// </summary>
        /// <param name="appConfigFilePath"></param>
        internal void Load(string appConfigFilePath)
        {
            XmlReader reader = null;
            try
            {
                var readerSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, CloseInput = true };

                // it's important to normalize the path as it may contain two slashes
                // see https://github.com/dotnet/msbuild/issues/4335 for details.
                appConfigFilePath = FileUtilities.NormalizePath(appConfigFilePath);

                // Need a filestream as the XmlReader doesn't support nonstandard unicode characters in path.
                // No need to dispose - as 'CloseInput' was passed to XmlReaderSettings
                FileStream fs = File.OpenRead(appConfigFilePath);
#pragma warning disable CA2000 // Dispose objects before losing scope is suppressed because the reader is disposed in the finally block
                reader = XmlReader.Create(fs, readerSettings);
#pragma warning restore CA2000 // Dispose objects before losing scope
                Read(reader);
            }
            catch (XmlException e)
            {
                int lineNumber = 0;
                int linePosition = 0;

                if (reader is IXmlLineInfo info)
                {
                    lineNumber = info.LineNumber;
                    linePosition = info.LinePosition;
                }

                throw new AppConfigException(e.Message, appConfigFilePath, lineNumber, linePosition, e);
            }
            catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
            {
                int lineNumber = 0;
                int linePosition = 0;

                if (reader is IXmlLineInfo info)
                {
                    lineNumber = info.LineNumber;
                    linePosition = info.LinePosition;
                }

                throw new AppConfigException(e.Message, appConfigFilePath, lineNumber, linePosition, e);
            }
            finally
            {
                reader?.Dispose();
            }
        }

        /// <summary>
        /// Read the .config from an XmlReader
        /// </summary>
        /// <param name="reader"></param>
        internal void Read(XmlReader reader)
        {
            // Read the app.config XML
            while (reader.Read())
            {
                // Look for the <runtime> section
                if (reader.NodeType == XmlNodeType.Element && StringEquals(reader.Name, "runtime"))
                {
                    Runtime.Read(reader);
                }
            }
        }

        /// <summary>
        /// Access the Runtime section of the application .config file.
        /// </summary>
        internal RuntimeSection Runtime { get; } = new RuntimeSection();

        /// <summary>
        /// App.config files seem to come with mixed casing for element and attribute names.
        /// If the fusion loader can handle this then this code should too.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        internal static bool StringEquals(string a, string b)
        {
            return String.Equals(a, b, StringComparison.OrdinalIgnoreCase);
        }
    }
}
