/** Pomelo客户端 */


import Emitter from './Emitter.js';
import Protocol from './Protocol.js';
import Protobuf from './Protobuf.js';
import Message from './Message.js';
import Package from './Package.js';
const StructParam = {
	/** @type {String | '主机地址和端口，如：127.0.0.1:1234'} */
	url: '',
	/** @type {Number | '用户uid'} */
	uid: 0,
	/** @type {String | '身份令牌'} */
	token: '',
	/** @type {String | '服务器名，默认为url'} */
	serverName: '',
	/** @type {Date | '服务器时间对象'} */
	serverTime: null,
	/** @type {Boolean | '是否断线重连'} */
	reconnect: false,
	/** @type {Number | '最大重连次数，默认10'} */
	maxReconnectAttempts: 0,
	/** @type {(data: String | JSON, needTime: Boolean)| '消息加密方法，需要服务器配合'} */
	enData: () => { },
	/** @type {(data: String | JSON)| '消息解密方法，需要服务器配合'} */
	deData: () => { },
	/** @type {Object | 'SSL证书'} */
	cert: null,
	/** @type {Function | '握手成功后的回调'} */
	handshakeCallback: () => { },
}

export default () => {

	return new class extends Emitter {

		constructor() {
			super();

			/** 握手：客户端类型 */
			this.JS_WS_CLIENT_TYPE = 'js-websocket';
			/** 握手：客户端版本 */
			this.JS_WS_CLIENT_VERSION = '0.0.1';
			/** 状态：握手成功 */
			this.RES_OK = 200;
			/** 状态：客户端版本太老 */
			this.RES_OLD_CLIENT = 501;

			/** web socket连接实例 */
			this.socket = null;
			/** 消息编号 */
			// 客户端每发送一个请求，不可能瞬间与服务器完成通讯，所以必须暂存起来一个个处理
			// 以下几个对象均为此而产生，与请求的消息一一对应
			this.reqId = 0;
			/** 请求的回调队列 */
			this.callbacks = {};
			/** 处理数据的方法的队列 */
			this.handlers = {};
			/** 请求id与路由的映射队列 */
			this.routeMap = {};
			/** 路由转为编码 */
			this.dict = {};
			/** 编码转为路由 */
			this.abbrs = {};
			/** 服务端的protos协议 */
			this.serverProtos = {};
			/** 客户端的protos协议 */
			this.clientProtos = {};
			/** protobuf的版本 */
			this.protoVersion = 0;

			/** 心跳间隔时长 */
			this.heartbeatInterval = 0;
			/** 心跳超时时长 */
			this.heartbeatTimeout = 0;
			/** 下次心跳超时时长 */
			this.nextHeartbeatTimeout = 0;
			/** 心跳间隔临界值 */
			this.gapThreshold = 100;
			/** 心跳id */
			this.heartbeatId = null;
			/** 心跳超时id */
			this.heartbeatTimeoutId = null;
			/** 握手成功的回调 */
			this.handshakeCallback = null;

			/** 数据发送开关（模拟客户端断网时用） */
			this.canSend = true;

			/** 是否开启断线重连 */
			this.reconnect = false;
			/** 是否已重连上的标志 */
			this.reconnected = false;
			/** 尝试重连的计时器id */
			this.reconncetTimer = null;
			/** 重连的地址 */
			this.reconnectUrl = null;
			/** 已尝试重连次数 */
			this.reconnectAttempts = 0;
			/** 尝试重连间隔 */
			this.reconnectionDelay = 1000 * 3;
			/** 默认最大重连尝试次数 */
			this.DEFAULT_MAX_RECONNECT_ATTEMPTS = 10;

			this.handshakeBuffer = {
				'sys': {
					type: this.JS_WS_CLIENT_TYPE,
					version: this.JS_WS_CLIENT_VERSION
				}
			};

			/** 初始化完成后的回调 */
			this.initCallback = null;

			this.protocol = Protocol();
			this.protobuf = Protobuf();
			this.message = Message();
			this.package = Package();

			this.handlers[this.package.TYPE_HANDSHAKE] = this.handshake.bind(this);
			this.handlers[this.package.TYPE_HEARTBEAT] = this.heartbeat.bind(this);
			this.handlers[this.package.TYPE_DATA] = this.onData.bind(this);
			this.handlers[this.package.TYPE_KICK] = this.onKick.bind(this);
		};

		/**
		 * 初始化和连接指定服务器
		 * @param {String} route 请求数据的路由
		 * @param {StructParam} params 初始化服务器时的参数
		 * @param {JSON} msg 发往服务器的消息
		 * @param {(_:cc.Struct['assemble']['res'])} callback
		 * @todo 只是把init和request合并
		 */
		createConnect(route, params, msg, callback) {
			this.init(params, () => {
				this.request(route, msg, callback);
			});
		};

		/**
		 * 初始化客户端连接
		 * @param {StructParam} params 初始化服务器时的参数
		 */
		init(params, cb) {
			this.initCallback = cb;

			this.enData = params.enData;
			this.deData = params.deData;

			let url = params.cert ? `wss://${params.url}` : `ws://${params.url}`;
			// let url = params.cert ? `wss://${params.url}` : `ws://192.168.0.80:5100`;
			// let url = `wss://${params.url}`

			this.handshakeCallback = params.handshakeCallback;
			this.serverName = params.serverName || url;
			this.serverTime = params.serverTime || new Date();
			this.reconnect = params.reconnect;
			this.connect(params, url, cb);
		};

		/**
		 * 默认解码算法
		 * 
		 * @param {Uint8Array} data 数据
		 * @return {any} 消息对象
		 */
		defaultDecode(data) {
			/** 已解码的消息 */
			var msg = this.message.decode(data);

			// 如果消息有id,则从映射中查找映射的路由
			if (msg.id > 0) {
				msg.route = this.routeMap[msg.id];
				delete this.routeMap[msg.id];
				if (!msg.route) {
					return;
				};
			};

			msg.body = this.deCompose(msg);

			return msg;
		};

		/**
		 * 默认编码算法
		 * @param {Number} reqId 请求id
		 * @param {String} route 路由
		 * @param {String} msg 消息
		 * @return {Uint8Array} 
		 */
		defaultEncode(reqId, route, msg) {
			/** 消息类型，如果指定了请求id就是请求，否则就是通知 */
			var type = reqId ? this.message.TYPE_REQUEST : this.message.TYPE_NOTIFY;

			// 如果有protobuf就根据protobuf压缩消息
			if (this.protobuf && this.clientProtos[route]) {
				msg = this.protobuf.encode(route, msg);
			}
			// 如果没有
			else {
				msg = this.protocol.strencode(JSON.stringify(msg));
			};

			// 判断是否压缩路由
			var compressRoute = 0;
			if (this.dict && this.dict[route]) {
				route = this.dict[route];
				compressRoute = 1;
			};

			// 消息编码
			return this.message.encode(reqId, type, compressRoute, route, msg);
		};

		/**
		 * 连接服务器
		 * @param {StructParam} params 初始化服务器时的参数
		 * @param {String} url 服务器地址
		 * @param {Function} cb 回调
		 */
		connect(params, url, cb) {
			var params = params || {};

			// 配置最大重连尝试次数
			var maxReconnectAttempts = params.maxReconnectAttempts || this.DEFAULT_MAX_RECONNECT_ATTEMPTS;
			// 缓存重连时连接的地址
			this.reconnectUrl = url;
			// 设置protobuf版本
			this.handshakeBuffer.sys.protoVersion = this.protoVersion;

			// 定义打开连接事件
			var onopen = (event) => {
				// 已经重连上
				if (!!this.reconnected) {
					this.emit('reconnect');
				};
				// 重置重连设置
				this.reset();
				// 握手数据
				var obj = this.package.encode(this.package.TYPE_HANDSHAKE, this.protocol.strencode(JSON.stringify(this.handshakeBuffer)));
				// 发送数据
				this.send(obj);
			};

			// 定义接收到消息的事件
			var onmessage = (event) => {
				this.processPackage(this.package.decode(event.data), cb);
				// 更新心跳超时，校正时间
				if (this.heartbeatTimeout) {
					this.nextHeartbeatTimeout = Date.now() + this.heartbeatTimeout;
				};
			};

			// 定义连接错误时发生的事件
			var onerror = (event) => {
				this.emit('io-error', event);
			};

			// 定义socket关闭时触发的事件
			var onclose = (event) => {
				this.emit('close', { count: this.reconnectAttempts, maxCount: maxReconnectAttempts, isReconnect: this.reconnect });

				// 清除心跳id、心跳
				if (this.heartbeatId) {
					clearTimeout(this.heartbeatId);
					this.heartbeatId = null;
				};

				// 清除心跳超时id、心跳超时
				if (this.heartbeatTimeoutId) {
					clearTimeout(this.heartbeatTimeoutId);
					this.heartbeatTimeoutId = null;
				};

				this.socket = null;

				if (this.reconnect) {
					if (this.reconnectAttempts < maxReconnectAttempts) {
						this.reconnected = true;
						// 断线后第一次重连不延迟，如果第一次重连未连上，以后每次延迟为this.reconnectionDelay
						let reconnectionDelay = this.reconnectAttempts ? this.reconnectionDelay : 0;
						this.reconnectAttempts++;
						this.reconncetTimer = setTimeout(() => {
							this.connect(params, this.reconnectUrl, cb);
						}, reconnectionDelay);
					} else {
						console.debug(`----------ish5 ${this.serverName} socket reconnect limit${maxReconnectAttempts}`);
					};
				};
			};

			// 创建连接时，传入参数验证
			let str = '';
			if (params.serverName) {
				str = this.enData({
					token: params.token,
					serverName: params.serverName
				});
				str = `/?${str}`;
			};

			let fullUrl = `${url}${str}`;
			console.debug(`----------ish5 ${this.serverName} socket connect……`, fullUrl);
			console.log(params, "params.cert++++++++++++==")
			this.socket = params.cert ? new WebSocket(fullUrl, [], 'https://api.t8club.com/files/cacert.pem') : new WebSocket(fullUrl);
			this.socket.binaryType = 'arraybuffer';
			this.socket.onopen = onopen;
			this.socket.onmessage = onmessage;
			this.socket.onerror = onerror;
			this.socket.onclose = onclose;
		};

		/**
		 * 主动关闭与服务器的连接
		 * @param {any} param 触发时传入的参数
		 */
		disconnect(param) {
			clearTimeout(this.reconncetTimer);

			// 通知socket断开连接
			if (this.socket) {
				this.reconnect = false;
				this.socket.onclose = null;
				if (this.socket.close) this.socket.close();
				this.socket = null;
			};

			// 清除心跳id、心跳
			if (this.heartbeatId) {
				clearTimeout(this.heartbeatId);
				this.heartbeatId = null;
			};

			// 清除心跳超时id、心跳超时
			if (this.heartbeatTimeoutId) {
				clearTimeout(this.heartbeatTimeoutId);
				this.heartbeatTimeoutId = null;
			};

			// 触发客户端主动关闭的监听
			this.emit('disconnect', param);
		};

		/** 重置重连设置 */
		reset() {
			// 已重连上标志设置为false
			this.reconnected = false;
			// 重连延迟设置为3秒
			this.reconnectionDelay = 1000 * 3;
			// 尝试重连次数
			this.reconnectAttempts = 0;
			// 清空重连计时器
			clearTimeout(this.reconncetTimer);
		};

		/** 是否已经连上 */
		get isConnected() {
			return this.socket ? true : false;
		};

		/**
		 * 发送请求
		 * @param {String} 路由
		 * @param {any} 消息
		 * @param {Function} cb
		 */
		request(route, msg, cb) {
			if (typeof msg === 'function' && !cb) {
				cb = msg;
				msg = {};
			} else {
				msg = msg || {};
			};

			route = route || msg.route;
			if (!route) {
				return;
			};

			this.reqId++;
			console.debug(`----------ish5 ${this.serverName} socket request`, route, msg);
			this.sendMessage(this.reqId, route, msg);

			this.callbacks[this.reqId] = cb;
			this.routeMap[this.reqId] = route;
		};

		/**
		 * 发送请求
		 * @param {String} 路由
		 * @param {any} 消息
		 */
		notify(route, msg) {
			msg = msg || {};
			console.debug(`----------ish5 ${this.serverName} socket notify`, route, msg);

			this.sendMessage(0, route, msg);
		};

		/**
		 * 发送消息
		 * @param {Number} reqId
		 * @param {String} route
		 * @param {any} msg
		 */
		sendMessage(reqId, route, msg) {
			// 加密数据
			if (this.enData) {
				try {
					msg = JSON.parse(this.enData(msg));
				} catch (error) { }
			};

			msg = this.defaultEncode(reqId, route, msg);
			var packet = this.package.encode(this.package.TYPE_DATA, msg);
			this.send(packet);
		};

		/**
		 * 发送数据
		 * @param {Uint8Array} packet 封包数据
		 */
		send(packet) {
			// 如果没有socket连接，则不发送数据            
			if (!this.socket) return;
			if (!this.canSend) return;
			this.socket.send(packet.buffer);
		};

		/** 心跳的处理 */
		heartbeat(data) {
			/** heartbeatInterval是表示心跳的延迟，该变量的值来自于服务端的配置，如果没有获取到这个值，就不处理心跳 */
			if (!this.heartbeatInterval) {
				return;
			};

			// 心跳数据
			var obj = this.package.encode(this.package.TYPE_HEARTBEAT);
			// 如果还未触发心跳超时的回调，就清除上一次的
			if (this.heartbeatTimeoutId) {
				clearTimeout(this.heartbeatTimeoutId);
				this.heartbeatTimeoutId = null;
			};

			// 如果已存在心跳就退出
			if (this.heartbeatId) {
				return;
			};

			// 准备下一次心跳
			this.heartbeatId = setTimeout(() => {
				// 清空心跳计时器id
				this.heartbeatId = null;
				// 发送心跳数据
				this.send(obj);

				// 设置下一次的心跳超时时间
				this.nextHeartbeatTimeout = Date.now() + this.heartbeatTimeout;
				this.heartbeatTimeoutId = setTimeout(this.heartbeatTimeoutCb.bind(this), this.heartbeatTimeout);
			}, this.heartbeatInterval);
		};

		/** 心跳超时的回调 */
		heartbeatTimeoutCb() {
			// 心跳超时时间和当前时间的时间差
			var gap = this.nextHeartbeatTimeout - Date.now();
			// 如果时间差超出临界值
			if (gap > this.gapThreshold) {
				this.heartbeatTimeoutId = setTimeout(this.heartbeatTimeoutCb.bind(this), gap);
			};
		};

		/**
		 * 握手处理
		 * @param {Uint8Array} data
		 */
		handshake(data) {
			data = JSON.parse(this.protocol.strdecode(data));
			if (data.code === this.RES_OLD_CLIENT) {
				this.emit('error', 'Need update client version');
				return;
			};

			if (data.code !== this.RES_OK) {
				this.emit('error', 'Handshake failed');
				return;
			};

			// 初始化心跳
			this.handshakeInit(data);

			var obj = this.package.encode(this.package.TYPE_HANDSHAKE_ACK);
			this.send(obj);
			if (this.initCallback) {
				this.initCallback(this.socket);
			};
		};

		/** 处理数据 */
		onData(data) {
			let msg = this.defaultDecode(data);

			this.processMessage(msg);
		};

		/** 被踢下线 */
		onKick(data) {
			data = JSON.parse(this.protocol.strdecode(data));
			this.disconnect();
			this.emit('onKick', data);
			console.debug(`----------ish5 ${this.serverName} socket onKick`, data);
		};

		/** 处理数据包 */
		processPackage(msgs) {
			if (Array.isArray(msgs)) {
				for (var i = 0; i < msgs.length; i++) {
					var msg = msgs[i];
					this.handlers[msg.type](msg.body);
				};
			} else {
				this.handlers[msgs.type](msgs.body);
			};
		};

		/** 处理消息 */
		processMessage(msg) {
			if (!msg) return;

			// body解密
			let body = this.deData && msg.body ? this.deData(msg.body) : msg.body;
			if (body.serverTime) this.serverTime = body.serverTime;

			// 如果没有id，说明该消息需要客户端监听，否则通过id查找回调方法进行回调
			if (!msg.id) {
				console.debug(`----------ish5 ${this.serverName} socket push`, msg.route, body);
				this.emit(msg.route, body);
				return;
			};

			var cb = this.callbacks[msg.id];

			delete this.callbacks[msg.id];
			if (typeof cb !== 'function') {
				return;
			};

			console.debug(`----------ish5 ${this.serverName} socket callback ${msg.route}`, body);
			cb(body);
			return;
		};

		/** 批量处理消息 */
		processMessageBatch(msgs) {
			for (var i = 0, l = msgs.length; i < l; i++) {
				this.processMessage(msgs[i]);
			};
		};

		/** 解压消息数据 */
		deCompose(msg) {
			// 获取消息路由（字符串）
			var route = msg.route;

			// 如果定义了路由压缩
			if (msg.compressRoute) {
				// 寻找对应的路由
				if (!this.abbrs[route]) {
					return {};
				};

				// 如果字典配置了路由，获取对应的路由
				route = msg.route = this.abbrs[route];
			};

			if (this.protobuf && this.serverProtos[route]) {
				return this.protobuf.decode(route, msg.body);
			} else {
				return JSON.parse(this.protocol.strdecode(msg.body));
			};
		};

		/** 获取服务器提供的客户端相关配置 */
		handshakeInit(data) {
			if (data.sys && data.sys.heartbeat) {
				this.heartbeatInterval = data.sys.heartbeat * 1000;
				this.heartbeatTimeout = this.heartbeatInterval * 2;
			} else {
				this.heartbeatInterval = 0;
				this.heartbeatTimeout = 0;
			};

			this.initData(data);

			if (typeof this.handshakeCallback === 'function') {
				this.handshakeCallback(data.user);
			};
		};

		/** 获取服务器提供的客户端相关配置 */
		initData(data) {
			if (!data || !data.sys) {
				return;
			};
			// 服务配置的压缩路由字符串的字典
			var dict = data.sys.dict;
			// 服务端配置的protos
			var protos = data.sys.protos;

			// 初始化压缩字典
			if (dict) {
				this.dict = dict;
				this.abbrs = {};

				for (var route in dict) {
					this.abbrs[dict[route]] = route;
				};
			};

			// 初始化protobuf
			if (protos) {
				this.protoVersion = protos.version || 0;
				this.serverProtos = protos.server || {};
				this.clientProtos = protos.client || {};

				if (!!this.protobuf) {
					this.protobuf.init({ encoderProtos: protos.client, decoderProtos: protos.server });
				};
			};
		};
	};
}