// 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.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis
{
    /// <summary>
    /// Represents subsystem version, see /subsystemversion command line 
    /// option for details and valid values.
    /// 
    /// The following table lists common subsystem versions of Windows.
    /// 
    /// Windows version             Subsystem version
    ///   - Windows 2000                5.00
    ///   - Windows XP                  5.01
    ///   - Windows Vista               6.00
    ///   - Windows 7                   6.01
    ///   - Windows 8 Release Preview   6.02
    /// </summary>
    public struct SubsystemVersion : IEquatable<SubsystemVersion>
    {
        /// <summary>
        /// Major subsystem version
        /// </summary>
        public int Major { get; }

        /// <summary>
        /// Minor subsystem version
        /// </summary>
        public int Minor { get; }

        /// <summary>
        /// Subsystem version not specified
        /// </summary>
        public static SubsystemVersion None => new SubsystemVersion();

        /// <summary>
        /// Subsystem version: Windows 2000
        /// </summary>
        public static SubsystemVersion Windows2000 => new SubsystemVersion(5, 0);

        /// <summary>
        /// Subsystem version: Windows XP 
        /// </summary>
        public static SubsystemVersion WindowsXP => new SubsystemVersion(5, 1);

        /// <summary>
        /// Subsystem version: Windows Vista
        /// </summary>
        public static SubsystemVersion WindowsVista => new SubsystemVersion(6, 0);

        /// <summary>
        /// Subsystem version: Windows 7
        /// </summary>
        public static SubsystemVersion Windows7 => new SubsystemVersion(6, 1);

        /// <summary>
        /// Subsystem version: Windows 8
        /// </summary>
        public static SubsystemVersion Windows8 => new SubsystemVersion(6, 2);

        private SubsystemVersion(int major, int minor)
        {
            this.Major = major;
            this.Minor = minor;
        }

        /// <summary>
        /// Try parse subsystem version in "x.y" format. Note, no spaces are allowed in string representation.
        /// </summary>
        /// <param name="str">String to parse</param>
        /// <param name="version">the value if successfully parsed or None otherwise</param>
        /// <returns>true if parsed successfully, false otherwise</returns>
        public static bool TryParse(string str, out SubsystemVersion version)
        {
            version = SubsystemVersion.None;
            if (!string.IsNullOrWhiteSpace(str))
            {
                string major;
                string? minor;

                int index = str.IndexOf('.');

                //found a dot
                if (index >= 0)
                {
                    //if there's a dot and no following digits, it's an error in the native compiler.
                    if (str.Length == index + 1)
                        return false;

                    major = str.Substring(0, index);
                    minor = str.Substring(index + 1);
                }
                else
                {
                    major = str;
                    minor = null;
                }

                int majorValue;

                if (major != major.Trim() ||
                    !int.TryParse(major, NumberStyles.None, CultureInfo.InvariantCulture, out majorValue) ||
                    majorValue >= 65356 || majorValue < 0)
                {
                    return false;
                }

                int minorValue = 0;

                //it's fine to have just a single number specified for the subsystem.

                if (minor != null)
                {
                    if (minor != minor.Trim() ||
                        !int.TryParse(minor, NumberStyles.None, CultureInfo.InvariantCulture, out minorValue) ||
                        minorValue >= 65356 || minorValue < 0)
                    {
                        return false;
                    }
                }

                version = new SubsystemVersion(majorValue, minorValue);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Create a new instance of subsystem version with specified major and minor values.
        /// </summary>
        /// <param name="major">major subsystem version</param>
        /// <param name="minor">minor subsystem version</param>
        /// <returns>subsystem version with provided major and minor</returns>
        public static SubsystemVersion Create(int major, int minor)
        {
            return new SubsystemVersion(major, minor);
        }

        /// <summary>
        /// Subsystem version default for the specified output kind and platform combination
        /// </summary>
        /// <param name="outputKind">Output kind</param>
        /// <param name="platform">Platform</param>
        /// <returns>Subsystem version</returns>
        internal static SubsystemVersion Default(OutputKind outputKind, Platform platform)
        {
            if (platform == Platform.Arm)
                return Windows8;

            switch (outputKind)
            {
                case OutputKind.ConsoleApplication:
                case OutputKind.DynamicallyLinkedLibrary:
                case OutputKind.NetModule:
                case OutputKind.WindowsApplication:
                    return new SubsystemVersion(4, 0);
                case OutputKind.WindowsRuntimeApplication:
                case OutputKind.WindowsRuntimeMetadata:
                    return Windows8;

                default:
                    throw new ArgumentOutOfRangeException(CodeAnalysisResources.OutputKindNotSupported, "outputKind");
            }
        }

        /// <summary>
        /// True if the subsystem version has a valid value
        /// </summary>
        public bool IsValid
        {
            get
            {
                return this.Major >= 0 &&
                       this.Minor >= 0 &&
                       this.Major < 65536 && this.Minor < 65536;
            }
        }

        public override bool Equals(object? obj)
        {
            return obj is SubsystemVersion && Equals((SubsystemVersion)obj);
        }

        public override int GetHashCode()
        {
            return Hash.Combine(this.Minor.GetHashCode(), this.Major.GetHashCode());
        }

        public bool Equals(SubsystemVersion other)
        {
            return this.Major == other.Major && this.Minor == other.Minor;
        }

        public override string ToString()
        {
            return string.Format("{0}.{1:00}", this.Major, this.Minor);
        }
    }
}
