﻿using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using IronPython.Hosting;
using IronPython.Runtime;
using Microsoft.Scripting.Hosting;
using Microsoft.Scripting.Hosting.Providers;
using Microsoft.Scripting.Hosting.Shell;

namespace RalphsDotNet.Apps.SimulationStudio.Base.Controls.PythonConsoleControl
{
    public delegate void ConsoleCreatedEventHandler(object sender, EventArgs e);

    /// <summary>
    ///     Hosts the python console.
    /// </summary>
    public class PythonConsoleHost : ConsoleHost, IDisposable
    {
        private readonly PythonTextEditor _textEditor;
        private PythonConsole _pythonConsole;
        private Thread _thread;

        public PythonConsoleHost(PythonTextEditor textEditor)
        {
            this._textEditor = textEditor;
        }

        public PythonConsole Console
        {
            get { return this._pythonConsole; }
        }

        protected override Type Provider
        {
            get { return typeof (PythonContext); }
        }

        public void Dispose()
        {
            if (this._pythonConsole != null)
            {
                this._pythonConsole.Dispose();
            }

            if (this._thread != null)
            {
                this._thread.Join();
            }
        }

        public event ConsoleCreatedEventHandler ConsoleCreated;

        /// <summary>
        ///     Runs the console host in its own thread.
        /// </summary>
        public void Run()
        {
            this._thread = new Thread(this.RunConsole) {IsBackground = true};
            this._thread.Start();
        }

        protected override CommandLine CreateCommandLine()
        {
            return new PythonCommandLine();
        }

        protected override OptionsParser CreateOptionsParser()
        {
            return new PythonOptionsParser();
        }

        /// <remarks>
        ///     After the engine is created the standard output is replaced with our custom Stream class so we
        ///     can redirect the stdout to the text editor window.
        ///     This can be done in this method since the Runtime object will have been created before this method
        ///     is called.
        /// </remarks>
        protected override IConsole CreateConsole(ScriptEngine engine, CommandLine commandLine, ConsoleOptions options)
        {
            var searchPaths = engine.GetSearchPaths();
            // ReSharper disable AssignNullToNotNullAttribute
            searchPaths.Add(Path.Combine(Path.GetDirectoryName(this.GetType().Assembly.Location), "Resources", "PyLib"));
            // ReSharper restore AssignNullToNotNullAttribute
            engine.SetSearchPaths(searchPaths);
            this.SetOutput(new PythonOutputStream(this._textEditor));
            this._pythonConsole = new PythonConsole(this._textEditor, commandLine);
            if (this.ConsoleCreated != null) this.ConsoleCreated(this, EventArgs.Empty);
            return this._pythonConsole;
        }

        protected virtual void SetOutput(PythonOutputStream stream)
        {
            this.Runtime.IO.SetOutput(stream, Encoding.UTF8);
        }

        /// <summary>
        ///     Runs the console.
        /// </summary>
        private void RunConsole()
        {
            this.Run(new[] {"-X:FullFrames"});
        }

        protected override ScriptRuntimeSetup CreateRuntimeSetup()
        {
            var srs = ScriptRuntimeSetup.ReadConfiguration();
            foreach (var langSetup in srs.LanguageSetups.Where(langSetup => langSetup.FileExtensions.Contains(".py")))
            {
                langSetup.Options["SearchPaths"] = new string[0];
            }
            return srs;
        }

        protected override void ParseHostOptions(string[] args)
        {
            // Python doesn't want any of the DLR base options.
            foreach (var s in args)
            {
                this.Options.IgnoredArgs.Add(s);
            }
        }

        protected override void ExecuteInternal()
        {
            var pc = HostingHelpers.GetLanguageContext(this.Engine) as PythonContext;
            pc.SetModuleState(typeof (ScriptEngine), this.Engine);
            base.ExecuteInternal();
        }
    }
}