using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Xml.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Unity.Tutorials.Core.Editor
{
    /// 
    /// Creates UIToolkit elements from a rich text.
    /// 
    public static class RichTextParser
    {
        /// 
        /// Tries to parse text to XDocument word by word - outputs the longest successful string before failing
        /// 
        /// Content that contains a markdown error
        /// 
        static string ParseUntilError(string faultyContent)
        {
            string longestString = "";
            string previousLongestString = "";
            string[] lines = faultyContent.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
            foreach (string line in lines)
            {
                string[] words = line.Split(new[] { " " }, StringSplitOptions.None);
                foreach (string word in words)
                {
                    longestString += word + " ";
                    try
                    {
                        XDocument.Parse("" + longestString + "");
                    }
                    catch
                    {
                        continue;
                    }
                    previousLongestString = longestString;
                }
                longestString += "\r\n";
            }
            return previousLongestString;
        }
        /// 
        /// Preprocess rich text - add space around tags.
        /// 
        /// Text with tags
        /// Text with space around tags
        static string PreProcessRichText(string inputText)
        {
            string processed = inputText;
            processed = processed.Replace("", " ");
            processed = processed.Replace("", " ");
            processed = processed.Replace("", " ");
            processed = processed.Replace("", " ");
            processed = processed.Replace("", " ");
            processed = processed.Replace("", " ");
            processed = processed.Replace("", " ");
            processed = processed.Replace("", "  ");
            return processed;
        }
        /// 
        /// Helper function to detect if the string contains any characters in
        /// the Unicode range reserved for Chinese, Japanese and Korean characters.
        /// 
        /// String to check for CJK letters.
        /// True if it contains Chinese, Japanese or Korean characters.
        static bool NeedSymbolWrapping(string textLine)
        {
            // Unicode range for CJK letters.
            // Range chosen from StackOverflow: https://stackoverflow.com/a/42411925
            // Validated from sources:
            // https://www.unicode.org/faq/han_cjk.html
            // https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block)
            return textLine.Any(c => (uint)c >= 0x4E00 && (uint)c <= 0x2FA1F);
        }
        /// 
        /// Adds a new wrapping word Label to the target visualElement. Type can be BoldLabel, ItalicLabel or Label
        /// 
        /// 
        /// The text inside the word label.
        /// Redundant storage, mostly used for automated testing.
        /// Parent container for the word Label.
        static Label AddLabel(string textToAdd, List elementList, VisualElement targetContainer)
            where T : Label
        {
            Type labelType = typeof(T);
            Func generator = null;
            if (labelType == typeof(ItalicLabel))
            {
                generator = () => new ItalicLabel(textToAdd);
            }
            else if (labelType == typeof(BoldLabel))
            {
                generator = () => new BoldLabel(textToAdd);
            }
            else if (labelType == typeof(TextLabel))
            {
                generator = () => new TextLabel(textToAdd);
            }
            else if (labelType == typeof(WhiteSpaceLabel))
            {
                generator = () => new WhiteSpaceLabel(textToAdd);
            }
            if (generator == null)
            {
                Debug.LogError("Error: Unsupported Label type used. Use TextLabel, BoldLabel or ItalicLabel.");
                return null;
            }
            return TrackAndAddGeneratedElement(generator, targetContainer, elementList) as Label;
        }
        static VisualElement GenerateParseErrorLabel(string errorText)
        {
            var label = new ParseErrorLabel()
            {
                text = Localization.Tr(LocalizationKeys.k_TutorialLabelParseError),
                tooltip = Localization.Tr(LocalizationKeys.k_TutorialLabelParseErrorTooltip)
            };
            label.RegisterCallback((e) => Debug.LogError(errorText));
            return label;
        }
        static VisualElement GenerateLinkLabel(string text, string url)
        {
            var label = new HyperlinkLabel
            {
                text = text,
                tooltip = url
            };
            EventCallback linkCallback = null;
            string actualPath = "";
            // Link points to a relative directory
            if (url[0] == '.')
            {
                if (url.Length == 1) //current folder
                {
                    actualPath = Path.GetFullPath(url);
                }
                else if (url.StartsWith("./"))
                {
                    if (url.Length == 2) //current folder
                    {
                        actualPath = Path.GetFullPath(url);
                    }
                    else //Folder within the current one, or parent folder
                    {
                        actualPath = Path.GetFullPath(url.Remove(0, 2));
                    }
                }
                else //local asset, assume the "." is there only because the user doesn't know about the "./" notation
                {
                    actualPath = Path.GetFullPath(url.Remove(0, 1));
                }
                linkCallback = (evt, path) =>
                {
                    EditorUtility.OpenWithDefaultApp(path);
                };
            }
            else
            {
                actualPath = url;
                linkCallback = (evt, path) =>
                {
                    TutorialEditorUtils.OpenUrl(path);
                };
            }
            label.RegisterCallback(linkCallback, actualPath);
            return label;
        }
        /// 
        /// Transforms HTML tags to word element labels with different styles to enable rich text.
        /// 
        /// 
        /// 
        /// The following need to set for the container's style:
        /// flex-direction: row;
        /// flex-wrap: wrap;
        /// 
        /// List of VisualElements made from the parsed text.
        public static List RichTextToVisualElements(string htmlText, VisualElement targetContainer)
        {
            targetContainer.Clear();
            var generatedElements = new List();
            // start streaming text per word to elements while retaining current style for each word block
            string[] lines = htmlText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
            RenderLines(lines, targetContainer, generatedElements);
            return generatedElements;
        }
        static void RenderLines(string[] lines, VisualElement targetContainer, List elements)
        {
            bool boldOn = false; //  sets this on  sets off
            bool italicOn = false; //  
            bool forceWordWrap = false;
            bool linkOn = false;
            string linkURL = "";
            string styleClass = "";
            bool addStyle = false;
            bool firstLine = true;
            bool lastLineHadText = false;
            foreach (string line in lines)
            {
                // Check if the line begins with whitespace and turn that into corresponding Label
                string initialWhiteSpaces = "";
                foreach (char singleCharacter in line)
                {
                    if (singleCharacter == ' ' || singleCharacter == '\t')
                    {
                        initialWhiteSpaces += singleCharacter;
                    }
                    else
                    {
                        break;
                    }
                }
                string processedLine = PreProcessRichText(line);
                // Separate the line into words
                string[] words = processedLine.Split(new[] { " ", "\t" }, StringSplitOptions.RemoveEmptyEntries);
                if (!lastLineHadText)
                {
                    if (!firstLine)
                    {
                        elements.Add(AddParagraphToElement(targetContainer));
                    }
                    if (initialWhiteSpaces.Length > 0)
                    {
                        AddLabel(initialWhiteSpaces, elements, targetContainer);
                    }
                }
                if (!firstLine && lastLineHadText)
                {
                    elements.Add(AddLinebreakToElement(targetContainer));
                    lastLineHadText = false;
                    if (initialWhiteSpaces.Length > 0)
                    {
                        AddLabel(initialWhiteSpaces, elements, targetContainer);
                    }
                }
                RenderWords(words, ref lastLineHadText, ref boldOn, ref italicOn, ref forceWordWrap, ref linkOn, ref addStyle, ref linkURL, ref styleClass, elements, targetContainer);
                firstLine = false;
            }
        }
        static void RenderWords(string[] words, ref bool lastLineHadText, ref bool boldOn,
            ref bool italicOn, ref bool forceWordWrap, ref bool linkOn, ref bool addStyle, ref string linkURL,
            ref string styleClass, List elements, VisualElement targetContainer)
        {
            foreach (string word in words)
            {
                // Wrap every character instead of word in case of Chinese and Japanese
                // Note: override with Force word wrapping here
                if (word == "" || word == " " || word == "   ")
                {
                    continue;
                }
                lastLineHadText = true;
                string strippedWord = word;
                bool removeBold = false;
                bool removeItalic = false;
                bool addParagraph = false;
                bool removeLink = false;
                bool removeWordWrap = false;
                bool removeStyle = false;
                strippedWord = strippedWord.Trim();
                if (strippedWord.Contains(""))
                {
                    strippedWord = strippedWord.Replace("", "");
                    boldOn = true;
                }
                if (strippedWord.Contains(""))
                {
                    strippedWord = strippedWord.Replace("", "");
                    italicOn = true;
                }
                if (strippedWord.Contains(""))
                {
                    strippedWord = strippedWord.Replace("", "");
                    forceWordWrap = true;
                }
                if (strippedWord.Contains("", "");
                }
                if (addStyle && strippedWord.Contains("class="))
                {
                    strippedWord = strippedWord.Replace("class=", "");
                    int styleFrom = strippedWord.IndexOf("\"", StringComparison.Ordinal) + 1;
                    int styleTo = strippedWord.LastIndexOf("\"", StringComparison.Ordinal);
                    styleClass = strippedWord.Substring(styleFrom, styleTo - styleFrom);
                    strippedWord = strippedWord.Substring(styleTo + 2, (strippedWord.Length - 2) - styleTo);
                    strippedWord.Replace("\">", "");
                }
                if (strippedWord.Contains("&"))
                {
                    strippedWord = WebUtility.HtmlDecode(strippedWord);
                }
                if (strippedWord.Contains(""))
                {
                    strippedWord = strippedWord.Replace("", "");
                    removeLink = true;
                }
                if (strippedWord.Contains(""))
                {
                    strippedWord = strippedWord.Replace("", "");
                    removeStyle = true;
                }
                if (strippedWord.Contains("
"))
                {
                    strippedWord = strippedWord.Replace("
", "");
                    addParagraph = true;
                }
                if (strippedWord.Contains(""))
                {
                    strippedWord = strippedWord.Replace("", "");
                    removeBold = true;
                }
                if (strippedWord.Contains(""))
                {
                    strippedWord = strippedWord.Replace("", "");
                    removeItalic = true;
                }
                if (strippedWord.Contains(""))
                {
                    strippedWord = strippedWord.Replace("", "");
                    removeWordWrap = true;
                }
                if (boldOn && strippedWord != "")
                {
                    if (wrapCharacters)
                    {
                        foreach (char character in strippedWord)
                        {
                            AddLabel(character.ToString(), elements, targetContainer);
                        }
                    }
                    else
                    {
                        AddLabel(strippedWord, elements, targetContainer);
                    }
                }
                else if (italicOn && strippedWord != "")
                {
                    if (wrapCharacters)
                    {
                        foreach (char character in strippedWord)
                        {
                            AddLabel(character.ToString(), elements, targetContainer);
                        }
                    }
                    else
                    {
                        AddLabel(strippedWord, elements, targetContainer);
                    }
                }
                else if (addParagraph)
                {
                    elements.Add(AddParagraphToElement(targetContainer));
                }
                else if (linkOn && !string.IsNullOrEmpty(linkURL))
                {
                    string url = linkURL;
                    TrackAndAddGeneratedElement(() => GenerateLinkLabel(strippedWord, url), targetContainer, elements);
                }
                else
                {
                    if (strippedWord != "")
                    {
                        if (wrapCharacters)
                        {
                            foreach (char character in strippedWord)
                            {
                                Label newLabel = AddLabel(character.ToString(), elements, targetContainer);
                                if (addStyle && !string.IsNullOrEmpty(styleClass))
                                {
                                    newLabel.AddToClassList(styleClass);
                                }
                            }
                        }
                        else
                        {
                            Label newLabel = AddLabel(strippedWord, elements, targetContainer);
                            if (addStyle && !string.IsNullOrEmpty(styleClass))
                            {
                                newLabel.AddToClassList(styleClass);
                            }
                        }
                    }
                }
                if (removeBold)
                {
                    boldOn = false;
                }
                if (removeItalic)
                {
                    italicOn = false;
                }
                if (removeLink)
                {
                    linkOn = false;
                    linkURL = "";
                }
                if (removeStyle)
                {
                    addStyle = false;
                }
                if (removeWordWrap)
                {
                    forceWordWrap = false;
                }
            }
        }
        static VisualElement TrackAndAddGeneratedElement(Func generator, VisualElement targetContainer, List trackedElements)
        {
            VisualElement newElement = generator.Invoke();
            targetContainer.Add(newElement);
            trackedElements.Add(newElement);
            return newElement;
        }
        static VisualElement AddLinebreakToElement(VisualElement elementTo)
        {
            Label wordLabel = new Label(" ");
            wordLabel.style.flexDirection = FlexDirection.Row;
            wordLabel.style.flexGrow = 1f;
            wordLabel.style.width = 3000f;
            wordLabel.style.height = 0f;
            elementTo.Add(wordLabel);
            return wordLabel;
        }
        static VisualElement AddParagraphToElement(VisualElement elementTo)
        {
            Label wordLabel = new Label(" ");
            wordLabel.style.flexDirection = FlexDirection.Row;
            wordLabel.style.flexGrow = 1f;
            wordLabel.style.width = 3000f;
            elementTo.Add(wordLabel);
            return wordLabel;
        }
        // Dummy classes so that we can customize the styles from a USS file.
        /// 
        /// Label for the red parser error displayed where the parsing fails
        /// 
        public class ParseErrorLabel : Label { }
        /// 
        /// Text label for links
        /// 
        public class HyperlinkLabel : Label { }
        /// 
        /// Text label for text that wraps per word
        /// 
        public class TextLabel : Label
        {
            /// 
            /// Constructs with text.
            /// 
            /// 
            public TextLabel(string text) : base(text)
            {
            }
        }
        /// 
        /// Text label for white space used to indent lines
        /// 
        public class WhiteSpaceLabel : Label
        {
            /// 
            /// Constructs with text.
            /// 
            /// 
            public WhiteSpaceLabel(string text) : base(text)
            {
            }
        }
        /// 
        /// Text label with bold style
        /// 
        public class BoldLabel : Label
        {
            /// 
            /// Constructs with text.
            /// 
            /// 
            public BoldLabel(string text) : base(text)
            {
            }
        }
        /// 
        /// Text label with italic style
        /// 
        public class ItalicLabel : Label
        {
            /// 
            /// Constructs with text.
            /// 
            /// 
            public ItalicLabel(string text) : base(text)
            {
            }
        }
    }
}