using System;
using System.Runtime.CompilerServices;
using System.Text;

[assembly: InternalsVisibleTo("Coffee.NanoMonitor.Tests")]

namespace Coffee.NanoMonitor
{
    internal static class StringBuilderExtensions
    {
        public static bool IsEqual(this StringBuilder sb, string other)
        {
            if (sb == null || other == null) return false;
            if (sb.Length != other.Length) return false;

            for (var i = 0; i < sb.Length; i++)
            {
                if (sb[i] != other[i]) return false;
            }

            return true;
        }

        public static void AppendFormatNoAlloc(this StringBuilder sb, string format, double arg0 = 0, double arg1 = 0, double arg2 = 0, double arg3 = 0)
        {
            for (var i = 0; i < format.Length; i++)
            {
                var c = format[i];

                // Append formatted value
                if (c == '{')
                {
                    i = GetFormat(format, i, out var argIndex, out var padding, out var precision, out var alignment);

                    switch (argIndex)
                    {
                        case 0:
                            sb.AppendDouble(arg0, padding, precision, alignment);
                            break;
                        case 1:
                            sb.AppendDouble(arg1, padding, precision, alignment);
                            break;
                        case 2:
                            sb.AppendDouble(arg2, padding, precision, alignment);
                            break;
                        case 3:
                            sb.AppendDouble(arg3, padding, precision, alignment);
                            break;
                    }

                    continue;
                }

                // Append character
                sb.Append(c);
            }
        }

        internal static void AppendInteger(this StringBuilder sb, double number, int padding, int precision, int alignment)
        {
            number = Math.Truncate(number);
            var sign = number < 0;
            number = sign ? -number : number;

            var startIndex = sb.Length;
            do
            {
                var n = Math.Truncate(number % 10);
                number /= 10;

                sb.Append((char) (n + 48));
            } while (1 <= number || (sb.Length - startIndex) < padding);

            if (sign)
            {
                sb.Append('-');
            }

            var endIndex = sb.Length - 1;

            sb.Reverse(startIndex, endIndex);
            sb.Alignment(alignment, startIndex, endIndex);
        }

        internal static void AppendDouble(this StringBuilder sb, double number, int padding, int precision, int alignment)
        {
            var integer = Math.Truncate(number);
            var startIndex = sb.Length;
            sb.AppendInteger(integer, padding, precision, 0);

            if (0 < precision)
            {
                sb.Append('.');
                number -= integer;
                number = Math.Round(number, precision);
                for (var p = 0; p < precision; p++)
                {
                    number *= 10;
                    integer = (long) number;
                    sb.Append((char) (integer + 48));
                    number -= integer;
                    number = Math.Round(number, precision);
                }
            }

            sb.Alignment(alignment, startIndex, sb.Length - 1);
        }

        internal static void Reverse(this StringBuilder sb, int start, int end)
        {
            while (start < end)
            {
                var c = sb[start];
                sb[start] = sb[end];
                sb[end] = c;

                start++;
                end--;
            }
        }

        internal static void Alignment(this StringBuilder sb, int alignment, int start, int end)
        {
            if (alignment == 0) return;

            var len = end - start + 1;
            if (0 < alignment && len < alignment)
            {
                sb.Append(' ', alignment - len);
                for (var i = 0; i < len; i++)
                {
                    var c = sb[end - i];
                    sb[end - i] = sb[start + alignment - i - 1];
                    sb[start + alignment - i - 1] = c;
                }
            }
            else if (alignment < 0 && len < -alignment)
            {
                sb.Append(' ', -alignment - len);
            }
        }

        internal static int GetFormat(string format, int i, out int argIndex, out int padding, out int precision, out int alignment)
        {
            argIndex = -1;
            padding = 0;
            precision = 0;
            alignment = 0;

            var alignmentSign = false;
            var readFlag = 0;

            for (; i < format.Length; i++)
            {
                var c = format[i];

                // End format
                if (c == '}')
                {
                    return i;
                }

                // Start format
                if (c == '{')
                {
                    readFlag = 1;
                }

                // After '{': Read argument index and format
                else if (readFlag == 1)
                {
                    if ('0' <= c && c <= '3')
                    {
                        argIndex = c - 48;
                    }
                    else if (c == ',')
                    {
                        readFlag = 2;
                    }
                    else if (c == ':')
                    {
                        readFlag = 3;
                    }
                }

                //After ',': Read alignment value
                else if (readFlag == 2)
                {
                    if ('0' <= c && c <= '9')
                    {
                        alignment = alignment * 10 + (alignmentSign ? 48 - c : c - 48);
                    }
                    else if (c == '-')
                    {
                        alignmentSign = true;
                    }
                    else if (c == ':')
                    {
                        readFlag = 3;
                    }
                }

                // After ':': Read format for integral
                else if (readFlag == 3)
                {
                    if (c == '.')
                    {
                        precision = 0;
                        readFlag = 4;
                    }
                    else if (c == '0')
                    {
                        padding++;
                    }
                    // Legacy mode
                    else if ('1' <= c && c <= '9')
                    {
                        precision = c - 48;
                    }
                }

                // After '.': Read decimal precision value
                else if (readFlag == 4)
                {
                    if (c == '0')
                    {
                        precision++;
                    }
                }
            }

            argIndex = -1;
            return i;
        }
    }
}
