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

using System.ComponentModel;
using System.Globalization;
using System.Windows.Forms.Analyzers.CSharp.Resources;
using static System.Windows.Forms.Analyzers.ApplicationConfig;

namespace System.Windows.Forms.CSharp.Generators.ApplicationConfiguration;

internal static partial class ProjectFileReader
{
    // Copied from https://github.com/dotnet/runtime/blob/00ee1c18715723e62484c9bc8a14f517455fc3b3/src/libraries/System.Drawing.Common/src/System/Drawing/FontConverter.cs
    internal static class FontConverter
    {
        private const string StylePrefix = "style=";
        private static readonly CultureInfo s_culture = CultureInfo.InvariantCulture;

        public static FontDescriptor? ConvertFrom(string font)
        {
            font = font!.Trim();

            // Expected string format: "name[, size[, units[, style=style1[, style2[...]]]]]"
            // Example using 'vi-VN' culture: "Microsoft Sans Serif, 8,25pt, style=Italic, Bold"
            if (font.Length == 0)
            {
                return null;
            }

            char separator = s_culture.TextInfo.ListSeparator[0]; // For vi-VN: ','
            string fontName = font; // start with the assumption that only the font name was provided.
            string? style = null;
            string? sizeStr;
            float fontSize = PropertyDefaultValue.FontSize;
            FontStyle fontStyle = FontStyle.Regular;
            GraphicsUnit units = GraphicsUnit.Point;

            // Get the index of the first separator (would indicate the end of the name in the string).
            int nameIndex = font.IndexOf(separator);

            if (nameIndex < 0)
            {
                return new FontDescriptor(fontName, fontSize, fontStyle, units);
            }

            // Some parameters are provided in addition to name.
            fontName = font.Substring(0, nameIndex);

            if (nameIndex < font.Length - 1)
            {
                // Get the style index (if any). The size is a bit problematic because it can be formatted differently
                // depending on the culture, we'll parse it last.
                int styleIndex = s_culture.CompareInfo.IndexOf(font, StylePrefix, CompareOptions.IgnoreCase);

                if (styleIndex != -1)
                {
                    // style found.
                    style = font.Substring(styleIndex);

                    // Get the mid-substring containing the size information.
                    sizeStr = font.Substring(nameIndex + 1, styleIndex - nameIndex - 1);
                }
                else
                {
                    // no style.
                    sizeStr = font.Substring(nameIndex + 1);
                }

                // Parse size.
                (string? size, string? unit) unitTokens = ParseSizeTokens(sizeStr, separator);

                if (unitTokens.size is not null)
                {
                    try
                    {
                        fontSize = (float)TypeDescriptor.GetConverter(typeof(float)).ConvertFromString(null, s_culture, unitTokens.size);
                    }
                    catch
                    {
                        // Exception from converter is too generic.
                        throw new ArgumentException(string.Format(SR.TextParseFailedFormat,
                            font,
                            $"name{separator} size[units[{separator} style=style1[{separator} style2{separator} ...]]]"));
                    }
                }

                if (unitTokens.unit is not null)
                {
                    // ParseGraphicsUnits throws an ArgumentException if format is invalid.
                    units = ParseGraphicsUnits(unitTokens.unit);
                }

                if (style is not null)
                {
                    // Parse FontStyle
                    style = style.Substring(6); // style string always starts with style=
                    string[] styleTokens = style.Split(separator);

                    for (int tokenCount = 0; tokenCount < styleTokens.Length; tokenCount++)
                    {
                        string styleText = styleTokens[tokenCount];
                        styleText = styleText.Trim();

                        fontStyle |= (FontStyle)Enum.Parse(typeof(FontStyle), styleText, true);

                        // Enum.IsDefined doesn't do what we want on flags enums...
                        FontStyle validBits = FontStyle.Regular | FontStyle.Bold | FontStyle.Italic | FontStyle.Underline | FontStyle.Strikeout;
                        if ((fontStyle | validBits) != validBits)
                        {
                            throw new InvalidEnumArgumentException(nameof(style), (int)fontStyle, typeof(FontStyle));
                        }
                    }
                }
            }

            return new FontDescriptor(fontName, fontSize, fontStyle, units);
        }

        private static GraphicsUnit ParseGraphicsUnits(string units) =>
            units switch
            {
                // Display unit is not supported
                // https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/libraries/System.Drawing.Common/src/System/Drawing/FontConverter.cs#L446-L463
                // "display" => GraphicsUnit.Display,
                "doc" => GraphicsUnit.Document,
                "pt" => GraphicsUnit.Point,
                "in" => GraphicsUnit.Inch,
                "mm" => GraphicsUnit.Millimeter,
                "px" => GraphicsUnit.Pixel,
                "world" => GraphicsUnit.World,
                _ => throw new ArgumentException(string.Format(SR.InvalidArgumentValueFontConverter, units), nameof(units)),
            };

        private static (string?, string?) ParseSizeTokens(string text, char separator)
        {
            string? size = null;
            string? units = null;

            text = text.Trim();

            int length = text.Length;
            int splitPoint;

            if (length > 0)
            {
                // text is expected to have a format like " 8,25pt, ". Leading and trailing spaces (trimmed above),
                // last comma, unit and decimal value may not appear. We need to make it ####.##CC
                for (splitPoint = 0; splitPoint < length; splitPoint++)
                {
                    if (char.IsLetter(text[splitPoint]))
                    {
                        break;
                    }
                }

                char[] trimChars = [separator, ' '];

                if (splitPoint > 0)
                {
                    size = text.Substring(0, splitPoint);

                    // Trimming spaces between size and units.
                    size = size.Trim(trimChars);
                }

                if (splitPoint < length)
                {
                    units = text.Substring(splitPoint);
                    units = units.TrimEnd(trimChars);
                }
            }

            return (size, units);
        }
    }
}
