using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.IO;
using System.Text;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using DevExpress.XtraBars;
using DevExpress.XtraEditors;
using DevExpress.XtraPrinting.Native;
using PXP.Crawler.API.Model;
using ScintillaNET;

namespace PXP.Crawler.API.Designers.Controls
{
    public partial class CodeEditorControl : DevExpress.XtraEditors.XtraUserControl
    {
        #region Variables

        internal Project project;
        internal ProjectItem projectItem;

        private string scriptsPath;

        #endregion
        
        #region Constructors

        public CodeEditorControl()
        {
            Init();
        }

        public CodeEditorControl(Project proj, ProjectItem projItem)
        {
            project = proj;
            projectItem = projItem;
            Init();

            codeScintilla.Text = projItem.data;
        }

        #endregion

        #region Methods

        private void Init()
        {
            InitializeComponent();
        }

        #endregion

        #region Internal Events

        private void CodeEditorControl_Load(object sender, EventArgs e)
        {
            string appPath = Application.ExecutablePath;
            int lpi = appPath.LastIndexOf('\\');
            appPath = appPath.Substring(0, lpi) + "\\";
            scriptsPath = appPath + "Scripts";

            DirectoryInfo scDir = new DirectoryInfo(scriptsPath);
            foreach (FileInfo scFileInfo in scDir.GetFiles("*.cs"))
            {                
                BarItem scrBarItem = new BarButtonItem(barManager, scFileInfo.Name.Substring(0, scFileInfo.Name.Length - 3));
                scrBarItem.Tag = scFileInfo.FullName;
                libraryBarSubItem.AddItem(scrBarItem);
                scrBarItem.ItemClick += ScrBarItemOnItemClick;
            }

            codeScintilla.AutoComplete.List.Clear();
            codeScintilla.AutoComplete.ListSeparator = ';';
            codeScintilla.AutoComplete.List.Add("Test 1");
            codeScintilla.AutoComplete.List.Add("Test 2");
            codeScintilla.AutoComplete.List.Add("Test 3");
        }

        private void ScrBarItemOnItemClick(object sender, ItemClickEventArgs e)
        {
            string scrPath = e.Item.Tag.ToString();
            codeScintilla.Text = File.ReadAllText(scrPath);
        }
        private void saveBarButtonItem_ItemClick(object sender, ItemClickEventArgs e)
        {
            projectItem.data = codeScintilla.Text;
        }

        private void buildBarButtonItem_ItemClick(object sender, ItemClickEventArgs e)
        {
            ParentForm.Cursor = Cursors.WaitCursor;

            saveBarButtonItem.PerformClick();
            project.Compiler.BuildProject();
            ParentForm.Cursor = Cursors.Default;
        }

        private class ErrorLine
        {
            public int start;
            public int end;
            public string errorText;

            public bool Contains(int pos)
            {
                if (pos < start)
                    return false;
                if (pos > end)
                    return false;
                return true;
            }
        }

        List<ErrorLine> errLines = new List<ErrorLine>();

        public void AddErrorLine(int line, int col, string errorText, bool showText = false)
        {
            codeScintilla.GetRange(0, 0).SetIndicator(0);

            codeScintilla.Indicators[2].Style = IndicatorStyle.Box;
            codeScintilla.Indicators[2].Color = Color.DarkRed;
            codeScintilla.Indicators[2].Alpha = 100;
            codeScintilla.Indicators[2].OutlineAlpha = 200;
            //codeScintilla.Indicators[2].DrawMode = IndicatorDrawMode.Underlay;            

            Line ceLine = codeScintilla.Lines[line];
            if (ceLine != null)
            {
                errLines.Add(new ErrorLine() { start = ceLine.StartPosition + col - 1, end = ceLine.EndPosition, errorText = errorText});

                string tls = ceLine.Text.TrimStart(new char[] {' ', '\t'});
                int spd = ceLine.Length - tls.Length;
                Range cr = codeScintilla.GetRange(ceLine.StartPosition + spd, ceLine.EndPosition);
                cr.SetIndicator(2);

            }

            //codeScintilla.Caret.Position = ceLine.StartPosition + col;
            codeScintilla.Selection.Start = ceLine.StartPosition + col - 1;
            codeScintilla.Selection.Length = 0;

            if (showText)
            {
                Point cp = new Point(codeScintilla.PointXFromPosition(codeScintilla.Caret.Position),
                    codeScintilla.PointYFromPosition(codeScintilla.Caret.Position));
                cp = codeScintilla.PointToScreen(cp);
                selPoint = cp;
                errorToolTipController.ShowHint(errorText, "Script Error", cp);
                selErrLine = errLines[errLines.Count - 1];
                selIndPos = selErrLine.start;
            }
            
        }

        public void ClearErrorLines()
        {
            codeScintilla.GetRange().ClearIndicator(2);            
            errLines.Clear();
            errorToolTipController.HideHint();
        }

        #endregion

        private void clearErrorsBarButtonItem_ItemClick(object sender, ItemClickEventArgs e)
        {
            ClearErrorLines();
            errorToolTipController.HideHint();
        }

        private int selIndPos = -1;
        private Point selPoint = new Point();
        private ErrorLine selErrLine = null;

        private void codeScintilla_IndicatorClick(object sender, ScintillaMouseEventArgs e)
        {
            selIndPos = e.Position;
            bool elFound = false;
            foreach (ErrorLine el in errLines)
            {
                if (el.Contains(e.Position))
                {
                    elFound = true;
                    selErrLine = el;
                    
                    Point sPos = new Point(codeScintilla.PointXFromPosition(e.Position), codeScintilla.PointYFromPosition(e.Position));
                    selPoint = codeScintilla.PointToScreen(sPos);
                    errorToolTipController.ShowHint(el.errorText, sPos);
                    break;
                }
            }            
        }

        private void codeScintilla_SelectionChanged(object sender, EventArgs e)
        {
            if (selIndPos == -1)
            {
                foreach (ErrorLine el in errLines)
                {
                    if (!(el.Contains(codeScintilla.Selection.Start) || el.Contains(codeScintilla.Selection.End)))
                    {
                        errorToolTipController.HideHint();
                        break;
                    }
                }
            }
            else
            {
                if (selErrLine != null)
                {
                    errorToolTipController.ShowHint(selErrLine.errorText, "Script Error", selPoint);                    
                }
            }
        }

        private void codeScintilla_MouseUp(object sender, MouseEventArgs e)
        {
            selIndPos = -1;
        }

        List<string> usings = new List<string>();
        private Type classType;

        private Type FindType(string name)
        {
            Type t = Type.GetType(name);
            if (t == null)
            {
                foreach (string us in usings)
                {
                    t = Type.GetType(us + "." + name);
                    if (t != null)
                        return t;
                }
            }
            return null;
        }


        private void codeScintilla_TextInserted(object sender, TextModifiedEventArgs e)
        {            
            rootScrEl.Parse(codeScintilla.Text);

            Regex classRegex = new Regex(@"[\s\n]class[^\:]*\:\s{0,}(\w*)", RegexOptions.Multiline);
            MatchCollection classMatches = classRegex.Matches(codeScintilla.Text);

            classType = null;

            if (classMatches.Count > 0)
            {
                Match classMatch = classMatches[0];

                if (classMatch.Groups.Count == 2)
                {
                    string classTypeName = classMatch.Groups[1].Value;

                    Regex usingsRegex = new Regex(@"using\s{1,}([^\n]*)\;", RegexOptions.Multiline);
                    usings.Clear();
                    MatchCollection usingsMatches = usingsRegex.Matches(codeScintilla.Text);
                    foreach (Match usingMatch in usingsMatches)
                    {
                        if (usingMatch.Groups.Count == 2)
                        {
                            usings.Add(usingMatch.Groups[1].Value);
                        }
                    }

                    classType = FindType(classTypeName);
                }                
            }

            codeChangeTimer.Enabled = true;
            codeChangeTimer.Stop();
            codeChangeTimer.Start();
        }

        ScriptElement rootScrEl = new ScriptElement("", 0, 0);



        public class ScriptElement : IDisposable
        {
            public int start = 0;
            public int end = 0;
            public string script = "";
            public ScriptElement parent = null;
            public List<ScriptElement> elements = new List<ScriptElement>();

            public ScriptElement(string fullScript, int start, int end)
            {
                if (end < fullScript.Length)
                {
                    script = fullScript.Substring(start, end + start);                    
                }
            }

            public void Parse(string scr)
            {
                elements.ForEach(element => element.Dispose());
                elements.Clear();

                int parentStart = 0;
                int parentEnd = end;
                if (parent != null)
                {
                    parentStart = parent.start;
                    parentEnd = parent.end;
                }
                else
                {
                    start = 0;
                    parentEnd = end = scr.Length;
                }

                Regex elRegex = new Regex(@"[\n\r]{0,}([^\n\r]*)[\n\r]{0,}[\n\r]{0,}\{[\n\r]{0,}(.*)\}", RegexOptions.Singleline);
                MatchCollection elMatches = elRegex.Matches(scr);
                foreach (Match elMatch in elMatches)
                {
                    if (elMatch.Groups.Count == 3)
                    {
                        start = elMatch.Groups[2].Index + parentEnd;
                        end = elMatch.Groups[2].Index + elMatch.Groups[2].Length + parentEnd;
                        script = elMatch.Groups[2].Value;
                        ScriptElement csEl = new ScriptElement(scr, 0, scr.Length);
                        csEl.Parse(script);
                        elements.Add(csEl);
                        csEl.parent = this;
                    }
                
                }
            }

            public void Dispose()
            {
                elements.ForEach(element => element.Dispose());
            }

            public ScriptElement FindScrElement(int pos, int len)
            {
                if ((start > 0 && end > 0) && pos >= start && pos + len < end)
                {
                    return this;
                }
                ScriptElement se = null;
                foreach (ScriptElement cse in elements)
                {
                    se = cse.FindScrElement(pos, len);
                    if (se != null)
                        return se;
                }
                return se;
            }
        }

        private void codeChangeTimer_Tick(object sender, EventArgs e)
        {
            codeChangeTimer.Enabled = false;
            project.Log("Code changed...");
        }
    }
}
