//------------------------------------------------------------------------------
// 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.IO;
using System.Reflection;
using System.Runtime.InteropServices;

namespace SimBase
{
	public abstract class DllUnitSim : IDisposable, IUnitSim
	{
		[DllImport("kernel32", CharSet = CharSet.Unicode)]
		private extern static IntPtr LoadLibrary(string lpFileName);

		[DllImport("kernel32")]
		private extern static bool FreeLibrary(IntPtr hModule);

		[DllImport("kernel32", CharSet = CharSet.Ansi)]
		private extern static IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

		[DllImport("kernel32", CharSet = CharSet.Unicode)]
		private extern static IntPtr FindResource(IntPtr hModule, int lpName, string lpType);

		[DllImport("kernel32", CharSet = CharSet.Unicode)]
		private extern static int SizeofResource(IntPtr hModule, IntPtr hResInfo);

		[DllImport("kernel32", CharSet = CharSet.Unicode)]
		private extern static IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);

		[DllImport("kernel32", CharSet = CharSet.Unicode)]
		private extern static IntPtr LockResource(IntPtr hResData);

		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate void TKernelEvent(IntPtr Obj);
		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate void TOutputEvent(IntPtr Obj, int Kind, IntPtr Data, int Size);
		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate void TGetSystemTimeEvent(IntPtr Obj, ref long Now, ref long Frequency);

		private string m_DllName;
		private IntPtr m_Object;
		private IntPtr m_Module;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TStart();
		private TStart m_Start;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TExit();
		private TExit m_Exit;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TInterrupt(int IntNo);
		private TInterrupt m_Interrupt;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TGetUnitName(IntPtr value, int size);
		private TGetUnitName m_GetUnitName;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TSetUnitName(IntPtr value);
		private TSetUnitName m_SetUnitName;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TAssignOnSetEvent(TKernelEvent OnSetEvent, IntPtr ObjOfOnSetEvent);
		private TAssignOnSetEvent m_AssignOnSetEvent;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TAssignOnStart(TKernelEvent OnStart, IntPtr ObjOfOnStart);
		private TAssignOnStart m_AssignOnStart;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TAssignOnTerminate(TKernelEvent OnTerminate, IntPtr ObjOfOnTerminate);
		private TAssignOnTerminate m_AssignOnTerminate;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TAssignOnIdle(TKernelEvent OnIdle, IntPtr ObjOfOnIdle);
		private TAssignOnIdle m_AssignOnIdle;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TAssignOnOutput(TOutputEvent OnLog, IntPtr ObjOfOnLog);
		private TAssignOnOutput m_AssignOnOutput;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TAssignOnGetSystemTime(TGetSystemTimeEvent OnGetSystemTime, IntPtr ObjOfOnGetSystemTime);
		private TAssignOnGetSystemTime m_AssignOnGetSystemTime;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate int TReadAddr(uint Addr, IntPtr buffer, int count);
		private TReadAddr m_ReadAddr;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate int TWriteAddr(uint Addr, IntPtr buffer, int count);
		private TWriteAddr m_WriteAddr;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate bool TGetBit(uint Addr, int bit);
		private TGetBit m_GetBit;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TSetBit(uint Addr, int bit, bool value);
		private TSetBit m_SetBit;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate bool TProcessEvent();
		private TProcessEvent m_ProcessEvent;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TInput(int Kind, IntPtr Data, int Size);
		private TInput m_Input;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate long TGetTimer();
		private TGetTimer m_GetTimer;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TProgress(long Timer);
		private TProgress m_Progress;
		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
		private delegate void TCallTimeOut(long Frequency);
		private TCallTimeOut m_CallTimeOut;

		private TKernelEvent m_UnitOnSetEvent;
		private TKernelEvent m_UnitOnStart;
		private TKernelEvent m_UnitOnTerminate;
		private TKernelEvent m_UnitOnIdle;
		private TOutputEvent m_UnitOnOutput;
		private TGetSystemTimeEvent m_UnitOnGetSystemTime;

		private string m_TraceLogResource;

		public DllUnitSim(string dllName)
		{
			m_DllName = dllName;
			m_Module = LoadLibrary(dllName);

			if (m_Module == IntPtr.Zero)
				throw new DllNotFoundException(dllName + ".dllの読み込みに失敗しました。");

			m_Start = (TStart)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "Start"), typeof(TStart));
			m_Exit = (TExit)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "Exit"), typeof(TExit));
			m_Interrupt = (TInterrupt)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "Interrupt"), typeof(TInterrupt));
			m_GetUnitName = (TGetUnitName)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "GetUnitName"), typeof(TGetUnitName));
			m_SetUnitName = (TSetUnitName)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "SetUnitName"), typeof(TSetUnitName));
			m_AssignOnSetEvent = (TAssignOnSetEvent)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "AssignOnSetEvent"), typeof(TAssignOnSetEvent));
			m_AssignOnStart = (TAssignOnStart)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "AssignOnStart"), typeof(TAssignOnStart));
			m_AssignOnTerminate = (TAssignOnTerminate)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "AssignOnTerminate"), typeof(TAssignOnTerminate));
			m_AssignOnIdle = (TAssignOnIdle)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "AssignOnIdle"), typeof(TAssignOnIdle));
			m_AssignOnOutput = (TAssignOnOutput)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "AssignOnOutput"), typeof(TAssignOnOutput));
			m_AssignOnGetSystemTime = (TAssignOnGetSystemTime)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "AssignOnGetSystemTime"), typeof(TAssignOnGetSystemTime));
			m_ReadAddr = (TReadAddr)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "ReadAddr"), typeof(TReadAddr));
			m_WriteAddr = (TWriteAddr)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "WriteAddr"), typeof(TWriteAddr));
			m_GetBit = (TGetBit)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "GetBit"), typeof(TGetBit));
			m_SetBit = (TSetBit)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "SetBit"), typeof(TSetBit));
			m_ProcessEvent = (TProcessEvent)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "ProcessEvent"), typeof(TProcessEvent));
			m_Input = (TInput)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "Input"), typeof(TInput));
			m_GetTimer = (TGetTimer)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "GetTimer"), typeof(TGetTimer));
			m_Progress = (TProgress)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "Progress"), typeof(TProgress));
			m_CallTimeOut = (TCallTimeOut)Marshal.GetDelegateForFunctionPointer(GetProcAddress(m_Module, "CallTimeOut"), typeof(TCallTimeOut));

			m_Object = Marshal.GetIUnknownForObject(this);

			m_UnitOnSetEvent = new TKernelEvent(UnitOnSetEvent);
			m_AssignOnSetEvent(m_UnitOnSetEvent, m_Object);
			m_UnitOnStart = new TKernelEvent(UnitOnStart);
			m_AssignOnStart(m_UnitOnStart, m_Object);
			m_UnitOnTerminate = new TKernelEvent(UnitOnTerminate);
			m_AssignOnTerminate(m_UnitOnTerminate, m_Object);
			m_UnitOnIdle = new TKernelEvent(UnitOnIdle);
			m_AssignOnIdle(m_UnitOnIdle, m_Object);
			m_UnitOnOutput = new TOutputEvent(UnitOnOutput);
			m_AssignOnOutput(m_UnitOnOutput, m_Object);
			m_UnitOnGetSystemTime = new TGetSystemTimeEvent(UnitOnGetSystemTime);
			m_AssignOnGetSystemTime(m_UnitOnGetSystemTime, m_Object);

			IntPtr hResInfo = FindResource(m_Module, 1, "TRACELOG_RESOURCE");
			if (hResInfo == IntPtr.Zero)
				return;

			int size = SizeofResource(m_Module, hResInfo);
			IntPtr hGlobal = LoadResource(m_Module, hResInfo);

			byte[] buf = new byte[size];
			Marshal.Copy(LockResource(hGlobal), buf, 0, buf.Length);

			m_TraceLogResource = Encoding.Default.GetString(buf);
		}

		public virtual void Dispose()
		{
			m_Start = null;
			m_Exit = null;
			m_Interrupt = null;
			m_GetUnitName = null;
			m_SetUnitName = null;
			m_AssignOnStart = null;
			m_AssignOnTerminate = null;
			m_AssignOnIdle = null;
			m_AssignOnOutput = null;
			m_UnitOnGetSystemTime = null;
			m_ReadAddr = null;
			m_WriteAddr = null;
			m_GetBit = null;
			m_SetBit = null;
			m_ProcessEvent = null;
			m_Input = null;
			m_GetTimer = null;
			m_Progress = null;
			m_CallTimeOut = null;

			Marshal.Release(m_Object);
			m_Object = IntPtr.Zero;
			if (m_Module != null)
			{
				if (FreeLibrary(m_Module))
				{
					m_Module = IntPtr.Zero;
				}
			}
		}

		public string TraceLogResource { get { return m_TraceLogResource; } }

		public event UnitEventHandler UnitSetEvent;
		public event UnitEventHandler UnitStart;
		public event UnitEventHandler UnitExit;
		public event UnitEventHandler UnitIdle;
		public event UnitOutputEventHandler UnitOutput;
		public event UnitGetSystemTimeEventHandler UnitGetSystemTime;

		private static void UnitOnSetEvent(IntPtr Obj)
		{
			DllUnitSim u = (DllUnitSim)Marshal.GetObjectForIUnknown(Obj);

			if (u.UnitSetEvent == null)
				return;

			u.UnitSetEvent(u);
		}

		private static void UnitOnStart(IntPtr Obj)
		{
			DllUnitSim u = (DllUnitSim)Marshal.GetObjectForIUnknown(Obj);

			if (u.UnitStart == null)
				return;

			u.UnitStart(u);
		}

		private static void UnitOnTerminate(IntPtr Obj)
		{
			DllUnitSim u = (DllUnitSim)Marshal.GetObjectForIUnknown(Obj);

			if (u.UnitExit == null)
				return;

			u.UnitExit(u);
		}

		private static void UnitOnIdle(IntPtr Obj)
		{
			DllUnitSim u = (DllUnitSim)Marshal.GetObjectForIUnknown(Obj);

			if (u.UnitIdle == null)
				return;

			u.UnitIdle(u);
		}

		private static void UnitOnOutput(IntPtr Obj, int Kind, IntPtr Data, int Size)
		{
			DllUnitSim u = (DllUnitSim)Marshal.GetObjectForIUnknown(Obj);

			byte[] data = new byte[Size];

			Marshal.Copy(Data, data, 0, Size);

			if (Kind == 0)
			{
				System.Diagnostics.Debug.WriteLine(u.UnitName + " : " + Encoding.Unicode.GetString(data));
			}
			else if (Kind < 0)
			{
				u.Push(-Kind, data);
			}
			else if (u.UnitOutput != null)
			{
				u.UnitOutput(u, Kind, data);
			}
		}

		private static void UnitOnGetSystemTime(IntPtr Obj, ref long now, ref long frequency)
		{
			DllUnitSim u = (DllUnitSim)Marshal.GetObjectForIUnknown(Obj);

			if (u.UnitGetSystemTime == null)
				return;

			u.UnitGetSystemTime(u, ref now, ref frequency);
		}

		public void Start()
		{
			m_Start();
		}

		public void Exit()
		{
			m_Exit();
		}

		public void Interrupt(int IntNo)
		{
			m_Interrupt(IntNo);
		}

		public int ReadAddr(uint Addr, byte[] dst)
		{
			int result;

			GCHandle hResult = GCHandle.Alloc(dst, GCHandleType.Pinned);
			try
			{
				IntPtr buffer = Marshal.UnsafeAddrOfPinnedArrayElement(dst, 0);

				result = m_ReadAddr(Addr, buffer, dst.Length);
			}
			finally
			{
				hResult.Free();
			}

			return result;
		}

		public int WriteAddr(uint Addr, byte[] src)
		{
			int result;

			GCHandle hResult = GCHandle.Alloc(src, GCHandleType.Pinned);
			try
			{
				IntPtr buffer = Marshal.UnsafeAddrOfPinnedArrayElement(src, 0);

				result = m_WriteAddr(Addr, buffer, src.Length);
			}
			finally
			{
				hResult.Free();
			}

			return result;
		}

		public void SetBit(uint addr, int bit, bool value)
		{
			m_SetBit(addr, bit, value);
		}

		public bool GetBit(uint addr, int bit)
		{
			return m_GetBit(addr, bit);
		}

		public void Input(int kind, byte[] data)
		{
			GCHandle hResult = GCHandle.Alloc(data, GCHandleType.Pinned);
			try
			{
				IntPtr buffer = Marshal.UnsafeAddrOfPinnedArrayElement(data, 0);

				m_Input(kind, buffer, data.Length);
			}
			finally
			{
				hResult.Free();
			}
		}

		public long GetTimer()
		{
			return m_GetTimer();
		}

		public void Progress(long timer)
		{
			m_Progress(timer);
		}

		public void CallTimeOut(long frequency)
		{
			m_CallTimeOut(frequency);
		}

		public string UnitName
		{
			get
			{
				char[] result = new char[256];
				GCHandle hResult = GCHandle.Alloc(result, GCHandleType.Pinned);
				try
				{
					IntPtr buffer = Marshal.UnsafeAddrOfPinnedArrayElement(result, 0);

					m_GetUnitName(buffer, result.Length);
				}
				finally
				{
					hResult.Free();
				}
				int last = Array.IndexOf<char>(result, '\0');
				if (last < 0)
					last = result.Length;
				return new String(result, 0, last);
			}
			set
			{
				char[] result = value.ToCharArray();
				GCHandle hResult = GCHandle.Alloc(result, GCHandleType.Pinned);
				try
				{
					IntPtr buffer = Marshal.UnsafeAddrOfPinnedArrayElement(result, 0);

					m_SetUnitName(buffer);
				}
				finally
				{
					hResult.Free();
				}
			}
		}

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

		protected abstract void Push(int kind, byte[] Data);

		public abstract IList<IUnitInterface> Interfaces { get; }

		public event UnitInterfaceEventHandler AddInterface;
		public event UnitInterfaceEventHandler RemoveInterface;
		public event UnitInterfaceEventHandler UpdateInterface;

		protected void DoAddInterface(IUnitInterface item)
		{
			if (AddInterface != null)
				AddInterface(this, item);
		}

		protected void DoRemoveInterface(IUnitInterface item)
		{
			if (RemoveInterface != null)
				RemoveInterface(this, item);
		}

		protected void DoUpdateInterface(IUnitInterface item)
		{
			if (UpdateInterface != null)
				UpdateInterface(this, item);
		}

		protected void CallLog(int kind, byte[] data)
		{
			if (UnitOutput != null)
				UnitOutput(this, kind, data);
		}
	}
}
