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

#if !WINDOWS_UWP
using Microsoft.VisualStudio.TestPlatform.MSTestAdapter.PlatformServices.Interface;
#endif

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Microsoft.VisualStudio.TestPlatform.MSTest.TestAdapter;

/// <summary>
/// The run configuration settings.
/// </summary>
internal sealed class RunConfigurationSettings
{
    /// <summary>
    /// The settings name.
    /// </summary>
    public const string SettingsName = "RunConfiguration";

    /// <summary>
    /// Gets a value indicating the requested platform apartment state.
    /// </summary>
    internal ApartmentState? ExecutionApartmentState { get; private set; }

    /// <summary>
    /// Gets the configuration settings from the xml.
    /// </summary>
    /// <param name="runSettingsXml"> The xml with the settings passed from the test platform. </param>
    public static RunConfigurationSettings GetSettings(string? runSettingsXml)
    {
        if (StringEx.IsNullOrEmpty(runSettingsXml))
        {
            // This will contain default configuration settings
            return new RunConfigurationSettings();
        }

        using var stringReader = new StringReader(runSettingsXml);
        var reader = XmlReader.Create(stringReader, new() { IgnoreComments = true, IgnoreWhitespace = true, DtdProcessing = DtdProcessing.Prohibit });

        // read to the fist child
        ReadToRootNode(reader);
        ReadToNextElement(reader);

        // Read till we reach nodeName element or reach EOF
        while (!string.Equals(reader.Name, SettingsName, StringComparison.OrdinalIgnoreCase)
                && !reader.EOF)
        {
            SkipToNextElement(reader);
        }

        if (!reader.EOF)
        {
            // read nodeName element.
            return ToSettings(reader.ReadSubtree());
        }

        return new RunConfigurationSettings();
    }

    /// <summary>
    /// Convert the parameter xml to TestSettings.
    /// </summary>
    /// <param name="reader">Reader to load the settings from.</param>
    /// <returns>An instance of the <see cref="MSTestSettings"/> class.</returns>
    private static RunConfigurationSettings ToSettings(XmlReader reader)
    {
        if (reader is null)
        {
            throw new ArgumentNullException(nameof(reader));
        }

        // Expected format of the xml is: -
        //
        // <Runsettings>
        // <RunConfiguration>
        // <CollectSourceInformation>true</CollectSourceInformation>
        // <ExecutionApartmentState>STA/MTA</ExecutionApartmentState>
        // </RunConfiguration>
        // </Runsettings>
        RunConfigurationSettings settings = new();

        // Read the first element in the section
        ReadToNextElement(reader);

        if (!reader.IsEmptyElement)
        {
            reader.Read();

            while (reader.NodeType == XmlNodeType.Element)
            {
                string elementName = reader.Name.ToUpperInvariant();
                switch (elementName)
                {
                    case "EXECUTIONTHREADAPARTMENTSTATE":
                        {
                            if (Enum.TryParse(reader.ReadInnerXml(), out ApartmentStateSetting apartmentStateSetting))
                            {
                                settings.ExecutionApartmentState = apartmentStateSetting switch
                                {
                                    ApartmentStateSetting.STA => ApartmentState.STA,
                                    ApartmentStateSetting.MTA => ApartmentState.MTA,
                                    _ => throw new NotSupportedException($"Platform apartment state '{apartmentStateSetting}' is not supported."),
                                };
                            }

                            break;
                        }

                    default:
                        {
                            SkipToNextElement(reader);
                            break;
                        }
                }
            }
        }

        return settings;
    }

#if !WINDOWS_UWP
    internal static RunConfigurationSettings SetRunConfigurationSettingsFromConfig(IConfiguration configuration, RunConfigurationSettings settings)
    {
        // Expected format of the json is: -
        // "mstest" : {
        //  "execution": {
        //    "executionApartmentState": "STA"
        //  }
        // }
        string? apartmentStateValue = configuration["mstest:execution:executionApartmentState"];
        if (Enum.TryParse(apartmentStateValue, out ApartmentStateSetting apartmentStateSetting))
        {
            settings.ExecutionApartmentState = apartmentStateSetting switch
            {
                ApartmentStateSetting.STA => ApartmentState.STA,
                ApartmentStateSetting.MTA => ApartmentState.MTA,
                _ => throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, Resource.InvalidValue, apartmentStateSetting, "execution:executionApartmentState")),
            };
        }

        return settings;
    }
#endif

    private static void ReadToRootNode(XmlReader reader)
    {
        ReadToNextElement(reader);

        // Verify that it is a "RunSettings" node.
        if (reader.Name != "RunSettings")
        {
            throw new FormatException($"The runsettings XML document has an invalid root element '<{reader.Name}>'. Expected root element is '<RunSettings>'.");
        }
    }

    private static void ReadToNextElement(XmlReader reader)
    {
        while (!reader.EOF && reader.Read() && reader.NodeType != XmlNodeType.Element)
        {
        }
    }

    private static void SkipToNextElement(XmlReader reader)
    {
        reader.Skip();

        if (reader.NodeType != XmlNodeType.Element)
        {
            ReadToNextElement(reader);
        }
    }
}
