//------------------------------------------------------------------------------
// Embedded Software Simulation Base Classes
// Copyright (C) 2010-2011 Cores Co., Ltd. Japan
//------------------------------------------------------------------------------
// $Id: StateMachine.cs 88 2011-04-05 11:03:57Z nagasima $
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace SimBase
{
	/// <summary>
	/// 状態マシン
	/// </summary>
	public class StateMachine<TEvent>
	{
		/// <summary>
		/// イベント情報インターフェイス
		/// </summary>
		public interface IEventInfo
		{
			/// <summary>表示名称</summary>
			string Description { get; }
			/// <summary>
			/// イベント判定
			/// </summary>
			/// <param name="sm">状態マシン</param>
			/// <param name="e">イベント</param>
			/// <returns>true：処理対象</returns>
			bool IsTarget(StateMachine<TEvent> sm, TEvent e);
			/// <summary>
			/// イベント処理
			/// </summary>
			/// <param name="sm">状態マシン</param>
			/// <param name="e">イベント</param>
			/// <returns>次の状態</returns>
			IStateInfo DoEvent(StateMachine<TEvent> sm, TEvent e);
		}

		/// <summary>
		/// 状態情報インターフェイス
		/// </summary>
		public interface IStateInfo
		{
			/// <summary>表示名称</summary>
			string Description { get; }
			/// <summary>イベント情報</summary>
			IEnumerable<IEventInfo> Events { get; }
			/// <summary>タイムアウト時間</summary>
			int DefaultTimeOut { get; }
			/// <summary>
			/// タイムアウト処理
			/// </summary>
			/// <param name="sm">状態マシン</param>
			/// <returns>次の状態</returns>
			IStateInfo DoTimeOut(StateMachine<TEvent> sm);
		}

		/// <summary>
		/// イベント情報
		/// </summary>
		public class EventInfo : IEventInfo
		{
			private string m_Description;
			private MethodInfo m_IsTarget;
			private MethodInfo m_DoEvent;

			/// <summary>
			/// イベント情報の構築
			/// </summary>
			/// <param name="description">表示名称</param>
			/// <param name="typeWithIsTarget">isTargetを持つクラスの型</param>
			/// <param name="isTarget">イベント判定のメソッド名</param>
			/// <param name="typeWithDoEvent">doEventを持つクラスの型</param>
			/// <param name="doEvent">イベント処理のメソッド名</param>
			public EventInfo(string description, Type typeWithIsTarget, string isTarget, Type typeWithDoEvent, string doEvent)
			{
				m_Description = description;
				BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

				m_IsTarget = typeWithIsTarget.GetMethod(isTarget, flags, null, new Type[] { typeof(TEvent) }, null);
				if (m_IsTarget == null)
					throw new ArgumentException(typeWithIsTarget.Name + "クラスに" + isTarget + "というメソッドが見つかりません。", "isTarget");
				if (m_IsTarget.ReturnType != typeof(bool))
					throw new ArgumentException(typeWithIsTarget.Name + "クラスの" + isTarget + "メソッドは戻り値の型がboolでなければなりません。", "isTarget");

				m_DoEvent = typeWithDoEvent.GetMethod(doEvent, flags, null, new Type[] { typeof(TEvent) }, null);
				if (m_DoEvent == null)
					throw new ArgumentException(typeWithDoEvent.Name + "クラスに" + doEvent + "というメソッドが見つかりません。", "doEvent");
				if (m_DoEvent.ReturnType != typeof(IStateInfo))
					throw new ArgumentException(typeWithDoEvent.Name + "クラスの" + doEvent + "メソッドは戻り値の型がIStateInfoでなければなりません。", "doEvent");
			}

			/// <summary>表示名称</summary>
			public string Description
			{
				get { return m_Description; }
			}

			/// <summary>
			/// イベントが処理対象かの判定
			/// </summary>
			/// <param name="sm">状態マシン</param>
			/// <param name="e">イベント情報</param>
			/// <returns>true：処理対象</returns>
			public bool IsTarget(StateMachine<TEvent> sm, TEvent e)
			{
				return (bool)m_IsTarget.Invoke(sm, new object[] { e });
			}

			/// <summary>
			/// イベント処理
			/// </summary>
			/// <param name="sm">状態マシン</param>
			/// <param name="e">イベント情報</param>
			/// <returns>次の状態</returns>
			public IStateInfo DoEvent(StateMachine<TEvent> sm, TEvent e)
			{
				return (IStateInfo)m_DoEvent.Invoke(sm, new object[] { e });
			}
		}

		/// <summary>
		/// 状態情報
		/// </summary>
		public class StateInfo : IStateInfo
		{
			private string m_Description;
			private IEnumerable<IEventInfo> m_Events;
			private int m_DefaultTimeOut;
			private MethodInfo m_DoTimeOut;

			/// <summary>
			/// 状態情報の構築
			/// </summary>
			/// <param name="description">表示名称</param>
			/// <param name="events">イベント情報</param>
			/// <param name="defaultTimeOut">タイムアウト時間</param>
			/// <param name="type">doTimeoutを持つクラスの型</param>
			/// <param name="doTimeout">タイムアウト処理のメソッド名</param>
			public StateInfo(string description, IEnumerable<IEventInfo> events, int defaultTimeOut, Type type, string doTimeout)
			{
				m_Description = description;
				m_Events = events;
				m_DefaultTimeOut = defaultTimeOut;
				if (String.IsNullOrEmpty(doTimeout))
				{
					if (defaultTimeOut != -1)
						throw new ArgumentException("doTimeoutに値を設定しない場合、defaultTimeOutは-1に設定しなければなりません。", "defaultTimeOut");
				}
				else
				{
					BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
					m_DoTimeOut = type.GetMethod(doTimeout, flags, null, Type.EmptyTypes, null);
					if (m_DoTimeOut == null)
						throw new ArgumentException(type.Name + "クラスに" + doTimeout + "というメソッドが見つかりません。", "doTimeout");
					if (m_DoTimeOut.ReturnType != typeof(IStateInfo))
						throw new ArgumentException(type.Name + "クラスの" + doTimeout + "メソッドは戻り値の型がIStateInfoでなければなりません。", "doTimeout");
				}
			}

			/// <summary>表示名称</summary>
			public string Description
			{
				get { return m_Description; }
			}

			/// <summary>イベント情報</summary>
			public IEnumerable<IEventInfo> Events
			{
				get { return m_Events; }
			}

			/// <summary>タイムアウト時間</summary>
			public int DefaultTimeOut
			{
				get { return m_DefaultTimeOut; }
			}

			/// <summary>
			/// タイムアウト処理
			/// </summary>
			/// <param name="sm">状態マシン</param>
			/// <returns>次の状態</returns>
			public IStateInfo DoTimeOut(StateMachine<TEvent> sm)
			{
				return (IStateInfo)m_DoTimeOut.Invoke(sm, null);
			}
		}

		private IEnumerable<IStateInfo> m_States;
		private IStateInfo m_State;
		private int m_Timer;
		private int? m_TimeOut;

		/// <summary>
		/// ステートマシンの構築
		/// </summary>
		/// <param name="states"></param>
		public StateMachine(IEnumerable<IStateInfo> states)
		{
			m_States = states;
		}

		/// <summary>有限状態</summary>
		public IEnumerable<IStateInfo> States
		{
			get { return m_States; }
		}

		/// <summary>現在の状態</summary>
		public IStateInfo State
		{
			get { return m_State; }
		}

		/// <summary>タイマー</summary>
		public int Timer
		{
			get { return m_Timer; }
		}

		/// <summary>次の状態のタイムアウト値(nullなら状態のデフォルト値)</summary>
		public int? TimeOut
		{
			get { return m_TimeOut; }
			set { m_TimeOut = value; }
		}

		/// <summary>
		/// 初期化
		/// </summary>
		public void Init()
		{
			m_State = DoInit();
			m_Timer = m_State.DefaultTimeOut;
		}

		/// <summary>
		/// 初期化
		/// </summary>
		protected virtual IStateInfo DoInit()
		{
			IEnumerator<IStateInfo> stateEnum = m_States.GetEnumerator();
			stateEnum.Reset();
			stateEnum.MoveNext();
			return stateEnum.Current;
		}

		/// <summary>
		/// イベント処理
		/// </summary>
		/// <param name="e">イベント要因</param>
		/// <returns>処理の有無</returns>
		public bool DoEvent(TEvent e)
		{
			// 未初期化の場合
			if (m_State == null)
				return false;

			foreach (IEventInfo ei in m_State.Events)
			{
				if (ei.IsTarget(this, e))
				{
					IStateInfo si = ei.DoEvent(this, e);
					if (si != null)
					{
						m_State = si;
						if (m_TimeOut.HasValue)
						{
							m_Timer = m_TimeOut.Value;
							m_TimeOut = null;
						}
						else
							m_Timer = m_State.DefaultTimeOut;
					}
					return true;
				}
			}

			return false;
		}

		/// <summary>
		/// 時間経過
		/// </summary>
		/// <param name="timer">経過時間</param>
		public void Progress(int timer)
		{
			if (m_Timer == -1)
				return;

			m_Timer -= timer;
			if (m_Timer < 0)
				m_Timer = 0;
		}

		/// <summary>
		/// タイムアウト処理
		/// </summary>
		public void CallTimeOut()
		{
			if (m_Timer == -1)
				return;

			if (m_Timer == 0)
			{
				m_State = m_State.DoTimeOut(this);
				if (m_TimeOut.HasValue)
				{
					m_Timer = m_TimeOut.Value;
					m_TimeOut = null;
				}
				else
					m_Timer = m_State.DefaultTimeOut;
			}
		}
	}
}
