﻿using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;

public class ScriptExecutor
{
	private const string SRC = @"
assert a.bar();

assert a.extension1() == 0;

assert a == b, 'a equals b'
assert not a is b, 'a is not b'

assert a in (a,), 'a is in (a,)'
assert a in (b,), 'a is in (b,)'
";

	private static void Main()
	{
		try
		{
			ScriptEngine engine = Python.CreateEngine();
			ScriptSource scriptSource = engine.CreateScriptSourceFromString(SRC, "test.py");

			engine.Runtime.LoadAssembly(typeof(ScriptExecutor).Assembly);

			ScriptScope scope = engine.CreateScope();

			scope.SetVariable("a", new Foo().Extender);
			scope.SetVariable("b", new Foo().Extender);

			scriptSource.Execute(scope);
		}
		catch (Exception ex)
		{
			Console.WriteLine(ex);
		}
		finally
		{
			Console.WriteLine("Script Finished");
			Console.ReadLine();
		}
	}
}

public interface IFoo : IBaseObject<IFoo>
{
	bool bar();
}

public class Foo : IFoo
{
	public Foo()
	{
		this.Extender = ExtendedObject<IFoo>.Create(this);
	}

	public override bool Equals(object obj)
	{
		return true;
	}

	public bool Equals(Foo other)
	{
		return true;
	}

	public override int GetHashCode()
	{
		return 0;
	}

	public bool bar()
	{
		return true;
	}

	public IExtendedObject<IFoo> Extender
	{
		get;
		private set;
	}

	public bool Equals(IFoo other)
	{
		return true;
	}

	public bool Equals(IExtendedObject<IFoo> other)
	{
		return true;
	}
}

internal class ExtendedObject<T> : IDynamicMetaObjectProvider, IExtendedObject<T>
	where T : IBaseObject<T>
{
	private readonly T BASE_OBJECT;
	internal readonly IList<object> EXTENSIONS = new List<object>();

	private ExtendedObject(T baseObject)
	{
		this.BASE_OBJECT = baseObject;
	}

	#region IDynamicMetaObjectProvider Members

	public DynamicMetaObject GetMetaObject(Expression parameter)
	{
		return new ScriptMetaObject<T>(parameter, BindingRestrictions.Empty, this);
	}

	#endregion

	#region IExtendedObject<T> Members

	public T BaseObject
	{
		get { return BASE_OBJECT; }
	}

	public IEnumerable<object> Extensions
	{
		get
		{
			for (int i = 1; i < EXTENSIONS.Count - 1; i += 1)
			{
				yield return EXTENSIONS[i];
			}
		}
	}

	#endregion

	// TODO: Cacheing of ExtendableTypes...
	internal static ExtendedObject<T> Create(T baseObject)
	{
		ExtendedObject<T> result = new ExtendedObject<T>(baseObject);

		// We put the base object into search path s_first so that exceptions are mapped to the
		// correct type.
		result.EXTENSIONS.Add(baseObject);

		// In the real code, a bunch of extensions is fetched from the plugin manager.
		result.EXTENSIONS.Add(new Extension1());		

		// We also put the base object into the search past s_last so that the base object overrides
		// extension objects.
		result.EXTENSIONS.Add(baseObject);		

		return result;
	}

	public override string ToString()
	{
		return BASE_OBJECT.ToString();
	}

	public override bool Equals(object obj)
	{
		return BASE_OBJECT.Equals(obj);
	}

	public override int GetHashCode()
	{
		return BASE_OBJECT.GetHashCode();
	}
}

internal static class MyDynamicObjectOps<T>
	where T : IBaseObject<T>
{
	public static object GetPlugin(object myDo, int index)
	{
		return ((ExtendedObject<T>)myDo).EXTENSIONS[index];
	}

	internal static MethodInfo GetPluginMethod = typeof(MyDynamicObjectOps<T>).GetMethod("GetPlugin");
}

internal class ScriptMetaObject<T> : DynamicMetaObject
	where T : IBaseObject<T>
{
	public ScriptMetaObject(Expression parameter, BindingRestrictions restrictions, ExtendedObject<T> value)
		: base(parameter, restrictions, value)
	{
	}

	public new ExtendedObject<T> Value
	{
		get
		{
			return (ExtendedObject<T>)base.Value;
		}
	}

	public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
	{
		DynamicMetaObject boundMember = null;

		for (int i = 0; i < Value.EXTENSIONS.Count; i++)
		{
			DynamicMetaObject pluginDo = new DynamicMetaObject(
				Expression.Call(
					MyDynamicObjectOps<T>.GetPluginMethod,
					Expression,
					Expression.Constant(i)
				),
				BindingRestrictions.GetInstanceRestriction(this.Expression, Value),
				Value.EXTENSIONS[i]
			);

			boundMember = binder.FallbackInvokeMember(pluginDo, args, boundMember);
		}

		return boundMember;
	}

	public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
	{
		DynamicMetaObject boundMember = null;

		for (int i = 0; i < Value.EXTENSIONS.Count; i++)
		{
			DynamicMetaObject pluginDo = new DynamicMetaObject(
				Expression.Call(
					MyDynamicObjectOps<T>.GetPluginMethod,
					Expression,
					Expression.Constant(i)
				), BindingRestrictions.GetInstanceRestriction(this.Expression, Value), Value.EXTENSIONS[i]);

			boundMember = binder.FallbackSetMember(pluginDo, value, boundMember);
		}

		return boundMember;
	}

	public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
	{
		DynamicMetaObject boundMember = null;

		for (int i = 0; i < Value.EXTENSIONS.Count; i++)
		{
			DynamicMetaObject pluginDo = new DynamicMetaObject(Expression.Call(
					MyDynamicObjectOps<T>.GetPluginMethod,
					Expression,
					Expression.Constant(i)
				), BindingRestrictions.GetInstanceRestriction(this.Expression, Value), Value.EXTENSIONS[i]);

			boundMember = binder.FallbackGetMember(pluginDo, boundMember);
		}

		return boundMember;
	}

	public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg)
	{
		// Currently only supported for equality (python == operator) equality, we bind to the base object only.
		DynamicMetaObject baseDo = new DynamicMetaObject(Expression.Call(
					MyDynamicObjectOps<T>.GetPluginMethod,
					Expression,
					Expression.Constant(0)
				), BindingRestrictions.GetInstanceRestriction(this.Expression, Value), Value.EXTENSIONS[0]);

		return binder.FallbackBinaryOperation(baseDo, arg, null);
	}
}
public interface IExtendedObject<T>
	where T : IBaseObject<T>
{
	T BaseObject { get; }

	IEnumerable<Object> Extensions { get; }
}

public interface IBaseObject<T> : IEquatable<T>, IEquatable<IExtendedObject<T>>
	where T : IBaseObject<T>
{
	IExtendedObject<T> Extender { get; }
}

public class Extension1
{
	public int extension1() { return 0; }
}
