
/*=============================================================================================
	author:ZhaoJun
	version:1.2
	email:2732810281@qq.com
=============================================================================================*/


//严格模式
/*-------------------------------------------------------------------------------------------*/	
'use strict';
/*-------------------------------------------------------------------------------------------*/

const crypto=require('crypto')
const packages=$Sys.model('/socket/package')
const filters= $Sys.model('/fun/filter')
    
// net操作
/*-------------------------------------------------------------------------------------------*/	
	class init_s
	{
		constructor(request,req,tag)
		{
			let time=Date.now()
			request.$time=time-$ExtendNet.twMin
			request.$timeStart=time
			packages.reset.call(this)
			if(tag==1) this.route(request)
			else if(tag==2) this.routeWs(req,request)
			else this.routeUdp(req,request)
		}


		
		route(request)
		{
			this.tag=1;
			this.tagName='tcp'
			this.com(request)

			request.setEncoding('utf8');
			let fun=async (d)=>
			{
				let list=this.checkMessage(d)
                if(!list)return
                for (let index = 0; index < list.length; index++) 
                {
                    await this.makeMessage(list[index])
                }
			}
			request.on('data',fun);
			request.rmListener=()=>request.removeListener('data',fun)
		}

		routeWs(req,request)
		{
			this.tag=2;
			this.tagName='ws'
			this.com(request)

			let key = req.headers['sec-websocket-key'];
			key = crypto.createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64");
			let headers = [
			'HTTP/1.1 101 Switching Protocols',
			'Upgrade: websocket',
			'Connection: Upgrade',
			'Sec-WebSocket-Accept: ' + key
			];
			if(request.writable && !request._writableState.destroyed)request.write(headers.join("\r\n") + "\r\n\r\n", 'ascii');
			else return

			let fun=async (d)=>
			{
				let list=this.checkMessage(d)
				if(!list)return
				for (let index = 0; index < list.length; index++) 
				{
					await this.makeMessage(list[index])
				}
			}
			request.on('data',fun)
			request.rmListener=()=>request.removeListener('data',fun)
		}

		routeUdp(request)
		{
			this.tag=3;
			this.tagName='udp'
			this.com(request)

			let fun=async (d, info)=>
			{
				d = d.toString();
				let bool=this.checkMessage(d,info)
				if(!bool)return
				await this.makeMessage(d,info)
			}
			request.on('message',fun);
			request.rmListener=()=>request.removeListener('message',fun)
		}


		com(request)
		{
			this.code=$Code.sys.control.code
			this.request=request

			request.ip=request.remoteAddress;

			if(this.tag!==3)
			{
				if($ExtendNet.twTimeout)request.setTimeout
				(
					$ExtendNet.twTimeout,
					()=>
					{
						request.end()
					}
				)
			}

			request.sender=(da,ty={})=>
			{
				let buf=Buffer.isBuffer(da)
				if(request.writable || this.tag===3)
				{
					if(request._writableState)
					{
						if(request._writableState.destroyed)return
					}
					//opcode：占四个位，可以表示0~15的十进制，0表示为附加数据帧，1表示为文本数据帧，2表示二进制数据帧，
					//8表示发送一个连接关闭的数据帧，9表示ping，10表示pong，ping和pong都是用于心跳检测，
					//当一端发送ping时，另一端必须响应pong表示自己仍处于响应状态。


					//指示这个是消息的最后片段。第一个片段可能也是最后的片段。如果为1即为最后片段，
					//（其实这个位的用途我个人有点疑惑，按照书上以及网上查的资料，当数据被分片的时候，不同片应该都会有fin位，
					//会根据fin为是不是0来判断是否为最后一帧，但是实际实现中却发现，当数据比较大需要分片时，
					//服务端收到的数据就只有第一帧是有fin位为1，其他帧则整个帧都是数据段，也就是说，感觉这个fin位似乎用不上，
					//至少我自己写的demo中是通过数据长度来判断是否到了最后一帧，完全没用到这个fin位是否为1来判断）
					
					if(typeof da != 'string' && !buf)
					{
						console.warn(`[${this.tagName}响应数据]------消息不符合要求:`,da)
						return
					}
					else if($Config.response)
					{
						if(da!==$ExtendNet.heartTag)
						{
							console.white(`[${this.tagName}响应数据]\r\n`,da)
						}
						else if($ExtendNet.twHeartPrint)
						{
							console.white(`[${this.tagName}响应数据]\r\n`,'心跳包')
						}
					}

					if(this.tag===1)
					{
						da=packages.encode(da)
					}
					else if(this.tag===2)
					{
						da=packages.encodeWs({FIN:1,Opcode:1,Payload_data:da});
					}
					else if(this.tag===3)
					{
						if(!buf)buf=Buffer.from(da)
						return request.send(buf,0,buf.length,ty.$port,ty.$ip)
					}

					if(ty.$end)
					{
						return request.end(da);
					}
					else
					{
						return request.write(da);
					}
					
				}
				else
				{
					return false;
				}
			}

			request.json=(data,mydata={})=>
			{
				if(!data)return
				if(data.$unique===undefined && mydata.$unique) data.$unique=mydata.$unique;
				if(mydata.$timeEnd)
				{
					let tiout=mydata.$timeEnd - Date.now()
					if(tiout < 0)
					{
						console.warn(`消息超时,不在发送${-tiout}`)
						return
					}
				}
				if(data instanceof Object)return request.sender(JSON.stringify(data),mydata);
			}

		}




		checkMessage(d,info)
		{
			let request=this.request
			let tag=this.tag
			let arr

			let param={}
			if(info)
			{
				param.$ip=info.address
				param.$port=info.port
			}

			if(tag===1)
            {
                arr=packages.decode.call(this,d)
			}
			else if(tag===2)
			{
				try
				{
					arr=packages.decodeWs.call(this,d)
				}
				catch(e)
				{
					console.error("websocket数据处理错误------"+`描述：${e.stack}`);
					return;
				}
			}
            else
            {
				this.$len=d.length
				arr=true
            }


			if(this.$str.length>$ExtendNet.twLen || this.$len>$ExtendNet.twLen)
			{
				packages.reset.call(this)
				request.json(filters.echo($Code.sys.requestBig),param);
				return;
			}

			return arr
		}


		async makeMessage(str,info)
		{
			if(str===$ExtendNet.heartTag) 
			{
				packages.pong.call(this,true) 
				return
			}
			if($Config.request)console.white(`[${this.tagName}客户端数据]------`,str);

			let request=this.request
			var mydata={};
			if(info)
			{
				mydata.$ip=info.address
				mydata.$port=info.port
			}

			mydata=this.disp(str,mydata);
			if(!mydata)return;

			if(info)
			{
				mydata.$ip=info.address
				mydata.$port=info.port
			}


			let now=Date.now()
			if(this.tag===3)
			{
				if(now-request.$time<$ExtendNet.twMinUdp)
				{
					request.json(filters.echo($Code.sys.busy),mydata)
					request.$time=Date.now()
					return
				}
			}
			else if(now-request.$time<$ExtendNet.twMin)
			{
				request.json(filters.echo($Code.sys.busy),mydata)
				request.$time=Date.now()
				return;
			}
			request.$time=Date.now()

			var _path=mydata['path'];

			request.user= $Sys.model('/socket/user');

			if(_path)
			{
				await this.bero(_path,mydata);
			}
			else
			{
				request.json(filters.echo($Code.sys.notFound),mydata)
				return;
			}
		}


		disp(d,info)
		{
			let request=this.request
			let mydata={};

			try 
			{
				mydata=JSON.parse(d);
			} 
			catch (e) 
			{
				e=e.stack;
				console.error(`${this.tagName}解析错误------描述：${e.stack}\r\n${d}`);
				request.json(filters.echo($Code.sys.jsonParse),info);
				return false;
			}
			return mydata
		}


		to(data,mydata)
		{
			let request=this.request
			if(data===undefined)return
			else if(data instanceof Object)
			{
				request.json(data,mydata);
			}
			else
			{
				request.sender(String(data));
			}
		}

		async next(fun,mydata)
		{
			let request=this.request
			let data=await fun(mydata,request);
			if(data)
			{
				if(data!='_file')
				{
					if(data!==true)this.sender(data,mydata);
				}
				return true;
			}
			else
			{
				return false;
			}
		}


		async  run(__con,ur,mydata)
		{
			let request=this.request
			request.control=async function(url,met='default',data={})
			{
				let res=$Sys.control(url);
				if(res)
				{
					let nm=new res();
					try 
					{
						return await nm[met](data,request);
					} 
					catch (error) 
					{
						console.error('[调用控制器失败]',error)
						return false;
					}
				}
				return false;
			}
			
			if(__con[ur])
			{
				try 
				{
					let data=await __con[ur](mydata,request);
					request.json(data,mydata);
				} 
				catch (e) 
				{
					if(e.cache)
					{
						request.sender(e.cache)
						return
					}
					if(e.string)
					{
						request.sender(e.string)
						return
					}
					request.json($Sys.errIo(e,$Code.sys.control),mydata)
				}	
			}
			else
			{
				console.warn("[注意]","未找到------"+ur+"方法");
				request.json(filters.echo($Code.sys.notFound),mydata)
			}					

		}

		async bero(_path,mydata={})
		{
			let request=this.request
			
			try 
			{
				for (let val of $Config.middle[this.tagName]) 
				{
					if(val[0] instanceof RegExp)
					{
						if(val[0].test(_path))
						{
							let result=await this.next(val[1],mydata);
							if(result)return true;
						}
					}
					else if(_path.indexOf(val[0])==0)
					{
						let result=await this.next(val[1],mydata);
						if(result)return true;
					}							
				}
			} 
			catch (error) 
			{
				request.json($Sys.errIo(error,$Code.sys.middle),mydata)
				return true
			}

			var _all=_path.split('-');
			var _argument=_all.slice(1);
			var _alla=_all[0].split('/');
			var _allf=_alla.pop();
			var _allp=_alla.join("/");
			
			try 
			{
				var tomethod=$Sys.control(_allp);	
			} 
			catch (e) 
			{
				request.json($Sys.errIo(e,$Code.sys.control),mydata)
				return
			}

			request.argument=_argument;



			if(tomethod)
			{
				let nm=new tomethod(request);
				await this.run(nm,_allf,mydata);
			}
			else
			{
				await this.run
				(
					{
						"fun":async ()=>
						{
							
							request.json(filters.echo($Code.sys.notFound),mydata)
						}
					}
					,'fun',mydata
				);
			}
		}      
        
  }
/*-------------------------------------------------------------------------------------------*/   
   
module.exports=init_s;