/**
 * @fileOverview 低レベルAPIを定義する。低レベルAPIは、MiMicRemoteMcuをインストールしたMCUとの通信クラスを提供する。
 */

/**
 * MiMicが使用する関数、定数を定義する。
 * @namespace
 */
var MiMicLib=
{
	/**
	 * MiMicJsAPIのバージョン文字列。
	 */
	VERSION:"MiMicJsAPI/1.0.2",
	/**
	 * 現在の時刻を返す
	 * @returns
	 */
	getNow:function getNow(){
		return (new Date()).getTime();
	},
	/**
	 * aがundefinedであるかを真偽値で返す。
	 * @function
	 * @param {object} a
	 * 判定対象のオブジェクト
	 * @return {Boolean}
	 * aがundefinedであれば、trueである。
	 */	
	isUndef:function isUndef(a){
		return typeof a==="undefined"
	},
	/**
	 * aが配列であるかを返す。
	 * @function
	 * @param {object} a
	 * 判定対象のオブジェクト
	 * @return {Boolean}
	 * aがarrayであれば、trueである。
	 */
	isArray:function isArray(a){
		return a instanceof Array;
	},
	/**
	 * 連想配列をシャローコピーして複製する。
	 * @function
	 * @param {object} a
	 * 複製元のオブジェクトである。
	 * @return {object}
	 * 複製したオブジェクトである。
	 */	
	cloneAssoc:function cloneAssoc(a)
	{
		var r={};
		for(var i in a){
			r[i]=a[i];
		}
		return r;
	},
	/**
	 * 桁数を指定して、int値を16進数に変換する。
	 * @param {int} i_val
	 * 変換する値
	 * @param {int} i_digit
	 * 桁数
	 * @return {string}
	 * 文字列形式の値
	 */
	hexout:function hexout(i_val,i_digit)
	{
		try{
			var dt=["","0","00","000","0000","00000","000000","0000000"];
			var s=(i_val>>>0).toString(16).toLowerCase();
			if(s.length>i_digit){
				//マイナスだともれなくエラー
				throw new MiMicException(EE.NG);
			}
			var l=i_digit-s.length;
			return dt[l]+s;
		}catch(e){
			throw new MiMicException(e);
		}
	},
	assocToStr:function assocToStr(a)
	{
		var s="";
		for(k in a){s+=k+":"+a[k]+",";}
		return s;
	}	
}

/**
 * MiMicExceptionが使用するエラーコードと、その判定関数を定義する。
 * エラーコードは、以下の形式の配列オブジェクトで表現する。
 * <pre>
 * [code:int,message:string]
 * </pre>
 * 
 * codeは31ビットのエラーコードである。ビットフィールドの意味は以下の通りである。
 * <table>
 * <tr><th>bit</th><th>name</th><th>discription</th></tr>
 * <tr><td>30</td><td>ErrorBit</td><td>Error:1,OK:0</td></tr>
 * <tr><td>29-24</td><td>Reserved</td><td>-</td></tr>
 * <tr><td>23-16</td><td>ModuleID</td><td>0x00:unknown<br/>0x39:MiMic<br/>0xF0-0xFF: user define<br/>Other:Reserved<br/></td></tr>
 * <tr><td>15-8</td><td>ClassID</td><td>0x00:unknown</td></tr>
 * <tr><td>7-0</td><td>ErrorID</td><td></td></tr>
 * </table>
 * @namespace
 * @example
 * throw new MiMicException(MiMicError.NG);
 */
var MiMicError=
{
	/** 成功を示すエラー値
	 * @constant*/	
	OK:[0x00000000,"OK"],
	/** 失敗を示すエラー値。不明な場所で、不明な何かが、不明なエラーを起こしたことを示す。
	 * @constant*/	
	NG:[0x40000000,"NG"],
	/**
	 * エラーコードがOKか調べる。
	 * @function
	 * @param {Object as [MiMicErrorCode]} v
	 * 評価するオブジェクト
	 * @return {Boolean}
	 * エラーコードでなければ、trueを返す。
	 * @example
	 * MiMicError.isOK(MiMicError.OK);//true
	 */
	isOK:function(v){
		return (0x40000000 & v)==0x00000000;
	},
	/*
	 * 定数定義
	 */
	/** @private*/
	MID_MiMic:0x00390000,
	/** @private*/
	CAID_RemoteMCU     :0x0100,	//
	/** @private*/
	CAID_LPCXPresso1769:0x0200	//リモートAPI
	
}


/**
 * 引数が1個のパターン。
 * @name MiMicException.2
 * @function
 * @param {object} e
 * eのクラスにより、動作が異なる。
 * <ul>
 * <li>{string} - MiMicException(MiMicError.NG,e)と等価である。</li>
 * <li>{object as [MiMicErrorCode]} - エラーコードを指定して例外を生成する。エラーコードについては、MiMicErrorを参照</li>
 * <li>{object} - MiMicException(MiMicError.NG,e.toString())と等価である。objectを文字列化して例外を生成する。</li>
 * <li>{MiMicException} - codeとmessageを継承して例外を生成する。コールスタックを生成するときは、このパターンを使うこと。</li>
 * </ul>
 * @example
 * throw new MiMicException(MiMicError.NG);
 * throw new MiMicException("Error");
 * try{
 * 	 throw new MiMicException("Error");
 * }catch(e){
 * 	 throw new MiMicException(e);
 * }
 */
/**
 * 引数が2個のパターン。追加メッセージを含めることが出来る。
 * @name MiMicException.3
 * @function
 * @param {object} e
 * eのクラスにより、動作が異なる。
 * <ul>
 * <li>{MiMicErrorCode} - エラーコードを指定して例外を生成する。</li>
 * <li>{MiMicException} - codeとmessageを継承して例外を生成する。コールスタックを生成するときは、このパターンを使うこと。</li>
 * </ul>
 * @param {string} s
 * 追加するエラーメッセージ。
 */
/**
 * MiMic javascript APIが生成する例外クラスのコンストラクタである。関数ごとにMiMicExceptionを使ったtry-catchを導入することにより、例外発生時にスタックトレースメッセージを得ることが出来る。
 * スタックトレースは改行で連結された文字列である。messageプロパティに格納される。alert関数で表示することで、効率的なデバックが可能である。
 * 引数の違いにより、数種類の呼び出し方がある。
 * @constructor
 * @param ...
 * 詳細は、MiMicException.nを参照。
 */
function MiMicException(/*...*/)
{
	var pfx;
	if(typeof arguments.callee.caller=="function"){
		 if(arguments.callee.caller.name.toString().length>0){
			pfx="function '"+arguments.callee.caller.name+'.';
		 }else{
		 	var s=arguments.callee.caller.toString();
			pfx="closure '"+s.substring(0,s.indexOf("{"))+"...'";
		 }
	}else{
		pfx="root document";
	}
	var sfx="";
	switch(arguments.length){
	case 0:
		//とりあえずexceptiion
		this.code=MiMicError.NG[0];
		this.message=pfx+" code(0x"+this.code.toString(16)+")"+MiMicError.NG[1];
		return;
	case 1:
		var v=arguments[0];
		if(v instanceof MiMicException){
			//exception継承
			this.code=v.code;
			sfx="  \nfrom "+v.message;
		}else if(typeof v=="object" && v.length==2){
			//Errorコードテーブル
			this.code=v[0];
			sfx=v[1];
		}else{
			//文字列やオブジェクト
			this.code=MiMicError.NG[0];
			sfx=MiMicError.NG[1]+" "+(((typeof v)!='undefined')?v.toString():"v==undefined");
		}
		this.message=pfx+" code(0x"+this.code.toString(16)+")"+sfx;
		return;
	case 2:
		var v=arguments[0];
		if(v instanceof MiMicException){
			//Exception+メッセージ
			this.code=v.code;
			this.message=pfx+" code(0x"+this.code.toString(16)+")"+"\nfrom "+v.message+":"+arguments[1];
			return;
		}else if(typeof v!="string" && v.length==2){
			//Errorコードテーブル+メッセージ
			this.code=v[0];
			this.message=pfx+" code(0x"+this.code.toString(16)+")"+v[1]+":"+arguments[1];
		}else{
			break;
		}
		return;
	default:
		break;
	}
	throw new MiMicException("Invalid MiMicException argument.");
}

MiMicException.prototype={
	/**
	 * MiMicErrorCode形式のエラーコードを保持する。
	 * @field {object as MiMicErrorCode}
	 */
	code:MiMicError.OK,
	/**
	 * エラーメッセージを保持する。この値は、改行区切りのコールスタックである。
	 * @field {string}
	 */
	message:"",
	/**
	 * messageフィールドをalertで表示する。
	 * @function
	 * @example
	 * try{
	 * 	throw new MiMicException();
	 * }catch(e){
	 * 	e.alert();
	 * }	 
	 */
	alert:function(){
		alert(this.message);
	},
	/**
	 * toStringを上書きする。オブジェクトを文字列化する。
	 * 文字列は例外のコールスタックであり、デバックで役立つ。
	 * @function
	 * @return {string}
	 * 現在のオブジェクトの状態（例外のコールスタック）
	 * @example
	 * try{
	 * 	throw new MiMicException();
	 * }catch(e){
	 * 	alert(e.toString());
	 * }	 
	 */
	toString:function()
	{
		return "MiMicException:"+this.message;
	}	
}






/*
 * MiMicRemoteMCU
 */
var MiMicRemoteMcuInterface;

(function(){
	/**
	 * エラーID定義
	 * @private
	 */
	var EE=function(i_base)
	{
		return {
		NG:[i_base|0x00,"Unknown exception in MiMicRemoteMcuInterface"],
		TCP_CONNECT:[i_base|0x01,"TCP CONNECT FAILED"],//接続エラー
		HTTP   	   :[i_base|0x02,"HTTP FAILED"]}//HTTPエラー
	}(MiMicError.NG[0]|MiMicError.MID_MiMic|MiMicError.CAID_RemoteMCU);
	/**
	 * MiMicRemoteMcuInterfaceクラスのコンストラクタ。
	 * MiMicRemoteMcuInterfaceクラスは、MCUで動作するMiMicRemoteMcuとの通信機能と、接続状態の監視機能を提供する。低レベルAPI全てを実装する。
	 * 低レベルAPIは、MiMicRemoteMcuとの通信を、関数コールに変換する。
	 * インスタンス生成直後は、MiMicRemoteMcuとの接続は断状態である。connect関数を実行して、接続する必要がある。
	 * 通信仕様については、MiMicVM.pdf Appendix 1.MiMicVM HTTP Interfaceを参照すること。
	 * @constructor
	 * @param {string} i_path
	 * 接続するMiMicRemoteMcuのホストパスを指定します。
	 * MiMicVersion 1.4以降はMiMicサーバの/mvm/に割り当てられています。
	 * ホストアドレスのみを指定した場合、"/mvm/"サフィックスを自動で追加します。
	 * ex.
	 * "127.0.0.1" =&lt; "127.0.0.1/mvm/"<br/>
	 * "127.0.0.1:3939/mvm/" =&lt; "127.0.0.1:3939/mvm/"
	 * @example
	 * //create a controlable MCU via network.
	 * var mcu=new MiMicRemoteMcuInterface(“192.168.0.39/mvm/”);	 
	 */
	MiMicRemoteMcuInterface=function MiMicRemoteMcuInterface(i_path)
	{
		this._mimic_path=i_path+((i_path.indexOf("/")==-1)?"/mvm/":"");
	}

	MiMicRemoteMcuInterface.prototype=
	{
		_mimic_path:null,
		_keep_alive:null,
		/**
		 * 最後にXhrレスポンスを得た時刻
		 */
		_last_xhr_res_time:0,
		/**
		 * XmlHttpRequestを実行する。
		 * @private
		 * @return
		 * 取得したデータ。
		 * @throws
		 * 取得失敗時(例外発生の場合)
		 */
		_xhrGet:function(i_url,i_async,i_callback)
		{
		    var xhr = window.XDomainRequest ? new XDomainRequest() : new XMLHttpRequest();
		    var prog=0;
		    try{
		    	if(i_async){
		    		xhr.onreadystatechange = function(){
						if (xhr.readyState == 4){
				        	//最終受信時刻を記録
							this._last_xhr_res_time=MiMicLib.getNow();
							i_callback(xhr);
						}
					}
		    	}
		        xhr.open("GET",i_url,i_async);
		        prog=1;
		        xhr.send(null);	
		        if(!i_async){
		        	//最終受信時刻を記録
					this._last_xhr_res_time=MiMicLib.getNow();
		        }
		        prog=2;		        
		    } catch (e){
		    	throw new MiMicException(
		    		function(){
		    			switch(prog){
		    			case 1:return EE.HTTP;
		    			}
						return EE.TCP_CONNECT;
					}(),"url="+i_url);
		    }
		    return xhr;
		},
		/**
		 * MiMicの応答から、オンラインチェックを実行します。
		 * @private
		 * @param res
		 * レスポンスを格納済みのXHRオブジェクト
		 * @return
		 * オンラインならtrue
		 * 入力文章から状態が判定できなかった場合、false
		 */
		_isOnline_parseResponse: function(res)
		{
			function isok(i_s)
			{
				function parse_version_tag(i_v)
				{
					var t=i_v.split("/");
					var n=t[1].split(".");
					return {name:t[0],major:parseInt(n[0]),minor:parseInt(n[1])};
				}
				try{
					//"MiMicRemoteMCU/1.n;xならOK.(n>=1)
					var l=i_s.split(";");
					var rmcu=parse_version_tag(l[0]);//ModRemoteMcu
					var mcut=l[1];//MCUTYPE
					return ((rmcu.name=="ModRemoteMcu") && (rmcu.major==1) && (rmcu.minor>=0));
				}catch(e){
					throw MiMicException(e);
				}
			}

			try{
				if(res.status!=200){
					return false;
				}
				var ret=eval("("+res.responseText+")");
				if(!isok(ret.application)){
					return false;
				}
			}catch(e){
				return false;
			}
			return true;
		},
		/**
		 * MvmのレスポンスをJSOにする。
		 * @private
		 * @return
		 * 成功時はjson
		 * @throws
		 * JSONが得られない場合
		 */
		_parseMvmResult:function(i_mvmresult)
		{
			function isok(i_s)
			{
				//ModRemoteMcu/1.x;Json/1.0ならOK
				var l=i_s.split(";");
				return((l[0].indexOf("ModRemoteMcu/1.")==0) && (l[1]=="Json/1.0"));
			}
			var ret=eval("("+i_mvmresult+")");
			if(isok(ret.version)){
				if(ret.result!=undefined){
					if(ret.result!=0x0 || ret.stream!=undefined){
						return ret;
					}
				}
			}
			//なんかうまくいかない。
			throw new MiMicException(EE.NG,"Invalid json version:'"+ret.version+"'");
		},
		/**
		 * 接続状態を真偽値で返す。
		 * 接続状態の場合、execBc等の低レベルAPIを使用できる。
		 * 状態は、インスタンスが定期的に実行するプローブ信号でチェックされ、更新される。
		 * @function
		 * @return {boolean}
		 * 接続状態。trueなら、MCUインタフェイスは利用可能。
		 * @example
		 * //show connection status
		 * var mri=new  MiMicRemoteMcuInterface(“192.168.0.1”);
		 * alert(mri.isConnected());		 
		 */
		isConnected:function isConnected()
		{
			return this._keep_alive!=null;
		},
		/**
		 * 接続中のMiMicremoteMCUへ、整形済みのMiMicBCを送信する。
		 * 関数の利用前に、connect関数でMiMicremoteMCUへ接続する必要がある。
		 * 何らかのエラーが発生してMiMicRemoteMCUからの応答が得られない場合、関数は例外を発生する。
		 * この状況は、TCP/IPエラー、HTTPエラー、HTTPステータスエラー、MiMicVMのラインタイムエラー、MiMicRemoteMCUのフォールト等の原因で発生する。<br/>
		 * 関数は同期実行でのため、RemoteMCUが応答しないと制御がブロックする。非同期関数は今後実装する。
		 * @function
		 * @param {string} i_bc
		 * 整形済みのMiMicBC文字列。MiMicBCについては、MiMicVM.pdf の、MiMicBCの章を参照。
		 * @return
		 * MiMicVMのパース済みJavascriptObjectである。
		 * 形式は以下の通り。
		 * <pre>
		 * {version: string,result: int,stream int[]}
		 * </pre>
		 * 詳細は、 MiMicVM.pdf Appendix 1. MiMicVM HTTP Interfaceを参照。
		 * @example
		 * //execute NOP.
		 * var mri=new  MiMicRemoteMcuInterface(“192.168.0.1”);
		 * mri.connect(function(){});
		 * mri.execBc(“ZAZZ.E”);//NOP;EXIT;		 
		 */
		execBc:function execBc(i_bc)
		{
			try{
				if(this._keep_alive==null){
					throw new MiMicException(EE.NG,"disconnected");
				}
				var res=this._xhrGet("http://"+this._mimic_path+"mvm.api?v=1&bc="+i_bc,false);
				if(res.status!=200){
					throw new MiMicException(EE.HTTP,"i_bc="+i_bc+",XHR.status="+res.status);
				}
				return this._parseMvmResult(res.responseText,false);
			}catch(e){
				throw new MiMicException(e);
			}
		},

		/**
		 * MiMicRemoteMCUへ接続する。
		 * 既に接続済みの場合は何もしない。
		 * @function
		 * @param {function} i_callback
		 * 回線状態を定期的に通知するコールバックハンドラ。
		 * <pre>function(b:{boolean})</pre>
		 * disconnect関数を呼び出すまでの間、回線の状態を定期的に受け取る。
		 * bは接続状態を表す真偽値である。trueの時、接続中である。falseの場合、外部要因により切断されている。
		 * falseが通知されるのは、disconnectが呼び出されるまでに非同期切断を検出したときだけである。disconnectで切断した場合には呼び出されない。		 
		 * @example
		 * //show connection status
		 * var mri=new  MiMicRemoteMcuInterface(“192.168.0.1”);
		 * mri.connect(function(b){if(!b){alert(“disconnected!”);}});		 
		 */
		connect:function connect(i_callback)
		{
			try{
				var _t=this;
				//接続中ならおわり
				if(this._keep_alive!=null){
					return;
				}
				function xhrStatus(i_is_async,i_cb)
				{
					try{
						return _t._xhrGet("http://"+_t._mimic_path+"status.api",i_is_async,i_cb);
					}catch(e){
						return null;
					}
				};
				function intervalProc()
				{
					//現在時刻を計算
					var now=MiMicLib.getNow();
					//最後に通信に成功した時刻からの経過時間を計算
					var lt=now-_t._last_xhr_res_time;
					if(lt<3000){
						return;
					}
					//最後の通信からの経過時間がNを超えていたら通信チェック
					var res=xhrStatus(false,null);
					if((res==null) || (!_t._isOnline_parseResponse(res))){
						if(_t._keep_alive!=null){
							//Intrervalを中断
							clearInterval(_t._keep_alive.tid);
							_t._keep_alive=null;
							i_callback(false);
						}
					}					
					i_callback(true);
				};
				//1回目の接続確認(同期接続)
				var res=xhrStatus(false,null);
				if((res==null) || (!_t._isOnline_parseResponse(res))){
					throw new MiMicException("Bad response from "+this._mimic_path);
				}
				//周期監視の開始
				this._keep_alive={
					tid:setInterval(intervalProc,500)
				};
			}catch(e){
				throw new MiMicException(e);
			}
			return;
		},
		/**
		 * 接続中のMiMicRemoteMCUから切断する。接続されていなければ何もしない。
		 * @function
		 * @example
		 * //connect and disconnect
		 * var mri=new  MiMicRemoteMcuInterface(“192.168.0.1”);
		 * mri.connect(function(b){if(!b){alert(“disconnected!”);}});
		 * mri.disconnect();		 
		 */
		disconnect:function disconnect()
		{
			try{
				if(this._keep_alive!=null){
					clearInterval(this._keep_alive.tid);
					this._keep_alive=null;//abortによるコールバックを抑制
				}
			}catch(e){
				throw new MiMicException(e);
			}
		}
	}
	
	
	
	
	
}());



