using System.Globalization;
using Avalonia.Media.TextFormatting.Unicode;

namespace Avalonia.Controls.Utils
{
    internal static class StringUtils
    {
        private enum CharClass
        {
            CharClassUnknown,
            CharClassWhitespace,
            CharClassAlphaNumeric,
        }

        public static bool IsEol(char c)
        {
            return c == '\r' || c == '\n';
        }

        public static bool IsStartOfWord(string text, int index)
        {
            if (index >= text.Length)
            {
                return false;
            }

            var codepoint = new Codepoint(text[index]);

            // A 'word' starts with an AlphaNumeric or some punctuation symbols immediately
            // preceeded by lwsp.
            if (index > 0)
            {
                var previousCodepoint = new Codepoint(text[index - 1]);

                if (!previousCodepoint.IsWhiteSpace)
                {
                    return false;
                }

                if (previousCodepoint.IsBreakChar)
                {
                    return true;
                }
            }

            switch (codepoint.GeneralCategory)
            {
                case GeneralCategory.LowercaseLetter:
                case GeneralCategory.TitlecaseLetter:
                case GeneralCategory.UppercaseLetter:
                case GeneralCategory.DecimalNumber:
                case GeneralCategory.LetterNumber:
                case GeneralCategory.OtherNumber:
                case GeneralCategory.DashPunctuation:
                case GeneralCategory.InitialPunctuation:
                case GeneralCategory.OpenPunctuation:
                case GeneralCategory.CurrencySymbol:
                case GeneralCategory.MathSymbol:
                    return true;

                // TODO: How do you do this in .NET?
                // case UnicodeCategory.OtherPunctuation:
                //    // words cannot start with '.', but they can start with '&' or '*' (for example)
                //    return g_unichar_break_type(buffer->text[index]) == G_UNICODE_BREAK_ALPHABETIC;
                default:
                    return false;
            }
        }

        public static int PreviousWord(string text, int cursor)
        {
            if (string.IsNullOrEmpty(text))
            {
                return 0;
            }
            
            int begin;
            int i;
            int cr;
            int lf;

            lf = LineBegin(text, cursor) - 1;

            if (lf > 0 && text[lf] == '\n' && text[lf - 1] == '\r')
            {
                cr = lf - 1;
            }
            else
            {
                cr = lf;
            }

            // if the cursor is at the beginning of the line, return the end of the prev line
            if (cursor - 1 == lf)
            {
                return (cr > 0) ? cr : 0;
            }

            CharClass cc = GetCharClass(text[cursor - 1]);
            begin = lf + 1;
            i = cursor;

            // skip over the word, punctuation, or run of whitespace
            while (i > begin && GetCharClass(text[i - 1]) == cc)
            {
                i--;
            }

            // if the cursor was at whitespace, skip back a word too
            if (cc == CharClass.CharClassWhitespace && i > begin)
            {
                cc = GetCharClass(text[i - 1]);
                while (i > begin && GetCharClass(text[i - 1]) == cc)
                {
                    i--;
                }
            }

            return i;
        }

        public static int NextWord(string text, int cursor)
        {
            int i, lf, cr;

            cr = LineEnd(text, cursor);

            if (cursor >= text.Length)
            {
                return cursor;
            }
            
            if (cr < text.Length && text[cr] == '\r' && cr + 1 < text.Length && text[cr + 1] == '\n')
            {
                lf = cr + 1;
            }
            else
            {
                lf = cr;
            }

            // if the cursor is at the end of the line, return the starting offset of the next line
            if (cursor == cr || cursor == lf)
            {
                if (lf < text.Length)
                {
                    return lf + 1;
                }

                return cursor;
            }

            i = cursor;

            // skip any whitespace after the word/punct
            while (i < cr && char.IsWhiteSpace(text[i]))
            {
                i++;
            }

            if (i >= cr)
            {
                return i;
            }
            
            var cc = GetCharClass(text[i]);
            
            // skip over the word, punctuation, or run of whitespace
            while (i < cr && GetCharClass(text[i]) == cc)
            {
                i++;
            }

            return i;
        }

        private static CharClass GetCharClass(char c)
        {
            if (char.IsWhiteSpace(c))
            {
                return CharClass.CharClassWhitespace;
            }
            else if (char.IsLetterOrDigit(c))
            {
                return CharClass.CharClassAlphaNumeric;
            }
            else
            {
                return CharClass.CharClassUnknown;
            }
        }

        private static int LineBegin(string text, int pos)
        {
            while (pos > 0 && !IsEol(text[pos - 1]))
            {
                pos--;
            }

            return pos;
        }

        private static int LineEnd(string text, int cursor, bool include = false)
        {
            while (cursor < text.Length && !IsEol(text[cursor]))
            {
                cursor++;
            }

            if (include && cursor < text.Length)
            {
                if (text[cursor] == '\r' && text[cursor + 1] == '\n')
                {
                    cursor += 2;
                }
                else
                {
                    cursor++;
                }
            }

            return cursor;
        }
    }
}
