using System;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.ComponentModel;
using XCircuit.COM;

namespace XCircuit.Proxy
{
	public class EventProxy
	{
		private object dispatch_obj;

		public EventProxy(object obj)
		{
			Console.WriteLine("Creating event proxy: {0}", obj);
			this.dispatch_obj = obj;
		}

		public void internal_handler(object sender, EventArgs args)
		{
			Console.WriteLine("Firing event: from={0}, to={1}, args={0}", sender, dispatch_obj, args);
			Manager.fire_event(ObjectProxy.unbox(sender), dispatch_obj, ObjectProxy.unbox(args));
		}

		private static ModuleBuilder moduleBuilder = null;
		private static Hashtable typeDict = new Hashtable();

		public static Type GetEventProxyType(Type eventHandlerType)
		{
			object val = typeDict[eventHandlerType];
			if (val == null) {
				val = typeDict[eventHandlerType] = BuildEventProxyType(eventHandlerType);
			}
			return (Type)val;
		}

		public static Type BuildEventProxyType(Type eventHandlerType)
		{
			if (moduleBuilder == null) {
				AssemblyName aname = new AssemblyName();
				aname.Name = "XCircuitInternal";
				aname.Version = new Version("1.0.0.0");
				AssemblyBuilder bld = AppDomain.CurrentDomain.DefineDynamicAssembly(aname, AssemblyBuilderAccess.Run);
				moduleBuilder = bld.DefineDynamicModule("XCircuitInternal");
			}

			MethodInfo handlerInfo = eventHandlerType.GetMethod("Invoke");
			ParameterInfo[] paramInfo = handlerInfo.GetParameters();
			Type[] argTypes = new Type[paramInfo.Length];
			for (int i=0; i<paramInfo.Length; i++)
				argTypes[i] = paramInfo[i].ParameterType;
			Type retType = handlerInfo.ReturnType;
			Console.WriteLine("handler: {0}", retType);
			foreach (Type t in argTypes)
				Console.WriteLine("  {0}", t);

			ILGenerator ilGen = null;
			TypeBuilder typeBuilder = moduleBuilder.DefineType(
					"XCircuitInternal.EventProxy"+eventHandlerType.Name,
					TypeAttributes.Public,
					typeof(EventProxy));
			
			ConstructorBuilder ctorBuilder = typeBuilder.DefineConstructor(
					MethodAttributes.Public,
					CallingConventions.Standard,
					new Type[1] { typeof(object) });
			ilGen = ctorBuilder.GetILGenerator();
			ilGen.Emit(OpCodes.Ldarg_0);
			ilGen.Emit(OpCodes.Ldarg_1);
			Console.WriteLine("Parent ctor: {0}", typeof(EventProxy).GetConstructor(new Type[1] { typeof(object) }));
			ilGen.Emit(OpCodes.Call, typeof(EventProxy).GetConstructor(new Type[1] { typeof(object) }));
			ilGen.Emit(OpCodes.Ret);

			MethodBuilder mBuilder = typeBuilder.DefineMethod(
					"Handler",
					MethodAttributes.Public,
					retType,
					argTypes);
			ilGen = mBuilder.GetILGenerator();
			ilGen.Emit(OpCodes.Ldarg_0);
			ilGen.Emit(OpCodes.Ldarg_1);
			ilGen.Emit(OpCodes.Ldarg_2);
			ilGen.Emit(OpCodes.Call, typeof(EventProxy).GetMethod("internal_handler"));
			//ilGen.Emit(OpCodes.Pop);
			ilGen.Emit(OpCodes.Ret);

			return typeBuilder.CreateType();
		}
	}

	public class ObjectProxy
	{
		private object dotnet_obj = null;
		private Type dotnet_type = null;
		private ArrayList dotnet_names = new ArrayList();
		private static int mask = 0x1000;

		public ObjectProxy(object obj)
		{
			Console.WriteLine("Creating .NET object: {0}", obj);
			this.dotnet_obj = obj;
		}

		public ObjectProxy(object obj, Type type)
		{
			Console.WriteLine("Creating .NET object: {0}, {1}", obj, type);
			this.dotnet_obj = obj;
			this.dotnet_type = type;
		}

		protected Type GetObjectType()
		{
			if (dotnet_type != null)
				return dotnet_type;
			return dotnet_obj.GetType();
		}

		public object InvokeProxy(int nameID, BindingFlags flags, object[] args)
		{
			try {
				string name = ((nameID & mask) != 0 ? 
						(string)dotnet_names[nameID & ~mask] :
						"<unknown>");
				Console.WriteLine("Invoking: {0}, {1}, {2}", name, flags, args);
				args = box(args);
				object result;
				if ((flags & (BindingFlags.SetProperty | BindingFlags.SetField)) != 0
						&& dotnet_obj != null
						&& GetObjectType().GetEvent(name) != null) {
					Console.WriteLine("Define new event handler for [{0}]::{1}", dotnet_obj, name);
					EventInfo e_info = GetObjectType().GetEvent(name);
					Type e_type = EventProxy.GetEventProxyType(e_info.EventHandlerType);
					EventProxy e_proxy = (EventProxy)Activator.CreateInstance(e_type, new object[1] {args[0]});
					Delegate handler = Delegate.CreateDelegate(e_info.EventHandlerType, e_proxy, "Handler");
					e_info.AddEventHandler(dotnet_obj, handler);
					result = null;
				} else {
					result = GetObjectType().InvokeMember(
							name,
							BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | flags,
							null,
							dotnet_obj,
							args);
				}
				Console.WriteLine("Result: {0}", result);
				return unbox(result);
			} catch (Exception e) {
				Console.WriteLine("Exception: {0}", e);
				throw e;
			}
		}

		public int GetMemberID(string name)
		{
			Console.WriteLine("Looking for object ID: " + name);
			for (int i=0; i<dotnet_names.Count; i++)
				if ((string)dotnet_names[i] == name)
					return (i | mask);
			if (GetObjectType().GetMember(name) != null) {
				dotnet_names.Add(name);
				return (dotnet_names.Count-1) | mask;
			} else
				return -1;
		}

		public static object unbox(object obj)
		{
			Type type = (obj != null ? obj.GetType() : null);
			if (type == null || type.IsPrimitive || type == typeof(string))
				return obj;
			else
				return new ObjectProxy(obj);
		}

		public static object[] box(object[] objs)
		{
			if (objs == null)
				return objs;
			else {
				object[] realObjs = new object[objs.Length];
				Type type = typeof(ObjectProxy);
				for (int i=0, k=objs.Length-1; i<objs.Length; i++, k--) {
					if (type.IsInstanceOfType(objs[k]))
						realObjs[i] = ((ObjectProxy)objs[k]).dotnet_obj;
					else
						realObjs[i] = objs[k];
					Console.WriteLine("  Arg: {0}", realObjs[i]);
					//foreach (MemberInfo m in realObjs[i].GetType().GetMembers())
					//	Console.WriteLine("    Member: {0}", m.Name);
				}
				return realObjs;
			}
		}
	}

	public class NamespaceProxy
	{
		private string ns_name;
		private ArrayList id_list = new ArrayList();
		private static Hashtable ns_hash = new Hashtable();
		private static int mask = 0x2000;

		public NamespaceProxy(string name)
		{
			ns_name = name;
		}

		public string GetNamespace()
		{
			return ns_name;
		}

		public static void CollectNamespaces()
		{
			ns_hash.Clear();
			foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
				foreach (Type t in a.GetTypes())
					if (t.Namespace != null) {
						string[] parts = t.Namespace.Split(new char[] {'.'});
						for (int i=0; i<parts.Length; i++) {
							ns_hash[String.Join(".", parts, 0, i)] = null;
						}
					}
		}

		public int GetMemberID(string name)
		{
			Console.WriteLine("Looking for namespace ID: " + name);
			for (int i=0; i<id_list.Count; i++)
				if ((string)id_list[i] == name)
					return (i | mask);

			string new_name = (ns_name != null ? ns_name + "." : "") + name;
			if (ns_hash.Contains(new_name)) {
				id_list.Add(name);
				return (id_list.Count-1) | mask;
			} else {
				Type t = Manager.find_type(new_name, false);
				if (t != null) {
					id_list.Add(name);
					return (id_list.Count-1) | mask;
				}
				return -1;
			}
		}

		public object InvokeProxy(int nameID, BindingFlags flags, object[] args)
		{
			string name = (ns_name != null ? ns_name + "." : "") + (string)id_list[nameID & ~mask];
			Console.WriteLine("Invoking namespace: {0}, {1}, {2}", name, flags, args);
			if (ns_hash.Contains(name) && (flags & (BindingFlags.GetProperty|BindingFlags.GetField)) != 0)
				return new NamespaceProxy(name);
			else {
				Type t = Manager.find_type(name, false);
				if (t == null)
					throw new Exception("Unknown type " + name);
				if ((flags & (BindingFlags.CreateInstance|BindingFlags.InvokeMethod)) != 0) {
					object result = Activator.CreateInstance(t, ObjectProxy.box(args));
					return ObjectProxy.unbox(result);
				} else if ((flags & (BindingFlags.GetProperty|BindingFlags.GetField)) != 0)
					return new ObjectProxy(null, t);
				else
					throw new Exception("Invalid operation with type " + name);
			}
		}
	}

	public class Manager
	{
		public Manager()
		{
			Assembly.LoadWithPartialName("System.Windows.Forms");
			NamespaceProxy.CollectNamespaces();
		}
		
		[DllImport("XCIRCUIT.EXE", EntryPoint="xcdotnet_fire_event", CallingConvention=CallingConvention.Cdecl)]
		public static extern void fire_event(
				[MarshalAs(UnmanagedType.IDispatch)] object sender,
				[MarshalAs(UnmanagedType.IDispatch)] object target,
				[MarshalAs(UnmanagedType.IDispatch)] object args);

		public static Type find_type(string typeName, bool throwException)
		{
			Type type;
			foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
				if ((type = a.GetType(typeName)) != null)
					return type;
			if (throwException)
				throw new Exception("Type " + typeName + " not found");
			else
				return null;
		}

		public object new_dotnet(string typeName, object[] args, bool staticFlag)
		{
			Type type = find_type(typeName, true);
			if (staticFlag)
				return new ObjectProxy(null, type);
			else {
				object result = Activator.CreateInstance(type, ObjectProxy.box(args));
				return ObjectProxy.unbox(result);
			}
		}

		public object get_top_namespace()
		{
			return new NamespaceProxy(null);
		}

		public void load_plugin(string filename)
		{
			try {
			Console.WriteLine("Loading plugin: {0}", filename);
			Assembly a = Assembly.LoadFrom(filename);
			foreach (Type t in a.GetTypes()) {
				Console.WriteLine("Considering class: {0}", t);
				foreach (MethodInfo i in t.GetMethods(BindingFlags.Static|BindingFlags.Public))
					Console.WriteLine("  {0}", i.Name);
				MethodInfo info = t.GetMethod("XC_init", BindingFlags.Static|BindingFlags.Public);
				if (info != null) {
					Console.WriteLine("Entry point found in type: {0}", t);
					info.Invoke(null, null);
					return;
				}
			}
			throw new InvalidProgramException("No entry point XC_init() found in the plugin " + filename);
			} catch (Exception e) {
				System.Windows.Forms.MessageBox.Show(
						(e.InnerException != null ? e.InnerException : e).ToString(),
						".NET plugin error",
						System.Windows.Forms.MessageBoxButtons.OK,
						System.Windows.Forms.MessageBoxIcon.Error);
			}
		}
	}
}

namespace XCircuit
{
	public class Application
	{
		/* External API */

		[DllImport("XCIRCUIT.EXE", EntryPoint="xccom_get_application", CallingConvention=CallingConvention.Cdecl)]
		internal static extern IXCircuit get_application();

		private static XCircuit.COM.XCircuit self_obj = null;

		private static void zoom_changed()
		{
			ZoomChanged(null, EventArgs.Empty);
		}
		private static void modified_changed()
		{
			ModifiedChanged(null, EventArgs.Empty);
		}

		public static XCircuit.COM.XCircuit self()
		{
			if (self_obj == null) {
				self_obj = (XCircuit.COM.XCircuit)get_application();
				self_obj.ModifiedChanged += new DXCEvents_ModifiedChangedEventHandler(modified_changed);
				self_obj.ZoomChanged += new DXCEvents_ZoomChangedEventHandler(zoom_changed);
			}
			return self_obj;
		}

		/* Regular API */

		public static void ZoomOut() { self().zoomout(); }
		public static void ZoomIn()  { self().zoomin(); }
		public static object Zoom
		{
			get
			{
				object obj = self().zoom;
				return self().zoom;
			}
			set
			{
				self().zoom = value;
			}
		}
		public static bool AutoRefresh
		{
			get { return self().autorefresh; }
			set { self().autorefresh = value; Refresh(); }
		}
		public static void Refresh() { self().refresh(); }
		public static void AddColor(System.Drawing.Color c)
		{
			self().add_color(c.R, c.G, c.B);
		}
		public static void AddColor(System.Drawing.Color c, bool select)
		{
			AddColor(c);
			if (select) {
				Color = c;
			}
		}
		public static System.Drawing.Color Color
		{
			set
			{
				self().color = value.R | (value.G << 8) | (value.B << 16);
			}
		}

		public static event EventHandler ZoomChanged;
		public static event EventHandler ModifiedChanged;
	}

	public class MenuItem
	{
		protected XCMenuItem xc_obj = null;

		private void menu_click()
		{
			Click(this, EventArgs.Empty);
		}

		private MenuItem()
		{
		}

		public MenuItem(string name, string parent) : this(name, parent, "")
		{
		}

		public MenuItem(string name, string parent, string after)
		{
			xc_obj = XCircuit.Application.self().new_menu(parent, name, false, after);
			xc_obj.Click += new DXCMenuEvents_ClickEventHandler(menu_click);
		}

		public static MenuItem GetMenuItem(string name)
		{
			XCMenuItem obj = XCircuit.Application.self().get_menu(name);
			if (obj == null)
				return null;
			MenuItem item = new MenuItem();
			item.xc_obj = obj;
			item.xc_obj.Click += new DXCMenuEvents_ClickEventHandler(item.menu_click);
			return item;
		}

		public event EventHandler Click;

		public void Reset()
		{
			Click = (EventHandler)Delegate.RemoveAll(Click, Click);
			xc_obj.reset();
		}
	}

	public abstract class ObjectBase
	{
		protected IXCObject xc_object = null;

		protected ObjectBase()
		{
		}

		protected ObjectBase(IXCObject obj)
		{
			this.xc_object = obj;
		}

		public System.Drawing.Color Color
		{
			get
			{
				int cval = xc_object.get_color();
				return System.Drawing.Color.FromArgb(
						cval & 0x000000ff,
						(cval & 0x0000ff00) >> 8,
						(cval & 0x00ff0000) >> 16);
			}
			set
			{
				int cval = (value.R | (value.G << 8) | (value.B << 16));
				xc_object.set_color(cval);
			}
		}

		public void Select()
		{
			xc_object.select(true);
		}
	}

	public class Point
	{
		protected IXCPoint xc_point = null;

		internal Point(IXCPoint obj)
		{
			this.xc_point = obj;
		}

		public short X
		{
			get { return xc_point.x; }
			set { xc_point.x = value; }
		}
		public short Y
		{
			get { return xc_point.y; }
			set { xc_point.y = value; }
		}
	}

	public enum LineStyle { Solid = 0, Dashed, Dotted, None };

	public abstract class LineObject : ObjectBase
	{
		protected LineObject() : base()
		{
		}

		protected LineObject(IXCLineObject obj) : base(obj)
		{
		}

		private static string[] style_strings = { "solid", "dashed", "dotted", "none" };
		public LineStyle Style
		{
			set { ((IXCLineObject)xc_object).border = style_strings[(int)value]; }
			get { return (LineStyle)Array.IndexOf(style_strings, ((IXCLineObject)xc_object).border); }
		}
		public bool Closed
		{
			set { ((IXCLineObject)xc_object).closed = value; }
			get { return ((IXCLineObject)xc_object).closed; }
		}
		public float LineWidth
		{
			set { ((IXCLineObject)xc_object).width = value; }
			get { return ((IXCLineObject)xc_object).width; }
		}
	}

	public class Arc : LineObject
	{
		public Arc(int cx, int cy, int r, float start, float span) : base()
		{
			this.xc_object = XCircuit.Application.self().arc(cx, cy, r, start, start+span);
		}

		public Arc(int cx, int cy, int r) : this(cx, cy, r, 0.0f, 360.0f)
		{
		}

		public Point Center
		{
			get { return new Point(((IXCArc)xc_object).center); }
		}
		public short XRadius
		{
			get { return ((IXCArc)xc_object).rx; }
			set { ((IXCArc)xc_object).rx = value; }
		}
		public short YRadius
		{
			get { return ((IXCArc)xc_object).ry; }
			set { ((IXCArc)xc_object).ry = value; }
		}
		public short Radius
		{
			set { XRadius = YRadius = value; }
		}
	}

	public class Polygon : LineObject
	{
		public class PointCollection
		{
			private Polygon owner = null;

			internal PointCollection(Polygon owner)
			{
				this.owner = owner;
			}

			public Point this[int index]
			{
				get { return new Point(((IXCPolygon)owner.xc_object).get_point(index)); }
			}

			public void Add(int x, int y)
			{
				((IXCPolygon)owner.xc_object).append(x, y);
			}
		}

		protected PointCollection points;

		public Polygon(object[] coords) : this()
		{
			this.xc_object = XCircuit.Application.self().polygon(coords);
		}

		protected Polygon() : base()
		{
			this.points = new PointCollection(this);
		}

		public PointCollection Points
		{
			get { return points; }
		}
	}

	public class Box : Polygon
	{
		public Box(int x, int y, int width, int height) : base()
		{
			this.xc_object = XCircuit.Application.self().box(x, y, width, height);
		}
	}
}
