using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.Highlighting;
using InABox.Core;
using InABox.Scripting;
using InABox.WPF;
using InABox.Wpf;
using Microsoft.CodeAnalysis;
using RoslynPad.Editor;
using RoslynPad.Roslyn;
using Syncfusion.Data.Extensions;
using Image = System.Windows.Controls.Image;

namespace InABox.DynamicGrid
{
    /// <summary>
    ///     Interaction logic for ScriptEditor.xaml
    /// </summary>
    public partial class ScriptEditorWindow : ThemableWindow
    {
        public static RoutedCommand SaveCommand = new();

        private bool _bChanged;

        private string _editorTitle = "ScriptEditor";

        public string EditorTitle
        {
            get => _editorTitle;
            set {
                _editorTitle = value;
                UpdateTitle();
            }
        }

        private RoslynHost _host;

        private readonly SyntaxLanguage _language = SyntaxLanguage.CSharp;

        private readonly string _script = "";

        private Dictionary<string, string[]> _snippets = new();

        private bool bCompiled;
        private readonly Dictionary<bool, Image> compile = SetupImage(Wpf.Resources.tick);
        private readonly Dictionary<bool, Image> copy = SetupImage(Wpf.Resources.copy);
        private readonly Dictionary<bool, Image> cut = SetupImage(Wpf.Resources.cut);
        private readonly Dictionary<bool, Image> paste = SetupImage(Wpf.Resources.paste);
        private readonly Dictionary<bool, Image> print = SetupImage(Wpf.Resources.print);
        private readonly Dictionary<bool, Image> redo = SetupImage(Wpf.Resources.redo);
        private readonly Dictionary<bool, Image> run = SetupImage(Wpf.Resources.run);

        private readonly Dictionary<bool, Image> save = SetupImage(Wpf.Resources.disk);
        private readonly Dictionary<bool, Image> undo = SetupImage(Wpf.Resources.undo);

        static ScriptEditorWindow()
        {
            SaveCommand.InputGestures.Add(new KeyGesture(Key.S, ModifierKeys.Control));
        }

        public ScriptEditorWindow(string script, SyntaxLanguage language = SyntaxLanguage.CSharp, string scriptTitle = "ScriptEditor")
        {
            _language = language;
            _script = script;
            EditorTitle = scriptTitle;

            InitializeComponent();

            // Not Sure if we need Roslyn for XAML - need to research this
            if (language == SyntaxLanguage.CSharp || language == SyntaxLanguage.XAML)
                try
                {
                    if (ScriptDocument.Host == null)
                        ScriptDocument.Initialize();
                }
                catch (Exception e)
                {
                    MessageBox.Show("Unable to initialize Script Editor!\nPlease try again.\n\n" + e.Message);
                }

            Roslyn.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition(
                language == SyntaxLanguage.HTML
                    ? "HTML"
                    : language == SyntaxLanguage.XAML
                        ? "XML"
                        : language == SyntaxLanguage.CSS
                            ? "CSS"
                            : "C#"
            );

            Roslyn.TextArea.TextEntering += TextArea_TextEntering;

            CheckButton(SaveButton, true, save);
            CheckButton(CopyButton, false, copy);
            CheckButton(CutButton, false, cut);
            CheckButton(PasteButton, false, paste);
            CheckButton(UndoButton, false, undo);
            CheckButton(RedoButton, false, redo);
            CheckButton(PrintButton, true, print);
            CheckButton(CompileButton, language == SyntaxLanguage.CSharp || language == SyntaxLanguage.XAML, compile);
            CheckButton(RunButton, false, run);

            var timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromMilliseconds(500);
            timer.Tick += Timer_Tick;
            timer.Start();

            bChanged = false;
        }

        public string Script
        {
            get => new String(Roslyn.Text);
        }

        public Dictionary<string, string[]> Snippets
        {
            get => _snippets;
            set
            {
                _snippets = value;
                if (_snippets != null && _snippets.Any())
                {
                    SnippetPanel.Visibility = Visibility.Visible;
                    SnippetSection.ItemsSource = _snippets.Keys;
                    SnippetSection.SelectedValue = _snippets.Keys.First();
                }
                else
                {
                    SnippetPanel.Visibility = Visibility.Collapsed;
                }
            }
        }

        private bool bChanged
        {
            get => _bChanged;
            set
            {
                _bChanged = value;
                UpdateTitle();
            }
        }

        private static Dictionary<bool, Image> SetupImage(Bitmap bitmap)
        {
            return new Dictionary<bool, Image>
            {
                { true, new Image { Source = bitmap.AsBitmapImage(), MaxHeight = 32, MaxWidth = 32 } },
                { false, new Image { Source = bitmap.AsGrayScale().AsBitmapImage(), MaxHeight = 32, MaxWidth = 32 } }
            };
        }

        private event EventHandler _onSave;

        public event EventHandler OnSave
        {
            add
            {
                _onSave += value;
                SaveButton.ToolTip = "Save";
            }
            remove
            {
                _onSave -= value;
                if (_onSave == null) SaveButton.ToolTip = "Save & Close";
            }
        }

        private event EventHandler _onCompile;

        public event EventHandler OnCompile
        {
            add
            {
                _onCompile += value;
                CheckButton(CompileButton, true, compile);
            }
            remove => _onCompile -= value;
        }

        private void CheckButton(Button button, bool condition, Dictionary<bool, Image> images)
        {
            if (button.Visibility != Visibility.Visible)
                button.Visibility = Visibility.Visible;
            if (condition != button.IsEnabled)
            {
                button.IsEnabled = condition;
                button.Content = images[condition];
            }
        }

        private void UpdateTitle()
        {
            if (bChanged)
            {
                Title = _editorTitle + "*";
            }
            else
            {
                Title = _editorTitle;
            }
        }

        #region Public Interface

        public ScriptEditorWindow ClearErrors()
        {
            Errors.Items.Clear();
            return this;
        }

        public ScriptEditorWindow AddError(string error)
        {
            Errors.Items.Add(error);
            return this;
        }

        public void Save()
        {
            bChanged = false;
            if (_onSave != null)
            {
                _onSave?.Invoke(this, EventArgs.Empty);
            }
            else
            {
                DialogResult = true;
                Close();
            }
        }

        #endregion

        #region Event Handlers

        private void TextArea_TextEntering(object sender, TextCompositionEventArgs e)
        {
            if (string.Equals(e.Text, "\r"))
                e.Handled = true;
        }

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            if (_language == SyntaxLanguage.CSharp || _language == SyntaxLanguage.XAML)
            {
                Roslyn.DataContext = new ScriptDocument(_script);
            }
            else
            {
                Roslyn.Text = _script;
                bChanged = false;
            }
            //Roslyn.Text = _script;
            //Changed = DateTime.MinValue;
        }

        private void Roslyn_Loaded(object sender, RoutedEventArgs e)
        {
            var editor = (RoslynCodeEditor)sender;
            editor.Focus();

            if (_language == SyntaxLanguage.CSharp || _language == SyntaxLanguage.XAML)
            {
                var changed = bChanged;
                var script = (ScriptDocument)editor.DataContext;
                script.Id = editor.InitializeAsync(
                    ScriptDocument.Host,
                    new ClassificationHighlightColors(),
                    Directory.GetCurrentDirectory(),
                    _script,
                    SourceCodeKind.Regular
                ).Result;
                bChanged = changed;
            }
        }

        private void Roslyn_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            if (bCompiled)
            {
                bCompiled = false;
                CheckButton(RunButton, false, run);
            }
        }

        private void Roslyn_TextChanged(object sender, EventArgs e)
        {
            bChanged = true;

            if (bCompiled)
            {
                bCompiled = false;
                CheckButton(RunButton, false, run);
            }
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            CheckButton(CopyButton, !string.IsNullOrEmpty(Roslyn.SelectedText), copy);
            CheckButton(CutButton, !string.IsNullOrEmpty(Roslyn.SelectedText), cut);
            CheckButton(PasteButton, Clipboard.ContainsText(), paste);
            CheckButton(UndoButton, Roslyn.CanUndo, undo);
            CheckButton(RedoButton, Roslyn.CanRedo, redo);
        }

        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            DialogResult = true;
            Close();
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            DialogResult = false;
            Close();
        }

        private void CompileButton_Click(object sender, RoutedEventArgs e)
        {
            CheckButton(RunButton, false, run);

            if (_onCompile != null)
            {
                _onCompile.Invoke(this, EventArgs.Empty);
            }
            else
            {
                var viewModel = (ScriptDocument)Roslyn.DataContext;
                viewModel.Text = Roslyn.Text;

                Errors.Items.Clear();
                Errors.Items.Add("Compiling Script... ");
                Task.Run(() =>
                {
                    bCompiled = viewModel.Compile();
                    Dispatcher.Invoke(() =>
                    {
                        Errors.Items.Clear();
                        if (bCompiled)
                        {
                            Errors.Items.Add("Script Compiled Successfully!");
                        }
                        else
                        {
                            var errors = viewModel.Result.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
                            errors.ForEach(x => Errors.Items.Add(x));
                        }

                        CheckButton(RunButton, bCompiled, run);
                    });
                });
            }
        }

        private void RunButton_Click(object sender, RoutedEventArgs e)
        {
            var viewModel = (ScriptDocument)Roslyn.DataContext;
            try
            {
                var result = viewModel.Execute();
            }
            catch (Exception err)
            {
                MessageBox.Show(CoreUtils.FormatException(err));
            }
        }

        private void GotoLine(string line)
        {
            if (line.StartsWith("("))
            {
                var sLine = line.Substring(1).Split(',')[0];
                var iLine = int.Parse(sLine);
                Roslyn.ScrollToLine(iLine);
            }
        }

        private void Errors_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count == 1) GotoLine(e.AddedItems[0].ToString());
        }

        private void Errors_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (Errors.SelectedItems.Count == 1) GotoLine(Errors.SelectedItems[0].ToString());
        }

        private void Window_Closing(object sender, CancelEventArgs e)
        {
            if (bChanged)
            {
                var bRes = MessageBox.Show("Script has changed.  Do you wish to save this script before closing?", "Save Changes",
                    MessageBoxButton.YesNoCancel);
                if (bRes == MessageBoxResult.Cancel)
                {
                    e.Cancel = true;
                    return;
                }

                if (bRes == MessageBoxResult.Yes) _onSave?.Invoke(this, EventArgs.Empty);

                try
                {
                    DialogResult = bRes == MessageBoxResult.Yes;
                }
                catch (InvalidOperationException)
                {
                    // Do nothing, just avoids a crash when closing a script editor which was opened by Show();
                }
            }
        }

        private void SaveButton_Click(object sender, RoutedEventArgs e)
        {
            Save();
        }

        private void CopyButton_Click(object sender, RoutedEventArgs e)
        {
            Roslyn.Copy();
        }

        private void CutButton_Click(object sender, RoutedEventArgs e)
        {
            Roslyn.Cut();
        }

        private void PasteButton_Click(object sender, RoutedEventArgs e)
        {
            Roslyn.Paste();
        }

        private void UndoButton_Click(object sender, RoutedEventArgs e)
        {
            Roslyn.Undo();
        }

        private void RedoButton_Click(object sender, RoutedEventArgs e)
        {
            Roslyn.Redo();
        }

        private void PrintButton_Click(object sender, RoutedEventArgs e)
        {
            //Syncfusion.Windows.Edit.EditCommands.PrintPreview.Execute(null, Edit1);
        }

        private void Snippets_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            Roslyn.SelectedText = SnippetList.SelectedValue.ToString();
        }

        private void SnippetSection_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.AddedItems.Count == 1)
                SnippetList.ItemsSource = _snippets[e.AddedItems[0].ToString()];
            else
                SnippetList.ItemsSource = null;
        }

        private void SaveCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (_onSave != null)
            {
                bChanged = false;
                _onSave?.Invoke(this, EventArgs.Empty);
            }
        }

        private void SaveCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = _onSave != null;
        }

        #endregion
    }
}