using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using FastColoredTextBoxNS;
using System.Text.RegularExpressions;

namespace ScriptEditor
{
    public class SyntaxHighlightTextBox : FastColoredTextBox
    {

        public string Filename {get; set;}
        public bool dirty { get; set; }



        string[] keywords ;//= { "for" , "to" , "next" , "if" , "then" , "else" , "endif" , "while" , "wend" , "return" , "continue" , "break" , "false" , "true" };
       // string[] keywords;
        string[] methods ;//= { "Equals()" , "GetHashCode()" , "GetType()" , "ToString()" };
        string[] snippets = { "if ^ then \n else \nendif" , "for ^ to \n\n\nnext" , "while ^ \n\nwend" };
        string[] declarationSnippets = { "function ^()\n\n\n\nfend" };

        string keywordsRegEx="";

        //Style invisibleCharsStyle = new InvisibleCharsRenderer(Pens.Gray);
        Color currentLineColor = Color.FromArgb(100 , 210 , 210 , 255);
        Color changedLineColor = Color.FromArgb(255 , 230 , 230 , 255);

        Style KeywordsStyle = new TextStyle(Brushes.Blue , null , FontStyle.Bold);
        Style CommentStyle = new TextStyle(Brushes.Green , null , FontStyle.Regular);
        Style FunctionNameStyle = new TextStyle(Brushes.DeepPink , null , FontStyle.Regular);
        
        FastColoredTextBox CurrentTB;
        private ScriptRunner.Runner _Scriptrunner;
        private ImageList ilAutocomplete;

        public SyntaxHighlightTextBox(ScriptRunner.Runner ScriptRunner, ImageList ilAutocomplete, string filename)
        {
            this._Scriptrunner = ScriptRunner;
            this.ilAutocomplete = ilAutocomplete;
            this.Filename = filename;

            // 
            // fastColoredTextBox_Script
            // 
            this.AllowDrop = true;
            this.AutoScrollMinSize = new System.Drawing.Size(0, 14);
            this.BackBrush = null;
            this.Cursor = System.Windows.Forms.Cursors.IBeam;
            this.DisabledColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(180)))), ((int)(((byte)(180)))), ((int)(((byte)(180)))));
            this.Dock = System.Windows.Forms.DockStyle.Fill;
            this.ImeMode = System.Windows.Forms.ImeMode.On;
            this.Location = new System.Drawing.Point(3, 3);
            this.Name = "fastColoredTextBox_Script";
            this.Paddings = new System.Windows.Forms.Padding(0);
            this.SelectionColor = System.Drawing.Color.FromArgb(((int)(((byte)(50)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(255)))));
            this.Size = new System.Drawing.Size(814, 246);
            this.TabIndex = 2;
            this.WordWrap = true;
            this.TextChangedDelayed += new System.EventHandler<FastColoredTextBoxNS.TextChangedEventArgs>(this.fastColoredTextBox_Script_TextChangedDelayed);
            this.Load += new System.EventHandler(this.fastColoredTextBox_Script_Load);
            this.TextChanged += new System.EventHandler<FastColoredTextBoxNS.TextChangedEventArgs>(this.fastColoredTextBox_Script_TextChanged);

        }


        private void InitScriptTextBox()
        {
            CurrentTB = this;
            this.Tag = new TbInfo();
            this.HighlightingRangeType = HighlightingRangeType.VisibleRange;

            //keywords
            keywords = getKeywords();

            //methods
            methods = new string[_Scriptrunner.methodlist.Count];
            _Scriptrunner.methodlist.Keys.CopyTo(methods, 0);

            //create autocomplete popup menu
            AutocompleteMenu popupMenu = new AutocompleteMenu(this);
            popupMenu.Items.ImageList = ilAutocomplete;
            popupMenu.Opening += new EventHandler<CancelEventArgs>(popupMenu_Opening);
            BuildAutocompleteMenu(popupMenu);
            (this.Tag as TbInfo).popupMenu = popupMenu;


        }

        private string[] getKeywords()
        {
            var Grammar = new ScriptRunner.myScriptGrammar();
            var list = new List<string>();
            keywordsRegEx = "";
            foreach (var keyword in Grammar.KeyTerms)
            {
                //アルファベットで始まらなかったら、追加しない
                if ("abcdefghijklmnopqrstuvwxy".Contains(keyword.Key.Substring(0 , 1).ToLower()) == false)
                    continue;

                list.Add(keyword.Key);
                keywordsRegEx += keyword.Key + "|";
            }
            if (keywordsRegEx.Length > 1)
            {
                //最後の｜を削除する
                keywordsRegEx = keywordsRegEx.Substring(0 , keywordsRegEx.Length - 1);
                keywordsRegEx = @"\b(" + keywordsRegEx + @")\b";
            }
            return list.ToArray();
        }


        private void fastColoredTextBox_Script_Load(object sender , EventArgs e)
        {
            //base.OnLoad(e);
            InitScriptTextBox();
            //fastColoredTextBox_Script.OnTextChanged();
            this.OnTextChanged();
            this.dirty = false;
        }

        //スタイルを適用する。
        private void fastColoredTextBox_Script_TextChangedDelayed(object sender , FastColoredTextBoxNS.TextChangedEventArgs e)
        {
            //clear styles
            this.Range.ClearStyle(KeywordsStyle , FunctionNameStyle);
            //highlight keywords of LISP
            this.Range.SetStyle(KeywordsStyle, keywordsRegEx, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            //highlight keywords of LISP
            this.Range.SetStyle(CommentStyle, @"#.*", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
            //find function declarations, highlight all of their entry into the code
            foreach(Range found in this.GetRanges(@"\b(function)\s+(?<range>\w+)\b", System.Text.RegularExpressions.RegexOptions.IgnoreCase))
                this.Range.SetStyle(FunctionNameStyle, @"\b" + found.Text + @"\b");

            
        }

        private void fastColoredTextBox_Script_TextChanged(object sender, FastColoredTextBoxNS.TextChangedEventArgs e) 
        {
            this.dirty = true;
        }



        private void popupMenu_Opening(object sender, CancelEventArgs e)
        {
            //---block autocomplete menu for comments
            //get index of green style (used for comments)
            var iGreenStyle = CurrentTB.GetStyleIndex(CurrentTB.SyntaxHighlighter.GreenStyle);
            if (iGreenStyle >= 0)
                if (CurrentTB.Selection.Start.iChar > 0)
                {
                    //current char (before caret)
                    var c = CurrentTB[CurrentTB.Selection.Start.iLine][CurrentTB.Selection.Start.iChar - 1];
                    //green Style
                    var greenStyleIndex = Range.ToStyleIndex(iGreenStyle);
                    //if char contains green style then block popup menu
                    if ((c.style & greenStyleIndex) != 0)
                        e.Cancel = true;
                }
        }



        
        private void BuildAutocompleteMenu(AutocompleteMenu popupMenu)
        {
            List<AutocompleteItem> items = new List<AutocompleteItem>();

            foreach (var item in snippets)
                items.Add(new SnippetAutocompleteItem(item) { ImageIndex = 1 });
            foreach (var item in declarationSnippets)
                items.Add(new DeclarationSnippet(item) { ImageIndex = 0 });
            //foreach (var item in methods)
            //    items.Add(new MethodAutocompleteItem(item) { ImageIndex = 2 });
            foreach(var item in methods)
                items.Add(new AutocompleteItem(item + "(") { ImageIndex = 2 });


            foreach (var item in keywords)
                items.Add(new AutocompleteItem(item));

            items.Add(new InsertSpaceSnippet());
            items.Add(new InsertSpaceSnippet(@"^(\w+)([=<>!:]+)(\w+)$"));
            items.Add(new InsertEnterSnippet());

            //set as autocomplete source
            popupMenu.Items.SetAutocompleteItems(items);
            popupMenu.SearchPattern = @"[\w\.:=!<>]";
        }



    }//class






    public class TbInfo
    {
        // Key - Line.UniqueId
        public HashSet<int> bookmarksLineId = new HashSet<int>();
        // Index - bookmark number, Value - Line.UniqueId
        public List<int> bookmarks = new List<int>();
        //
        public AutocompleteMenu popupMenu;
    }


    /// <summary>
    /// This item appears when any part of snippet text is typed
    /// </summary>
    class DeclarationSnippet : SnippetAutocompleteItem
    {
        public DeclarationSnippet(string snippet)
            : base(snippet)
        {
        }

        public override CompareResult Compare(string fragmentText)
        {
            var pattern = Regex.Escape(fragmentText);
            if (Regex.IsMatch(Text , "\\b" + pattern , RegexOptions.IgnoreCase))
                return CompareResult.Visible;
            return CompareResult.Hidden;
        }
    }//class


    /// <summary>
    /// Divides numbers and words: "123AND456" -> "123 AND 456"
    /// Or "cnt=2" -> "cnt = 2"
    /// </summary>
    class InsertSpaceSnippet : AutocompleteItem
    {
        string pattern;

        public InsertSpaceSnippet(string pattern)
            : base("")
        {
            this.pattern = pattern;
        }

        public InsertSpaceSnippet()
            : this(@"^(\d+)([a-zA-Z_]+)(\d*)$")
        {
        }

        public override CompareResult Compare(string fragmentText)
        {
            if (Regex.IsMatch(fragmentText , pattern))
            {
                Text = InsertSpaces(fragmentText);
                if (Text != fragmentText)
                    return CompareResult.Visible;
            }
            return CompareResult.Hidden;
        }

        public string InsertSpaces(string fragment)
        {
            var m = Regex.Match(fragment , pattern);
            if (m == null)
                return fragment;
            if (m.Groups[1].Value == "" && m.Groups[3].Value == "")
                return fragment;
            return (m.Groups[1].Value + " " + m.Groups[2].Value + " " + m.Groups[3].Value).Trim();
        }

        public override string ToolTipTitle
        {
            get
            {
                return Text;
            }
        }
    }//class




    /// <summary>
    /// Inerts line break after '}'
    /// </summary>
    class InsertEnterSnippet : AutocompleteItem
    {
        Place enterPlace = Place.Empty;

        public InsertEnterSnippet()
            : base("[Line break]")
        {
        }

        public override CompareResult Compare(string fragmentText)
        {
            var r = Parent.Fragment.Clone();
            while (r.Start.iChar > 0)
            {
                if (r.CharBeforeStart == '}')
                {
                    enterPlace = r.Start;
                    return CompareResult.Visible;
                }

                r.GoLeftThroughFolded();
            }

            return CompareResult.Hidden;
        }

        public override string GetTextForReplace()
        {
            //extend range
            Range r = Parent.Fragment;
            Place end = r.End;
            r.Start = enterPlace;
            r.End = r.End;
            //insert line break
            return Environment.NewLine + r.Text;
        }

        public override void OnSelected(AutocompleteMenu popupMenu , SelectedEventArgs e)
        {
            base.OnSelected(popupMenu , e);
            if (Parent.Fragment.tb.AutoIndent)
                Parent.Fragment.tb.DoAutoIndent();
        }

        public override string ToolTipTitle
        {
            get
            {
                return "Insert line break after '}'";
            }
        }
    }//class



}