//jsonClient RPC客户端

import web.json;
import web.socket.client;

namespace web.socket;

class jsonClient{
    
	ctor(){ 
		
		this.rpc = ..web.socket.client();
		this.rpc.version = "2.0";
		this.rpc.varargs = true;
		
		this.rpc._translateMessage = function(msg){
			if( msg.type == 1 ){
				if( owner.beforeJsonParse ) {
					msg.data = owner.beforeJsonParse(msg.data);
					if(!msg.data) return;
				}
			
    			var data,err  =  ..web.json.tryParse(msg.data);
    			if( type(data) != "table"){ 
    				// Chrome DevTools Protocol 没有 jsonrpc 字段
    				if( this.rpc.onError ) 	this.rpc.onError(..string.concat( err," 接收到的数据格式错误:",msg.data));
    				return;
    			}
    			 
 				if(#data){
 					for(i=1;#data ) this.rpc._response(data[i]); 
 				}
 				else {
 					this.rpc._response(data);
 				}
    		}
		};
		
		this.rpc.sendJson = function(data){
			var jsonData = ..web.json.stringify(data);
			if( owner.afterJsonStringify ) {
				jsonData = owner.afterJsonStringify(jsonData);
				if(!jsonData) return;
			};
			return this.rpc.send(jsonData);
		}
		
		
		this.rpc._response = function(repData){
		    if( repData.method ){
    			var event = owner.eventCallback[repData.method];
    			if(event) {
    				var result,err;
    				if( owner.varargs ){
    					result,err = event(..table.unpackArgs(repData.params) );
    				}
    				else {
    					result,err = event(repData.params);
    				}

    				if(repData.id) {
    					if( owner.sendJson( {
    						error = err;
							result = result;
							id = repData.id;
    					} ) ) return cb;
    				}
    				
    			}
    		}
    		elseif(repData.id) {
    			var cb = owner.endCallback[repData.id];
    			if(cb){
    				this.rpc.endCallback[repData.id] = null;
    				if( cb.end ){
    					cb.end( repData.result,repData.error )
    				}
    			};
    		}
    		else {
    		    if(repData.error){
    		    	this.rpc.onError(..string.concat("Server Error:",repData.error.message," #",repData.error.code),repData.error);
    		    	return; 
    		    }
    		    
    			if( this.rpc.onError ) 	this.rpc.onError("接收到的数据不符合 JSON RPC 协议规则");
    			
    			owner.sendJson( {
    				error = {
						code = -32600;
						message = "Invalid Request - The JSON sent is not a valid Request object."
					};
    			})
    		}
		};
		
		this.rpc.endCallback ={};
		this.rpc.eventCallback ={};
		this.rpc.beginTrans = function(){
			owner.transaction = {};
		};
		this.rpc.commitTrans = function(){
			if(! owner.sendJson(  owner.transaction ) ){
				if(this.rpc.onError){
					this.rpc.onError(..afterJsonStringify(owner.transaction) 
					+ '\n发送失败,请先调用connect函数连接JSON RPC服务端');	
				}
				return;
			} 
			owner.transaction = null;
		};
		this.rpc.rollbackTrans = function(){
			owner.transaction = null;
		}; 
		this.rpc.xcall = function(method,...){
			var cb = {};
			cb.method = method; 
			var id = ..table.push(owner.endCallback,cb);
			var data = {
				id = id;
				jsonrpc = owner.version;
				method = method;
			}
			
			if( owner.varargs ){
				data.params = {@{_type="array"};...}
			}
			else {
				data.params = ...;
			}
			
				
			if( owner[["beforeRequest"]] ){
				data = owner.beforeRequest(data);
			}; 
			
			if( owner.transaction ){
				..table.push(owner.transaction,data);
				return cb;
			}
			
			if( owner.sendJson(data) ) return cb;
			if(this.rpc.onError){
				var err =  '发送失败,请先调用connect函数连接JSON RPC服务端:\n'+ ..web.json.stringify(data);
				if(this.rpc.onError) this.rpc.onError(err);
				
				..win.setTimeout(
					function(){
						if( cb.end ) cb.end(,'发送失败,请先调用connect函数连接JSON RPC服务端');
						this.rpc.endCallback[id] = null;
					}
				)
				return cb;
			}
		}
	} 
	connect = function(url){
		return this.rpc.connect(url);
	};
	waitForConnected = function(hwnd,timeout){ 
		..win.delay(1000);
		return ..win.wait( 
			function(){
				if( this.rpc.readyState > 1 ) return false;
				if( this.rpc.readyState == 1 ) return true;
			},hwnd,timeout,1000
		);	 
	}; 
	on = function(method,callback){
		if( _socketEvent[method] ){
			this.rpc[_socketEvent[method]] = callback;
			return;
		}
		this.rpc.eventCallback[method] = callback;
	}
	@_meta;
}

namespace jsonClient{
    	
	_meta = {
  	
    	_get = function(k){
        	
    		if( type(k) == type.string ){
    			
    			if( owner[["method"]] ){
    				k = owner[["method"]] + "." + k;
    			}
    			
    			owner[[k]] = {
    				rpc = owner[["rpc"]];
    				method = k;
    				@_meta;
    			}
    			
    			return owner[[k]];
    		}
    	} 
    	_call = function( ... ){ 
				return owner.rpc.xcall( owner.method,...) ;
		}
	}	
	
	_socketEvent = {
    	open = "onOpen";
    	close = "onClose";
    	error = "onError";
    	message = "onMessage";
    	fragmen = "onFragment"; 
 	} 
}
/**intellisense()
web.socket.jsonClient = WebSocket / JSON-RPC 2.0 单线程异步客户端
web.socket.jsonClient() = !stdwebsocketjsonClient.
end intellisense**/

/**intellisense(!stdwebsocketjsonClient)
rpc = WebSocket客户端对象\n此对象的成员谨慎改动\n!stdwebsocketjsonClientrpc.
? = 远程对象名或远程方法名字,\n!stdWebSocketJsonClientObject.
on(method,proc) = @.on("__/*需要监听的Rpc通知事件名字*/",function(param){\n    \n})
on("open",proc) = @.on("open",function(){\n	__/*已打开连接*/		\n}
on("close",proc) = @.on("close",function(e){\n	__/*连接被关闭\ne.code为错误代码e.reason为错误原因*/\n})
on("error",proc) = @.on("error",function(err,serverError){\n	__/*发生错误,err为错误信息,\n如果服务端按 jsonrpc 协议返回 error 对象,\n则 serverError 有值,包含 messge,code 字段*/\n})
on("message",proc) = @.on("message",function(msg){\n    __/*收到服务端数据\nmsg.type为1时msg.data为文本,\n否则msg.data为字节数组（buffer类型）*/\n})
on("fragment",proc) = @.on("fragment",function(msg){\n   __/*收到分片数据\n第一个数据包使用msg.type指明类型,参考WebSocket协议规范\n后续数据包msg.type为0,最后一个数据包msg.fin为1\n\n如果不指定这个回调函数,则自动并接分片数据后触发onMessage事件*/	\n})
connect("ws://__") = 重新连接到WebSocket服务端\n参数指定WebSocket服务端网址，例如 "ws://localhost:7511"\n如果不指定参数,则获取上次调用此函数指定的网址参数,\n如果之前也没有指定网址则抛出异常
end intellisense**/

/**intellisense(!stdWebSocketJsonClientObject)
? = 远程对象名或远程方法名字,\n作为函数调用时返回一个调用对象,\n通过指定返回调用对象的 end 属性定义调用结束回调函数,例如:\nret.end=function(result,err){\n	\n}\n回调参数 result 为调用返回值,err 为错误信息,\n如果调和成功,err参数为null\n!stdWebSocketJsonClientObject.
rpc = WebSocket客户端对象\n此对象的成员谨慎改动\n!stdwebsocketjsonClientrpc.
end = @.end = function(result,err){
	__/*result为远程调用返回值,err为错误对象,\n调用成功err参数为null*/
}
end intellisense**/

/**intellisense(!stdwebsocketjsonClientrpc) 
varargs = 默认值为true,\n值为true时将不定个数的参数放入数组发送给服务端\n值为false时直接将单个参数发送给服务端\n\nJSON-RPC 2.0一个会制造混乱的地方：\n如果params是一个数组，并没有规定是展开为一个参数，还是作为一个数组参数。\n\n目前aardio的RPC服务端会负责展开数组作为多个参数，\n但客户端需要在这里手动设置
version = 值为JSON-RPC协议版本："2.0"\n不应该修改这个值
xcall(method,param) = @.xcall("Page.navigate",{\n    url = "网址"\n}).end = function(result,err){\n	__/*调用JSON-RPC服务端的指定方法\n服务器应答后回调此函数返回值的成员函数:end函数\nend函数有2个回调参数,参数@1为result，参数@2为err\n失败err参数非空*/\n}
beforeRequest(请求数据) = @.beforeRequest = function(reqData){
	__/*此回调事件在发送请求前触发\nreqData.params是即将发送的参数*/
	return reqData;
}
beforeJsonParse = @.beforeJsonParse = function(jsonData){
    __/*可以在这里解密服务端数据,返回null中止本次调用*/
	return jsonData;
}
afterJsonStringify = @.afterJsonStringify = function(jsonData){
    __/*可以在这里加密客户端JSON,返回null中止本次调用*/
	return jsonData;
}
beginTrans() = 开始批量调用\n之后的所有RPC调用不提交服务器,\n直到调用commitTrans函数
commitTrans() = 完成批量调用并提交到服务器
rollbackTrans() = 撤消尚未提交的批量调用
socket = 异步套接字对象\n在关闭连接状态下此属性的值为null\n应由对象自动打开或删除套接字对象，调用者不可改动此属性的值\n!stdtcpaclient.
connect("ws://__") = 重新连接到WebSocket服务端\n参数指定WebSocket服务端网址，例如 "ws://localhost:7511"\n如果不指定参数,则获取上次调用此函数指定的网址参数,\n如果之前也没有指定网址则抛出异常
waitForConnected(.(关联窗口句柄,超时) = 等待连接到WebSocket服务端\n所有参数可选,超时以毫秒为单位,\n\n连接成功返回true,失败返回false或null
url = 上次成功连接的网址\n也可以用于指定下次连接的默认网址
close() = 关闭连接\n可选增加2个参数指定发送给服务器的关闭帧附加数据:\n参数@1为数值类型的错误代码,参数@2为字符串类型错误描述
headers = 其他HTTP请求头\n值可以是文本或数组、或键值对组成的表\n请求时会调用 web.joinHeaders()函数拼接并转换HTTP头\n该函数支持的类型和格式这个属性都可以支持
responseHeaders = 服务端响应的HTTP头\n这是一个表对象，键名都已转为小写
readyState = 连接状态,\n0为等待连接,1为已连接并准备就绪,2为正在关闭,3为已关闭\n只有成功通过WebSocket协议握手以后readyState才会被置为1\n这与socket.readyState连接成功就会置为1是不同的
isClosed() = 套接字是否已关闭
isConnected() = 套接字是否已连接并准备就绪(已与服务器握手成功)
heartbeatInterval = 客户端主动发送心跳间隔,默认为30秒,\n设为-1时禁用客户端心跳,注意某些服务端收到心跳包会报错,这时建议关掉心跳,\n此属性值修改以后，只能在下次调用connect函数才会生效
heartbeatData = 单向心跳发送的数据,默认为空数据\n这个值修改以后，只能在下次调用connect函数才会生效
heartbeatType = 单向心跳发送的的帧类型,\n默认为0xA,也就是单向心跳 Pong 帧\n这个值修改以后，只能在下次调用connect函数才会生效
originUrl = 浏览器启动WebSocket客户端的网址\n一些WebSocket服务器根据这个判断是不是允许连接,\n所以有时候设置这个很重要\n默认使用WebSocket网址，并把 前面的ws://改为http://
userAgent = 客户端应用程序代理头\n默认为"Mozilla/5.0"
protocol = 应用程序支持的协议列表,默认为"chat"
end intellisense**/