// 与客户端交互方法列表
import wsPlugins from './plugins/index';
// const ReWebSocket = require('reconnecting-websocket');
// websocket重连
import ReWebSocket from '../node_modules/reconnecting-websocket/dist/reconnecting-websocket-cjs';
// const Bowser = require('bowser');
// 浏览器信息获取
import Bowser from "../node_modules/bowser/es5";
const browser = Bowser.getParser(window.navigator.userAgent);

interface callbacks {
	// 连接客户端状态
	connectResult: any,
	// 登录客户端状态
	loginResult: any
}


export default class Ws {
	[x: string]: any;
	// 连接地址
	static url: string = '';
	// 传入的配置项（方法）
	static opts: Object = {};
	// 密码加密公钥
	static publicKey = '';
	// 是否连接客户端
	private isConnectSuccessQt: Boolean = false;
	// 是否登录客户端
	private isLoginSuccess: Boolean = false;
	// 唯一实例
	private static _instance: any = null;
	// 当前连接的webSocket
	webSocket: any;
	// 最大重连数
	reConnectCount: number = 3;
	// 连接失败次数
	connectFailCount: number = 0;
	// 重连完成标识
	connectEnd: Boolean = false;
	// 登录完成标识
	loginEnd: Boolean = false;
	// 最大重登数
	reLoginCount: number = 1;
	// 连接失败次数
	loginFailCount: number = 0;
	// 当前websocket登录配置
	config: Object = {};
	// 登陆IP
	loginIp: string;
	// 登陆端口
	loginPort: string;
	// 登录用户名
	userName: string;
	// 登录密码，密码与token二选一
	userPwd: string;
	// 登录token，密码与token二选一
	token: string;
	// 用户标识符
	userCode: Number = 0;
	// 回调函数
	private callback: callbacks;
	// 创建的控件ID列表
	ids: Array<String> = [];
	// 创建的控件列表
	ctrls: Array<String> = [];
	// 心跳
	heartBeatTimer: any = null;
	// 监听的方法表
	listerns: any;
	constructor({
		url= 'ws://localhost:1234',
		publicKey= 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDbEpPpxpLJft4W9YZj8bRh2bYYZshBEsKOlxgyn11rlEyTasjBSZRV9aj33tvQ2T55izH0fWl+dL/dLZChawFrlGDcH8JuWge2xYMgII9mggcYa0UiQ7pLXJ9ivXZ/cOY3HzrRQdR7dGTSNn3Z0Ctbns6mLgvlA2r3qMNs/8wHBwIDAQAB',
		reConnectCount = 3,
		reLoginCount = 1,
		loginIp= location.hostname,
		loginPort= location.port,
		userName = '',
		userPwd = '',
		token = '',
		callback = {
			connectResult: null,
			loginResult: null
		}
	} : {
		url: string,
		publicKey: string,
		reConnectCount: number,
		reLoginCount: number,
		loginIp: string,
		loginPort: string,
		userName: string,
		userPwd: string,
		token: string
		callback: callbacks,
	}) {
		// if (/https/.test(location.protocol)) {
		// 	url = `wss:${url}`;
		// } else {
		// 	url = `ws:${url}`;
		// }
		// url = `ws://localhost:1234`;

		this.url = url;
		this.userCode = new Date().valueOf();
		this.webSocket = null;
		this.reConnectCount = reConnectCount;
		this.reLoginCount = reLoginCount;
		this.publicKey = publicKey;
		this.loginIp = loginIp;
		this.loginPort = loginPort;
		this.userName = userName;
		this.userPwd = userPwd;
		this.token = token;
		this.callback = callback;
		
		this.connectFailCount = this.connectFailCount;

		// 基础操作所需功能或属性
		this.ids = [];
		this.ctrls = [];

		// 是否连接客户端
		this.isConnectSuccessQt = false;
		// 是否登陆客户端
		this.isLoginSuccess = false;
		this.heartBeatTimer = null;

		// 初始化配置参数
		this.initConfig();

		// 连接客户端
		this.connectQt();

		// 获取与客户端交互方法列表
		const plugins = [];
		Object.keys(wsPlugins).forEach(item => {
			if(hasKey(wsPlugins, item)){
				plugins.push(wsPlugins[item]);
			}
		});
		// 与客户端通讯功能注册
		usePlugin(plugins);

		// 用户注册监听事件表
		this.listerns = new Map();

		// 传入登录配置，则登录
		if(this.userName) {
			this.detectConnectQt().then((res: Boolean) => {
				if(res) {
					// 登陆客户端
					this.loginClient();
					// 心跳保活
					this._heartbeat();
				}
			});
		}
	}
	/**
	 * @description 获得实例对象
	 */
	public static getInstance(options: any):Ws {
		if(!this._instance) {
			this._instance = new Ws(options);
		}
		return this._instance;
	}
	/**
	 * @description 用户注册监听事件
	 * @params {String} eventType 事件名称
	 * @params {any} callback 回调函数
	 */
	on(eventType: String, callback: any) {
		this.listerns.set(eventType, callback);
	}
	/**
	 * @description 用户取消监听事件
	 * @params {String} eventType 事件名称
	 */
	off(eventType: string) {
		delete this.listerns[eventType];
	}
	/**
	 * @description 发送消息给客户端
	 * @params {String} method 事件名称
	 * @params {Object} data 传输消息的数据内容
	 */
	emit(method: any, data: { method: any; }) {
		const { webSocket } = this;
		data.method = method;
		// 不需要判断登录和连接的方法过滤
		let filterList = ['heartbeat', 'login', 'logout', 'browserInfo'];
		if(filterList.includes(method)) {
			webSocket.send(JSON.stringify(data));
			return;
		}
		return new Promise((resolve, reject) => {
			this.detectConnectQt().then(con => {
				if(con) {
					this.detectLoginClient().then(login => {
						if(login) {
							webSocket.send(JSON.stringify(data));
							resolve(true);
						} else {
							// 登录失败
							reject(2);
						}
					})
				} else {
					// 连接失败
					reject(1);
				}
			})
		})
	}
	/*
	* 初始化配置
	*/
	initConfig() {
		this.config = {
			userName: this.userName,
			userCode: this.userCode,
			loginPort: this.loginPort,
			loginIp: this.loginIp,
			userPwd: this.userPwd,
			token: this.token,
		};
		// 浏览器信息
		const browserInfo = {
			name: '',
			version: '',
			platform: ''
		};
		browserInfo.name = browser.getBrowserName().toLowerCase();
		browserInfo.version = browser.getBrowser().version.toLowerCase();
		browserInfo.platform =
			browser._ua.indexOf('Win64') >= 0 || browser._ua.indexOf('Wow64') >= 0
				? 'win64'
				: 'win32';
		this.config['browser'] = browserInfo;
	}
	/**
	 * @description 连接客户端
	 */
	connectQt() {
		this.connectEnd = false;
		// 连接客户端
		this.webSocket = new ReWebSocket(this.url, '', {
			maxRetries: this.reConnectCount
		});
		this.addEvents();
	}
	/**
	 * @description 检测连接客户端状态
	 */
	detectConnectQt() {
		let _this = this;
		return new Promise((resolve, reject) => {
			if(!this.connectEnd) { // 连接中
				let _interval = setInterval(() => {
					if(_this.connectEnd) {
						clearInterval(_interval);
						resolve(_this.isConnectSuccessQt);
					}
				}, 50)
			} else {
				resolve(_this.isConnectSuccessQt);
			}
		})
	}
	/**
	 * @description 检测登录客户端状态
	 */
	detectLoginClient() {
		let _this = this;
		return new Promise((resolve, reject) => {
			if(!this.loginEnd) { // 连接中
				let _interval = setInterval(() => {
					if(_this.loginEnd) {
						clearInterval(_interval);
						resolve(_this.isLoginSuccess);
					}
				}, 50)
			} else {
				resolve(_this.isLoginSuccess);
			}
		})
	}
	/**
	 * @description 登录客户端
	 * @params {Object} config 登录相关配置
	 */
	login(config: Object) {
		this.loginEnd = false;
		Object.assign(this.config, config);
		// 未连接客户端
		if(!this.isOpen()) {
			// 连接失败
			return false
		} else {
			// 若已登录，先注销
			if (this.isLoginSuccess) {
				this.logout();
			}
			// 登陆客户端
			this.loginClient();
			// 心跳保活
			this._heartbeat();
		}
	}
	/**
	 * @description 连接客户端完成后登陆
	 */
	// loginAfterConnectEnd() {
	// 	// 未连接客户端
	// 	if(!this.isOpen()) {
	// 		// 连接失败
	// 		return false
	// 	} else {
	// 		// 若已登录，先注销
	// 		if (this.isLoginSuccess) {
	// 			this.logout();
	// 		}
	// 		// 登陆客户端
	// 		this.loginClient();
	// 		// 心跳保活
	// 		this._heartbeat();
	// 	}
	// }
	/**
	 * @description 登出客户端
	 */
	logout() {
		// 退出客户端
		this.logoutClient();
		this.isLoginSuccess = false;
		if (typeof this.callback.loginResult === 'function') {
			this.callback.loginResult.call(this, this.isLoginSuccess);
		}
	}
	/**
	 * @description 添加websocket/window监听事件
	 */
	addEvents() {
		const webSocket = this.webSocket;
		webSocket.addEventListener('open', () => this.onOpen());
		webSocket.addEventListener('message', (e: any) => this.onMessage(e));
		webSocket.addEventListener('error', () => this.onError());
		window.addEventListener('resize', () => this.reLocatedPosition());
		window.addEventListener('scroll', () => this.reLocatedPosition());
	}
	/**
	 * @description 移除websocket/window监听事件
	 */
	removeEvents() {
		const webSocket = this.webSocket;
		webSocket.removeEventListener('open', this.onOpen);
		webSocket.removeEventListener('message', this.onMessage);
		webSocket.removeEventListener('error', this.onError);
		webSocket.removeEventListener('resize', this.reLocatedPosition);
		webSocket.removeEventListener('scroll', this.reLocatedPosition);
	}
	/**
	 * @description 连接客户端成功事件
	 */
	onOpen() {
		this.isConnectSuccessQt = true;
		this.connectEnd = true;
		if (typeof this.callback.connectResult === 'function') {
			this.callback.connectResult.call(this, this.isConnectSuccessQt);
		}
	}
	/**
	 * @description 接收客户端消息
	 * @params {Object} event 接收客户端的消息数据
	 */
	onMessage(event: { data: string; }) {
		try {
			const data = JSON.parse(event.data);
			const { method } = data;
			const callback = this.listerns.get(method);
			if (method === 'loginState') {
				this.loginFailCount ++;
				if(this.loginFailCount < this.reLoginCount + 1) { // 登录未到最大次数
					if(data.params.loginResult === 0) { // 登录成功
						this.isLoginSuccess = data.params.loginResult === 0;
						if (typeof this.callback.loginResult === 'function') {
							this.callback.loginResult.call(this, this.isLoginSuccess);
						}
					} else { // 登录失败
						this.loginClient();
					}
				} else { // 登录达到最大次数
					this.loginEnd = true;
					this.isLoginSuccess = data.params.loginResult === 0;
					if (typeof this.callback.loginResult === 'function') {
						this.callback.loginResult.call(this, this.isLoginSuccess);
					}
				}
			}
			if (callback) {
				if (method === 'loginState') {
					callback(this.isLoginSuccess);
				} else if (method === 'createCtrlResult') {
					callback(data.params.array);
				} else {
					callback(data);
				}
			}
		} catch (e) {
			// console.error('error', e);
		}
	}
	/**
	 * @description 客户端发生错误事件
	 */
	onError() {
		this.isConnectSuccessQt = false;
		clearTimeout(this.heartbeatTimer);
		this.connectFailCount ++;
		if(this.connectFailCount === this.reConnectCount + 1) {
			this.connectEnd = true;
			if (typeof this.callback.connectResult === 'function') {
				this.callback.connectResult.call(this, this.isConnectSuccessQt);
			}
		}
	}
	/**
	 * @description 判断是否成功连接客户端
	 */
	isOpen() {
		if (!this.webSocket) return false;
		return this.webSocket.readyState === 1;
	}
	/**
	 * @description 心跳事件
	 */
	_heartbeat() {
		this.heartbeat();
		clearTimeout(this.heartbeatTimer);
		this.heartbeatTimer = setTimeout(() => {
			this._heartbeat();
		}, 10000);
	}
	/**
	 * @description 获取当前浏览器缩放和滚动条信息
	 */
	getScrollInfo() {
		let ratio = detectZoom();
		let scrollX = window.pageXOffset;
		let scrollY = window.pageYOffset;
		var hasscrollbary = hasScrollbarY();
		var hasscrollbarx = hasScrollbarX();
		var scrollbarWidth = getScrollbarWidth();
		let scrollXH = hasscrollbarx ? scrollbarWidth : 0;
		let scrollYW = hasscrollbary ? scrollbarWidth : 0;
		return { ratio, scrollX, scrollY, scrollXH, scrollYW };
	}
}

function promisify(func: Function) {
	return new	Promise((resolve, reject) => {
		func('1', (data) => resolve(data))
	})
}

/**
 * @description 判断对象是否含有某属性
 * @params {Object} obj 对象
 * @params {String} key 属性key
 */
function hasKey<O>(obj: O, key: keyof any): key is keyof O {
	return key in obj
}

/**
 * @description 与客户端通讯功能注册
 * @params {Array} plugins 功能列表
 */
function usePlugin(plugins: any[]) {
	plugins.forEach((plugin) => {
		Object.getOwnPropertyNames(plugin).forEach(prop => {
			Ws.prototype[prop] = plugin[prop];
		});
	});
}

function detectZoom() {
	var ratio = 0,
		screen = window.screen,
		ua = navigator.userAgent.toLowerCase();

	if (window.devicePixelRatio !== undefined) {
		ratio = window.devicePixelRatio;
	} else if (~ua.indexOf('msie')) {
		if (screen['deviceXDPI'] && screen['logicalXDPI']) {
			ratio = screen['deviceXDPI'] / screen['logicalXDPI'];
		}
	} else if (window.outerWidth !== undefined && window.innerWidth !== undefined) {
		ratio = window.outerWidth / window.innerWidth;
	}

	if (ratio) {
		ratio = Math.round(ratio * 100);
	}
	return ratio;
}

function hasScrollbarY() {
	return (
		document.body.scrollHeight >
		(window.innerHeight || document.documentElement.clientHeight)
	);
}

function hasScrollbarX() {
	return (
		document.body.scrollWidth >
		(window.innerWidth || document.documentElement.clientWidth)
	);
}

function getScrollbarWidth() {
	var scrollDiv = document.createElement('div');
	scrollDiv.style.cssText =
		'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;';
	document.body.appendChild(scrollDiv);
	var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
	document.body.removeChild(scrollDiv);

	return scrollbarWidth;
}