﻿using Nemesis;
using Nemesis.Mathematics;
using Nemesis.Programming.Compiler;
using Nemesis.Rendering;
using Nemesis.Rendering.View;
using NemesisGame.Programming.Text;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
using System.ComponentModel;
using System.Drawing;

namespace NemesisGame.Programming
{
    public sealed class CodeEditor : KeyboardOwner, ICodeEditor
    {
        private readonly DrawableTextEditor textEditor;
        private readonly MovableFloat textEditorColorAlpha = new MovableFloat(0.0f);

        private readonly BackgroundWorker codeBuilder = new BackgroundWorker();
        private object programmedObject;
        private int buildRequestsCount = 0;

        public CodeEditor(DrawableTextEditor textEditor)
        {
            this.textEditor = textEditor;
            this.codeBuilder.DoWork += codeBuilder_DoWork;
        }

        private void codeBuilder_DoWork(object sender, DoWorkEventArgs e)
        {
            programmedObject = BuildProgrammedObject();
        }

        void ICodeEditor.EnableCoding(IKeyboard newKeyboard)
        {
            textEditor.EnableTextEditing(newKeyboard);
            textEditorColorAlpha.MoveTo(1.0f, 1.0f, FloatInterpolation.QuarticOut);

            AddKeyboard(newKeyboard);
        }

        void ICodeEditor.DisableCoding()
        {
            RemoveKeyboard();

            textEditor.DisableTextEditing();
            textEditorColorAlpha.MoveTo(0.0f, 1.0f, FloatInterpolation.CubicInOut);
        }

        void ICodeEditor.SetSourceCode(string sourceCode)
        {
            var text = new EditableText(sourceCode);
            textEditor.SetEditor(new TextEditor(text));

            Build();
        }

        void ICodeEditor.SetDrawablePage(Vector3 pageCenter, SizeF pageSize, float rotationAngleInRadians)
        {
            textEditor.PageCenter = pageCenter;
            textEditor.PageSize = pageSize;
            textEditor.RotationAngleInRadians = rotationAngleInRadians;
        }

        Vector3 ICodeEditor.GetTextCenterPosition()
        {
            return textEditor.GetTextCenterPosition();
        }

        void IUpdatable.Update(float deltaTime)
        {
            textEditorColorAlpha.Update(deltaTime);
            textEditor.Update(deltaTime);

            BuildIfRequested();
        }

        private void BuildIfRequested()
        {
            if (buildRequestsCount > 0)
            {
                if (TryBuildAsync())
                {
                    --buildRequestsCount;
                }
            }
        }

        public void Build()
        {
            if (!TryBuildAsync())
            {
                RequestBuild();
            }
        }

        private bool TryBuildAsync()
        {
            if (codeBuilder.IsBusy)
            {
                return false;
            }

            codeBuilder.RunWorkerAsync();

            return true;
        }

        private object BuildProgrammedObject()
        {
            var sourceCode = GetCompilableCode();
            var program = BuildSourceCode(sourceCode);

            return (null != program) ?
                program.CreateObject("CodeEditorProgrammedObject") : null;
        }

        private CSharpProgram BuildSourceCode(string sourceCode)
        {
            try
            {
                var compiler = new CSharpCompiler();
                return compiler.Build(sourceCode);
            }
            catch { }

            return null;
        }

        private string GetCompilableCode()
        {
            const string planetCodeBase =
@"using System.Linq;

public class CodeEditorProgrammedObject
{{
  public {0}
}}";

            var planetCode = textEditor.TextString;

            return string.Format(planetCodeBase, planetCode);
        }

        object ICodeEditor.CallMethod(string methodName, params object[] args)
        {
            try
            {
                return CallMethod(programmedObject, methodName, args);
            }
            catch
            {
                // Cannot call desired method.
                // Code must be wrong. We need to rebuild it.
                programmedObject = null;
            }

            return null;
        }

        private static object CallMethod(object obj, string methodName, params object[] args)
        {
            return null != obj ?
                obj.GetType().GetMethod(methodName).Invoke(obj, args) : null;
        }

        void IDrawable.Draw(IView camera)
        {
            textEditor.PageColor = GetPageColorWithAlpha();
            textEditor.Draw(camera);
        }

        private Color4 GetPageColorWithAlpha()
        {
            var color = GetPageColor();
            color.A = new FloatInterval(color.A, 1.0f).GetValueAt(textEditorColorAlpha.CurrentValue);
            return color;
        }

        private Color4 GetPageColor()
        {
            return IsCodeCorrect() ?
                Colors.DefaultTextEditorColor : Colors.BuildFailTextEditorColor;
        }

        private bool IsCodeCorrect()
        {
            return null != programmedObject;
        }

        protected sealed override void HandleKeyChar(char charValue)
        {
            RequestBuild();
        }

        protected sealed override void HandleKey(Key key)
        {
            RequestBuild();
        }

        private void RequestBuild()
        {
            ++buildRequestsCount;
        }

        void IDrawableLayer.SetLayerPosition(float value)
        {
            textEditor.SetLayerPosition(value);
        }
    }
}
