/*
Copyright 2010 Eric Smith

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
the License.

You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License.
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using Microsoft.Scripting.Hosting;

namespace TheLimberLambda.Utils
{
	internal class PythonConverser : LineOrientedSocketConverser, INameBinder
	{
		private const string ShowPrefix = ">> ";

		private readonly ScriptRuntime _ScriptRuntime;
		private ScriptScope _ScriptScope;

		#region Nested types

		private class SocketConverserStream: Stream
		{
			private readonly Socket _Socket;

			public SocketConverserStream(Socket socket)
			{
				_Socket = socket;
			}

			public override bool CanRead
			{
				get { return false; }
			}

			public override bool CanSeek
			{
				get { return false; }
			}

			public override bool CanWrite
			{
				get { return true; }
			}

			public override long Length
			{
				get { throw new NotImplementedException(); }
			}

			public override long Position
			{
				get
				{
					throw new NotImplementedException();
				}
				set
				{
					throw new NotImplementedException();
				}
			}

			public override void Flush()
			{
			}

			public override int Read(byte[] buffer, int offset, int count)
			{
				throw new NotImplementedException();
			}

			public override long Seek(long offset, SeekOrigin origin)
			{
				throw new NotImplementedException();
			}

			public override void SetLength(long value)
			{
				throw new NotImplementedException();
			}

			public override void Write(byte[] buffer, int offset, int count)
			{
				_Socket.BeginSend(buffer, offset, count, SocketFlags.None, SentCallback, _Socket);
			}

			private static void SentCallback(IAsyncResult ar)
			{
				((Socket) ar.AsyncState).EndReceive(ar);
			}
		}

		#endregion

		internal PythonConverser(Socket socket, IEnumerable<NameBinding> nameBindings):
			base(socket)
		{
			_ScriptRuntime = IronPython.Hosting.Python.CreateRuntime();
			InitialiseScriptRuntime(socket);
			BindScriptScopeNames(nameBindings);
		}

		private void BindScriptScopeNames(IEnumerable<NameBinding> nameBindings)
		{
			foreach (var binding in nameBindings)
				_ScriptScope.SetVariable(binding.Name, binding.Target);
		}

		private void InitialiseScriptRuntime(Socket socket)
		{
			_ScriptRuntime.IO.SetOutput(new SocketConverserStream(socket), Encoding.ASCII);
			_ScriptRuntime.IO.SetErrorOutput(new SocketConverserStream(socket), Encoding.ASCII);
			_ScriptScope = _ScriptRuntime.CreateScope("py");
		}

		protected override void SomethingWasSaid(string whatWasSaid)
		{
			var scriptSource = _ScriptScope.Engine.CreateScriptSourceFromString(whatWasSaid);
			ExecuteScriptInCurrentScope(scriptSource);
		}

		private void ExecuteScriptInCurrentScope(ScriptSource scriptSource)
		{
			try
			{
				object result = scriptSource.Execute(_ScriptScope);
				if (result != null)
					Show(result);
			}
			catch (Exception e)
			{
				ShowScriptException(e);
			}
		}

		private void Show(object result)
		{
			if (WantToSeeAsList(result))
				ShowAsList(result);
			else
				ShowSingle(result);
		}

		private static bool WantToSeeAsList(object result)
		{
			if (result == null)
				throw new ArgumentNullException("result");
			return !(result is string) && result is IEnumerable;
		}

		private void ShowSingle(object toShow)
		{
			SayWithPrefix(toShow);
		}

		private void ShowAsList(object toShow)
		{
			SayWithPrefix(Bracket(String.Join(", ", QuoteAll(RenderAsStrings(toShow)).ToArray())));
		}

		private void SayWithPrefix(object toSay)
		{
			Say(ShowPrefix + toSay);
		}

		private static object Bracket(string toBracket)
		{
			return "[" + toBracket + "]";
		}

		private static IEnumerable<string> QuoteAll(IEnumerable<string> toQuote)
		{
			return toQuote.Select(element => "\"" + element + "\"");
		}

		private static IEnumerable<string> RenderAsStrings(object toRender)
		{
			return ((IEnumerable) toRender).Cast<object>().Select(element => element.ToString());
		}

		private void ShowScriptException(Exception anException)
		{
			Say(anException.ToString());
		}

		#region INameBinder implementation

		public void BindName(string name, object target)
		{
			_ScriptScope.SetVariable(name, target);
		}

		#endregion
	}
}