//------------------------------------------------------------------------------
// Embedded Software Simulation Base Classes
// Copyright (C) 2010-2011 Cores Co., Ltd. Japan
//------------------------------------------------------------------------------
// $Id: Caret.cs 86 2011-04-04 09:35:04Z nagasima $
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Runtime.InteropServices;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;

namespace SimBase
{
	public delegate IUnitInterface GetInterfaceCallback(IUnitSim owner, IUnitInterface item);

	public class UnitSimAdpt<T> : IDisposable, IUnitSim, IUnitSimAdpt
		where T : class, IUnitSim
	{
		Type m_Type;
		protected T m_UnitSim;
		IInterfaceHub m_InterfaceHub;
		List<IUnitInterface> m_Interfaces = new List<IUnitInterface>();
		string m_UnitName;

		public UnitSimAdpt(T unitSim, IInterfaceHub hub)
		{
			m_Type = unitSim.GetType();
			m_UnitSim = unitSim;
			Init(hub);
		}

		public UnitSimAdpt(IInterfaceHub hub)
		{
			m_Type = typeof(T);
			Create(hub);
		}

		protected Type Type
		{
			get { return m_Type; }
		}

		protected virtual void Create(IInterfaceHub hub)
		{
			ConstructorInfo ctor = m_Type.GetConstructor(new Type[] { typeof(IInterfaceHub) });
			m_UnitSim = (T)ctor.Invoke(new object[] { hub });
			Init(hub);
		}

		public void Dispose()
		{
			foreach (IUnitInterface item1 in m_Interfaces)
			{
				UnitInterfaceAdpt item2 = (UnitInterfaceAdpt)item1;
				item2.Target = null;
			}

			IDisposable dis = m_UnitSim as IDisposable;
			m_UnitSim = default(T);
			if (dis != null)
				dis.Dispose();
		}

		public void Restart()
		{
			Dispose();

			Create(m_InterfaceHub);
		}

		protected void Init(IInterfaceHub hub)
		{
			m_InterfaceHub = hub;
			m_UnitName = m_UnitSim.UnitName;
			m_UnitSim.UnitSetEvent += new UnitEventHandler(UnitSim_UnitSetEvent);
			m_UnitSim.UnitStart += new UnitEventHandler(UnitSim_UnitStart);
			m_UnitSim.UnitExit += new UnitEventHandler(UnitSim_UnitExit);
			m_UnitSim.UnitIdle += new UnitEventHandler(UnitSim_UnitIdle);
			m_UnitSim.UnitOutput += new UnitOutputEventHandler(UnitSim_Log);
			m_UnitSim.UnitGetSystemTime += new UnitGetSystemTimeEventHandler(UnitSim_UnitGetSystemTime);
			m_UnitSim.AddInterface += new UnitInterfaceEventHandler(UnitSim_AddInterface);
			m_UnitSim.RemoveInterface += new UnitInterfaceEventHandler(UnitSim_RemoveInterface);
			m_UnitSim.UpdateInterface += new UnitInterfaceEventHandler(UnitSim_UpdateInterface);
			foreach (IUnitInterface item in m_UnitSim.Interfaces)
			{
				UnitInterfaceAdpt oldItem = (UnitInterfaceAdpt)m_Interfaces.Find(
					delegate(IUnitInterface item1)
					{
						return ((UnitInterfaceAdpt)item1).InterfaceName == item.InterfaceName;
					});

				if (oldItem != null)
				{
					oldItem.Target = item;
					continue;
				}

				IUnitInterface adpt = GetOrGenerateInterface(item);
				m_Interfaces.Add(adpt);
			}
		}

		public IUnitInterface FindInterface(string name)
		{
			return m_Interfaces.Find(delegate(IUnitInterface cmp)
			{
				return cmp.InterfaceName == name;
			});
		}

		public static GetInterfaceCallback GetInterface;

		private IUnitInterface GetOrGenerateInterface(IUnitInterface item)
		{
			IUnitInterface uif = null;

			if (GetInterface != null)
				uif = GetInterface(this, item);

			if (uif == null)
				uif = GenerateInterface(item);

			return uif;
		}

		/// <summary>
		/// デフォルト値の式を返す
		/// </summary>
		/// <param name="type"></param>
		/// <returns></returns>
		private CodeExpression GetDefaultValue(Type type)
		{
			if (type == typeof(string))
				return new CodePrimitiveExpression("");
			else if (type == typeof(bool))
				return new CodePrimitiveExpression(false);
			else if (type.IsPrimitive)
				return new CodePrimitiveExpression(0);
			else if (type.IsEnum)
				return new CodeCastExpression(type, new CodePrimitiveExpression(0));
			else if (type.IsValueType)
			{
				CodeObjectCreateExpression ret = new CodeObjectCreateExpression();
				ret.CreateType = new CodeTypeReference(type);
				return ret;
			}
			else
				return new CodePrimitiveExpression(null);
		}

		static Dictionary<Type, Type> m_AdapterTypes = new Dictionary<Type, Type>();

		/// <summary>
		/// 動的にアダプタークラスを作成する
		/// </summary>
		/// <param name="item"></param>
		/// <returns></returns>
		private IUnitInterface GenerateInterface(IUnitInterface item)
		{
			Type itemType = item.GetType();
			Type[] itemInterfaces = itemType.GetInterfaces();
			Type adptType;

			if (!m_AdapterTypes.TryGetValue(itemType, out adptType))
			{
				List<string> assembles = new List<string>();
				assembles.Add(Assembly.GetExecutingAssembly().Location);
				assembles.Add(Assembly.GetAssembly(itemType).Location);

				CodePrimitiveExpression nullExp = new CodePrimitiveExpression(null);

				// 名前空間
				CodeNamespace nameSpace = new CodeNamespace("Console");

				// クラス
				string className = "UnitIFAdapter" + (m_AdapterTypes.Count + 1).ToString();
				CodeTypeDeclaration newType = new CodeTypeDeclaration(className);
				nameSpace.Types.Add(newType);

				newType.BaseTypes.Add(new CodeTypeReference(typeof(UnitInterfaceAdpt)));

				// コンストラクタ
				{
					CodeConstructor ctor = new CodeConstructor();
					newType.Members.Add(ctor);

					ctor.Attributes = MemberAttributes.Public;

					CodeParameterDeclarationExpression param1 = new CodeParameterDeclarationExpression();
					param1.Type = new CodeTypeReference(itemType);
					param1.Name = "unitIf";
					ctor.Parameters.Add(param1);

					CodeParameterDeclarationExpression param2 = new CodeParameterDeclarationExpression();
					param2.Type = new CodeTypeReference(typeof(IUnitSim));
					param2.Name = "unitSim";
					ctor.Parameters.Add(param2);

					ctor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression("unitIf"));
					ctor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression("unitSim"));
				}

				CodeFieldReferenceExpression this_Target = new CodeFieldReferenceExpression();
				this_Target.TargetObject = new CodeThisReferenceExpression();
				this_Target.FieldName = "Target";

				// インターフェイスを実装
				foreach (Type ifType in itemInterfaces)
				{
					if ((ifType == typeof(IUnitInterface) || (ifType == typeof(IUnitInterfaceAdpt))))
						continue;

					string assemblyLocation = Assembly.GetAssembly(ifType).Location;
					if (!assembles.Contains(assemblyLocation))
						assembles.Add(assemblyLocation);

					newType.BaseTypes.Add(new CodeTypeReference(ifType));

					CodeCastExpression if_Target = new CodeCastExpression();
					if_Target.Expression = this_Target;
					if_Target.TargetType = new CodeTypeReference(ifType);

					// ラッパープロパティの実装
					foreach (PropertyInfo pInfo in ifType.GetProperties())
					{
						CodeMemberProperty prop = new CodeMemberProperty();
						newType.Members.Add(prop);

						prop.PrivateImplementationType = new CodeTypeReference(ifType);
						prop.Attributes = (MemberAttributes)0;
						prop.Name = pInfo.Name;
						prop.Type = new CodeTypeReference(pInfo.PropertyType);

						assemblyLocation = Assembly.GetAssembly(pInfo.PropertyType).Location;
						if (!assembles.Contains(assemblyLocation))
							assembles.Add(assemblyLocation);

						List<CodeExpression> indexes = new List<CodeExpression>();

						ParameterInfo[] paramInfos = pInfo.GetIndexParameters();
						foreach (ParameterInfo paramInfo in paramInfos)
						{
							CodeParameterDeclarationExpression param = new CodeParameterDeclarationExpression();
							prop.Parameters.Add(param);

							FieldDirection dir = (FieldDirection)0;
							if (paramInfo.IsIn)
								dir |= FieldDirection.In;
							if (paramInfo.IsOut)
								dir |= FieldDirection.Out;
							if (paramInfo.IsRetval)
								dir |= FieldDirection.Ref;
							param.Direction = dir;
							param.Type = new CodeTypeReference(paramInfo.ParameterType);
							param.Name = paramInfo.Name;

							assemblyLocation = Assembly.GetAssembly(paramInfo.ParameterType).Location;
							if (!assembles.Contains(assemblyLocation))
								assembles.Add(assemblyLocation);

							indexes.Add(new CodeArgumentReferenceExpression(paramInfo.Name));
						}

						prop.HasGet = pInfo.GetGetMethod() != null;
						if (prop.HasGet)
						{
							CodeConditionStatement ifState = new CodeConditionStatement();
							prop.GetStatements.Add(ifState);

							CodeBinaryOperatorExpression ifStateCondition = new CodeBinaryOperatorExpression();
							ifStateCondition.Operator = CodeBinaryOperatorType.IdentityEquality;
							ifStateCondition.Left = this_Target;
							ifStateCondition.Right = nullExp;
							ifState.Condition = ifStateCondition;

							CodeMethodReturnStatement trueRetState = new CodeMethodReturnStatement();
							ifState.TrueStatements.Add(trueRetState);
							trueRetState.Expression = GetDefaultValue(pInfo.PropertyType);

							CodeMethodReturnStatement falseRetState = new CodeMethodReturnStatement();
							ifState.FalseStatements.Add(falseRetState);
							if (paramInfos.Length > 0)
							{
								falseRetState.Expression = new CodeArrayIndexerExpression(if_Target, indexes.ToArray());
							}
							else
								falseRetState.Expression = new CodeFieldReferenceExpression(if_Target, pInfo.Name);
						}
						prop.HasSet = pInfo.GetSetMethod() != null;
						if (prop.HasSet)
						{
							CodeConditionStatement ifState = new CodeConditionStatement();
							prop.SetStatements.Add(ifState);

							CodeBinaryOperatorExpression ifStateCondition = new CodeBinaryOperatorExpression();
							ifStateCondition.Operator = CodeBinaryOperatorType.IdentityInequality;
							ifStateCondition.Left = this_Target;
							ifStateCondition.Right = nullExp;
							ifState.Condition = ifStateCondition;

							CodeAssignStatement trueRetState = new CodeAssignStatement();
							ifState.TrueStatements.Add(trueRetState);
							if (paramInfos.Length > 0)
							{
								trueRetState.Left = new CodeArrayIndexerExpression(if_Target, indexes.ToArray());
							}
							else
								trueRetState.Left = new CodeFieldReferenceExpression(if_Target, pInfo.Name);
							trueRetState.Right = new CodePropertySetValueReferenceExpression();
						}
					}

					// ラッパーメソッドの実装
					foreach (MethodInfo mInfo in ifType.GetMethods())
					{
						if (mInfo.IsSpecialName)
							continue;

						CodeMemberMethod method = new CodeMemberMethod();
						newType.Members.Add(method);

						method.PrivateImplementationType = new CodeTypeReference(ifType);
						method.Attributes = (MemberAttributes)0;
						method.Name = mInfo.Name;
						method.ReturnType = new CodeTypeReference(mInfo.ReturnType);

						assemblyLocation = Assembly.GetAssembly(mInfo.ReturnType).Location;
						if (!assembles.Contains(assemblyLocation))
							assembles.Add(assemblyLocation);

						List<CodeExpression> prms = new List<CodeExpression>();

						ParameterInfo[] paramInfos = mInfo.GetParameters();
						foreach (ParameterInfo paramInfo in paramInfos)
						{
							CodeParameterDeclarationExpression param = new CodeParameterDeclarationExpression();
							method.Parameters.Add(param);

							FieldDirection dir = (FieldDirection)0;
							if (paramInfo.IsIn)
								dir |= FieldDirection.In;
							if (paramInfo.IsOut)
								dir |= FieldDirection.Out;
							if (paramInfo.IsRetval)
								dir |= FieldDirection.Ref;
							param.Direction = dir;
							param.Type = new CodeTypeReference(paramInfo.ParameterType);
							param.Name = paramInfo.Name;

							assemblyLocation = Assembly.GetAssembly(paramInfo.ParameterType).Location;
							if (!assembles.Contains(assemblyLocation))
								assembles.Add(assemblyLocation);

							CodeArgumentReferenceExpression prm = new CodeArgumentReferenceExpression(paramInfo.Name);
							if (dir != (FieldDirection)0)
								prms.Add(new CodeDirectionExpression(dir, prm));
							else
								prms.Add(prm);
						}

						CodeMethodReferenceExpression cmre = new CodeMethodReferenceExpression(if_Target, method.Name);
						if (mInfo.ReturnType == typeof(void))
						{
							CodeConditionStatement ifState = new CodeConditionStatement();
							method.Statements.Add(ifState);

							CodeBinaryOperatorExpression ifStateCondition = new CodeBinaryOperatorExpression();
							ifStateCondition.Operator = CodeBinaryOperatorType.IdentityInequality;
							ifStateCondition.Left = this_Target;
							ifStateCondition.Right = nullExp;
							ifState.Condition = ifStateCondition;

							ifState.TrueStatements.Add(new CodeMethodInvokeExpression(cmre, prms.ToArray()));
						}
						else
						{
							CodeConditionStatement ifState = new CodeConditionStatement();
							method.Statements.Add(ifState);

							CodeBinaryOperatorExpression ifStateCondition = new CodeBinaryOperatorExpression();
							ifStateCondition.Operator = CodeBinaryOperatorType.IdentityEquality;
							ifStateCondition.Left = this_Target;
							ifStateCondition.Right = nullExp;
							ifState.Condition = ifStateCondition;

							CodeMethodReturnStatement trueRetState = new CodeMethodReturnStatement();
							ifState.TrueStatements.Add(trueRetState);
							trueRetState.Expression = GetDefaultValue(mInfo.ReturnType);

							CodeMethodReturnStatement falseRetState = new CodeMethodReturnStatement();
							ifState.FalseStatements.Add(falseRetState);
							falseRetState.Expression = new CodeMethodInvokeExpression(cmre, prms.ToArray());
						}
					}
				}

				CodeCompileUnit compileUnit = new CodeCompileUnit();
				compileUnit.Namespaces.Add(nameSpace);

				compileUnit.ReferencedAssemblies.AddRange(assembles.ToArray());

				CSharpCodeProvider provider = new CSharpCodeProvider();

				CompilerParameters cplparam = new CompilerParameters();
				cplparam.GenerateInMemory = true;

				CodeCompileUnit[] units = { compileUnit };
				CompilerResults result = provider.CompileAssemblyFromDom(cplparam, units);

				if (result.Errors.Count > 0)
				{
					CodeGeneratorOptions option = new CodeGeneratorOptions();
					StringBuilder sb = new StringBuilder();
					TextWriter tw = new StringWriter(sb);

					provider.GenerateCodeFromCompileUnit(compileUnit, tw, option);

					tw.Close();

					System.Diagnostics.Debug.WriteLine(sb);
					foreach (CompilerError text in result.Errors)
						System.Diagnostics.Debug.WriteLine(text.ErrorText);

					return new UnitInterfaceAdpt(item, this);
				}

				Assembly assembly = result.CompiledAssembly;

				adptType = assembly.GetType("Console." + className);

				m_AdapterTypes.Add(itemType, adptType);
			}

			ConstructorInfo ctorInfo = adptType.GetConstructor(new Type[] { itemType, typeof(IUnitSim) });
			return (IUnitInterface)ctorInfo.Invoke(new object[] { item, this });
		}

		public T Target
		{
			get { return m_UnitSim; }
		}

		IUnitSim IUnitSimAdpt.Target
		{
			get { return m_UnitSim; }
		}

		public static T GetTarget(IUnitSim unit)
		{
			IUnitSimAdpt adpt = unit as IUnitSimAdpt;
			if (adpt == null)
			{
				T bare = unit as T;
				if (bare != null)
					return bare;
				return null;
			}

			return adpt.Target as T;
		}

		void UnitSim_UnitSetEvent(IUnitSim sender)
		{
			if (UnitSetEvent != null)
				UnitSetEvent(this);
		}

		void UnitSim_UnitStart(IUnitSim sender)
		{
			if (UnitStart != null)
				UnitStart(this);
		}

		void UnitSim_UnitExit(IUnitSim sender)
		{
			if (UnitExit != null)
				UnitExit(this);
		}

		void UnitSim_UnitIdle(IUnitSim sender)
		{
			if (UnitIdle != null)
				UnitIdle(this);
		}

		void UnitSim_Log(IUnitSim sender, int kind, byte[] data)
		{
			if (UnitOutput != null)
				UnitOutput(this, kind, data);
		}

		void UnitSim_UnitGetSystemTime(IUnitSim sender, ref long now, ref long frequency)
		{
			if (UnitGetSystemTime != null)
				UnitGetSystemTime(this, ref now, ref frequency);
		}

		void UnitSim_AddInterface(IUnitSim sender, IUnitInterface item)
		{
			IUnitInterface adpt = GetOrGenerateInterface(item);
			m_Interfaces.Add(adpt);
			if (AddInterface != null)
				AddInterface(this, adpt);
		}

		void UnitSim_RemoveInterface(IUnitSim sender, IUnitInterface item)
		{
			foreach (IUnitInterface item1 in m_Interfaces)
			{
				UnitInterfaceAdpt item2 = (UnitInterfaceAdpt)item1;
				if (item2.Target == item)
				{
					item2.Target = null;
					break;
				}
			}
		}

		void UnitSim_UpdateInterface(IUnitSim sender, IUnitInterface item)
		{
			if (UpdateInterface != null)
			{
				foreach (IUnitInterface item1 in m_Interfaces)
				{
					UnitInterfaceAdpt item2 = (UnitInterfaceAdpt)item1;
					if (item2.Target == item)
					{
						UpdateInterface(this, item2);
						break;
					}
				}
			}
		}

		#region IUnitSim メンバ

		public string UnitName
		{
			get { if (m_UnitSim != null) m_UnitName = m_UnitSim.UnitName; return m_UnitName; }
			set { m_UnitName = value; if (m_UnitSim != null) m_UnitSim.UnitName = value; }
		}

		public event UnitEventHandler UnitSetEvent;

		public event UnitEventHandler UnitStart;

		public event UnitEventHandler UnitExit;

		public event UnitEventHandler UnitIdle;

		public event UnitOutputEventHandler UnitOutput;

		public event UnitGetSystemTimeEventHandler UnitGetSystemTime;

		public event UnitInterfaceEventHandler AddInterface;

		public event UnitInterfaceEventHandler RemoveInterface;

		public event UnitInterfaceEventHandler UpdateInterface;

		public void Start()
		{
			if (m_UnitSim != null)
				m_UnitSim.Start();
		}

		public void Exit()
		{
			if (m_UnitSim != null)
				m_UnitSim.Exit();
		}

		public IList<IUnitInterface> Interfaces
		{
			get { return m_Interfaces.AsReadOnly(); }
		}

		public void Interrupt(int IntNo)
		{
			if (m_UnitSim != null)
				m_UnitSim.Interrupt(IntNo);
		}

		public void Input(int kind, byte[] Data)
		{
			if (m_UnitSim != null)
				m_UnitSim.Input(kind, Data);
		}

		public int ReadAddr(uint addr, byte[] dst)
		{
			if (m_UnitSim != null)
				return m_UnitSim.ReadAddr(addr, dst);
			return 0;
		}

		public int WriteAddr(uint addr, byte[] src)
		{
			if (m_UnitSim != null)
				return m_UnitSim.WriteAddr(addr, src);
			return 0;
		}

		public bool GetBit(uint addr, int bit)
		{
			if (m_UnitSim != null)
				return m_UnitSim.GetBit(addr, bit);
			return false;
		}

		public void SetBit(uint addr, int bit, bool value)
		{
			if (m_UnitSim != null)
				m_UnitSim.SetBit(addr, bit, value);
		}

		public long GetTimer()
		{
			if (m_UnitSim != null)
				return m_UnitSim.GetTimer();
			return -1;
		}

		public void Progress(long timer)
		{
			if (m_UnitSim != null)
				m_UnitSim.Progress(timer);
		}

		public void CallTimeOut(long frequency)
		{
			if (m_UnitSim != null)
				m_UnitSim.CallTimeOut(frequency);
		}

		public bool ProcessEvent()
		{
			if (m_UnitSim != null)
				return m_UnitSim.ProcessEvent();
			return false;
		}

		#endregion
	}
}
