using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System;
using System.Net;
namespace MiMic.CsApi
{
	public delegate void MiMicRemoteMcuInterfaceEvent(bool i_val);
	/**
	 * MiMicRemoteMcuInterfaceクラスは、MCUで動作するMiMicRemoteMcuとの通信機能と、接続状態の監視機能を提供する。低レベルAPI全てを実装する。
	 * 低レベルAPIは、MiMicRemoteMcuとの通信を、関数コールに変換する。
	 * インスタンス生成直後は、MiMicRemoteMcuとの接続は断状態である。connect関数を実行して、接続する必要がある。
	 * 通信仕様については、MiMicVM.pdf Appendix 1.MiMicVM HTTP Interfaceを参照すること。
	 * <p>
	 * Unity向けの仕様<br/>
	 * Unity向けの特殊仕様として、non-BlockingなAPI呼び出し(execBcNB)がある。この関数は、UnityのWWWクラスと同じシーケンスでRPCコールを行う為のものである。
	 * </p>
	 */	
	public class MiMicRemoteMcuInterface
	{
		/**
		 * Standard MiMic RPC result.
		 * MiMicVMの処理結果を格納する。execBc関数が返却する。
		 */
		public class MiMicResult
		{
			/**
			 * @param i_result
			 * The valiable that accepts MVM result.
			 */ 
			protected void _parseMvmResult(string i_jso_str)
			{
				JSONObject jso=new JSONObject(i_jso_str);
				string ver=jso.GetField("version").str;
				string[] l=ver.Split(';');
				if((l[0].IndexOf("MiMicVM/1.")==0) && (l[1]=="Json/1.0")){
					//MiMicVM/1.x;Json/1.0ならOK
					if(jso.HasField("result")){
						this._result=(UInt32)(jso.GetField("result").n);
						if(jso.HasField("stream")){
							JSONObject st=jso.GetField("stream");
							for(int i=0;i<st.Count;i++){
								this._stream.Add((UInt32)st[i].n);
							}
						}
						return;
					}
				}
				//something wrong.
				throw new MiMicException("Invalid json version:'"+ver+"'");
			}
			protected UInt32 _result;
			protected List<UInt32> _stream=new List<UInt32>();
			/**
			 * 継承用のコンストラクタ
			 */
			protected MiMicResult()
			{
			}
			public MiMicResult(string i_jso_str)
			{
				this._parseMvmResult(i_jso_str);
				return;
			}
			/**
			 * MiMicVMの処理コード。
			 * 詳細は、 MiMicVM.pdf Appendix 1. MiMicVM HTTP Interfaceを参照。
			 */
			public virtual UInt32 result
			{
				get{return this._result;}
			}
			/**
			 * MiMicVMの返却したストリームデータ。
			 * 詳細は、 MiMicVM.pdf Appendix 1. MiMicVM HTTP Interfaceを参照。
			 */
			public virtual List<UInt32> stream
			{
				get{return this._stream;}
			}
		}
		/**
		 * Non blocking MiMic RPC result.
		 * MiMicVMの処理結果を格納する。NonBlocking処理に関する拡張である。
		 * インスタンスはisDoneプロパティがtrueになるまで、所有するプロパティを返却できない。
		 */
		public class NBMiMicResult:MiMicResult
		{
			private WWW _www;
			private bool _can_read;
			private void update()
			{
				if(this._can_read){
					return;
				}
				if(this._www.isDone){
					this._parseMvmResult(this._www.text);
					this._can_read=true;
				}
			}
			public NBMiMicResult(WWW i_www):base()
			{
				this._can_read=false;
				this._www=i_www;
			}
			/**
			 * @return
			 * trueなら、プロパティresultとstreamが利用できる。
			 */
			public bool isDone
			{
				get{
					return this._www.isDone;
				}
			}
			public override UInt32 result
			{
				get{
					update();
					return this._result;
				}
			}
			public override List<UInt32> stream
			{
				get{
					update();
					return this._stream;
				}
			}

		}		
		private string _mimic_host;
		private struct TIsOnliineJsoData
		{
			public string name;
			public int major;
			public int minor;
		}
		private static void parse_version_tag(string i_s,ref TIsOnliineJsoData o)
		{
			string[] t=i_s.Split('/');
			string[] n=t[1].Split('.');
			o.name=t[0];
			o.major=Int32.Parse(n[0]);
			o.minor=Int32.Parse(n[1]);
		}
		/**
		 * This function parses JSO response, confirms validation.
		 */
		private static bool _isOnline_parseResponse(string i_s)
		{
			JSONObject jso=new JSONObject(i_s);
//			MiMicLib.log(jso.GetField("application").toString());
			//"MiMicRemoteMCU/1.n;xならOK.(n>=1)
			string[] l=jso.GetField("application").str.Split(';');
			TIsOnliineJsoData rmcu=new TIsOnliineJsoData();				
			parse_version_tag(l[0],ref rmcu);//RemoteMcu
			string mcut=l[1];//MCUTYPE
			bool ret=((rmcu.name=="MiMicRemoteMCU") && (rmcu.major==1) && (rmcu.minor>=1));
			return ret;
		}

		private MonoBehaviour _bh;
		private string _cbname;
		/**
		 * @param i_bh
		 * owner behaviour
		 * @param i_cbname
		 * callback function name for async xhr request.
		 */
		public MiMicRemoteMcuInterface(MonoBehaviour i_bh,string i_cbname,string i_server)
		{
			this._cbname=i_cbname;
			this._bh=i_bh;
			this._mimic_host=i_server;
			this._session_url="http://"+this._mimic_host+"/status.api";
			this._session_www=null;
			this._bc_url_prefix="http://"+this._mimic_host+"/mvm.api?v=1&bc=";
		}

		private static string _xhrGet(string i_url)
		{
			WWW www = new WWW(i_url);
			//非同期リクエストがあるなら終了まち.
			while(!www.isDone);//blocking
			return www.text;
		}
		private static WWW _xhrGetNb(string i_url)
		{
			WWW w=new WWW(i_url);
			return w;
		}
		public void connect(MiMicRemoteMcuInterfaceEvent i_callback)
		{			
			//接続中ならおわり.
			if(this._session_www!=null){
				return;
			}
			//同期チェック.
			string res=_xhrGet(this._session_url);			
			if(!_isOnline_parseResponse(res)){
				throw new MiMicException("Bad response from "+this._mimic_host);
			}


			//非同期監視の開始.
			this._session_cb=i_callback;
			this._session_www=_xhrGetNb(this._session_url);
			this._bh.Invoke(this._cbname,CHECK_INTERVAL);
			return;
		}
		private const int CHECK_INTERVAL=2;
		private string _session_url;
		private WWW _session_www;
		private string _bc_url_prefix;
		private MiMicRemoteMcuInterfaceEvent _session_cb;
		public bool periodic()
		{
			try{
				if(this._session_www.isDone){
					if(MiMicRemoteMcuInterface._isOnline_parseResponse(this._session_www.text)){
						this._session_cb(true);
						//online reinvoke
						this._session_www=_xhrGetNb(this._session_url);
						this._bh.Invoke(this._cbname,CHECK_INTERVAL);
						return true;
					}
				}
			}catch(Exception e){
				//例外は始末して非同期例外を生成.
			}
			//offline
			this._session_www=null;//
			this._session_cb(false);
			return false;
		}
		
		public void disconnect()
		{
			if(this._session_www!=null){
				this._session_www=null;//セッション監視wwwの削除.
			}else{
				//もはやnullなら何もしない.
				return;
			}
		}
		public bool isConnected()
		{
			return this._session_www!=null;
		}
		/**
		 */
		public MiMicResult execBc(string i_bc)
		{
			string res_str=_xhrGet(this._bc_url_prefix+i_bc);
			MiMicResult ret=new MiMicResult(res_str);
			return ret;
		}
		/**
		 * Non blocking version.
		 * This function is original method for Unity.
		 */
		public NBMiMicResult NBexecBc(string i_bc)
		{
			//MiMicLib.log(i_bc);
			return new NBMiMicResult(_xhrGetNb(this._bc_url_prefix+i_bc));
		}

	}
};
