import { Action } from "../CSharp/System/Action";
import { Encoding } from "../CSharp/System/Text/Encoding";
import { BaseEnumerator } from "../CoroutineV2/Core/BaseEnumerator";
import { INetRequest } from "./Core/INetRequest";
import { INetResponse } from "./Core/INetResponse";
import NetConfig from "./NetConfig";

export namespace Socket {
	export const Connect = Symbol("socket.connect");
	export const Message = Symbol("socket.message");
	export const Disconnect = Symbol("socket.disconnect");
	export const Error = Symbol("socket.error");
}

export interface Func {
	[Socket.Connect]: () => void,
	[Socket.Message]: (e) => void,
	[Socket.Disconnect]: () => void,
	[Socket.Error]: () => void,
}

export class NetConnector {
	readonly OnDataReceived: Action<INetResponse<any>> = new Action<INetResponse<any>>();
	readonly OnDisconnected: Action<void> = new Action<void>();
	readonly OnLoadUIMask: Action<boolean> = new Action<boolean>();

	get IsConnected() {
		return this._ws && this._ws.readyState === WebSocket.OPEN;
	}

	public get ws(): WebSocket {
		return this._ws;
	}

	private _host: string;
	private _ws: WebSocket;
	private _waitings: WsRequestEnumerator[] = [];

	constructor(host: string, port: number) {
		let checkHttp: string = "";
		let index: number = host.indexOf("https://");
		if (index != -1) {
			checkHttp = "https";
			host = host.replace("https://", "");
		} else {
			checkHttp = window.location.href.substring(0, 5);
			host = host.replace("http://", "");
		}
		// if (CC_DEBUG) {
		console.debug("[事件]checkHttp=", checkHttp, host, port);
		// }
		if (checkHttp != "https") {
			this._host = `ws://${host}:${port}`;
		} else {
			this._host = `wss://${host}:${port}`;
		}
	}

	ConnectAsync() {
		if (this._ws) {
			throw new Error("請先執行CasinoNetManager.Disconnect()中斷連線");
		}
		this._ws = new WebSocket(this._host);

		this._ws.binaryType = "arraybuffer";
		this._ws.onopen = this.OnWebSocketOpen.bind(this);
		this._ws.onmessage = this.OnWebSocketMessage.bind(this);
		this._ws.onclose = this.OnWebSocketClose.bind(this);
		this._ws.onerror = this.OnWebSocketError.bind(this);

		return new WsConnectEnumerator(this._ws);
	}

	Send(req: INetRequest<any, any>) {
		if (!this.IsConnected) return;

		let json = [req.Method];
		if (req.Data != null && req.Data != undefined && !Number.isNaN(req.Data)) {
			json[1] = req.Data;
		}

		// if (CC_DEBUG && NetConfig.ShowServerLog) {
		if (NetConfig.ShowServerLog) {
			if (req.Data != null && req.Data != undefined && !Number.isNaN(req.Data)) {
				console.log(`[RPC] 傳送server資料: ${req.Method}(${JSON.stringify(req.Data)})`);
			} else {
				console.log(`[RPC] 傳送server資料: ${req.Method}()`);
			}
		}

		let str = JSON.stringify(json);
		if (str.length > 65535) {
			throw new Error("要傳的資料太大囉");
		}

		let strary = Encoding.UTF8.GetBytes(str);
		let buffer = new Uint8Array(4 + strary.byteLength);
		let u16ary = new Uint16Array(buffer.buffer, 0, 3);
		u16ary[0] = strary.byteLength;
		buffer[3] = 0x01;
		buffer.set(strary, 4);

		this._ws.send(buffer);
	}

	SendAsync(req: INetRequest<any, any>, mask: boolean) {
		let iterator = new WsRequestEnumerator(req);
		if (!this.IsConnected) {
			iterator.SetResponse(ErrorResponse);
		} else {
			this._waitings.push(iterator);
			if (mask) {
				this.OnLoadUIMask.DispatchCallback(true);
			}
			this.Send(req);
		}
		return iterator;
	}

	Disconnect() {
		this.WebSocketEnded();
	}

	private WebSocketEnded() {
		if (!this._ws) return;

		this._ws.close();
		this._ws.onopen = null;
		this._ws.onmessage = null;
		this._ws.onclose = () => {
		};
		this._ws = null;

		this.CleanWaitings();
		this.OnDisconnected.DispatchCallback();
	}

	private CleanWaitings() {
		for (let w of this._waitings) {
			w.SetResponse(ErrorResponse);
			this.OnLoadUIMask.DispatchCallback(false);
		}
		this._waitings.length = 0;
	}

	private OnWebSocketOpen(e: Event) {
		console.debug(`[RPC] ${this._host} Connected.`);
	}

	private OnWebSocketMessage(e: MessageEvent) {
		if (e.data instanceof ArrayBuffer) {
			this.ParseRpcMessage(e.data, e);
		} else if (e.data instanceof Blob) {
			let reader = new FileReader();
			reader.onload = (e) => {
				this.ParseRpcMessage(<ArrayBuffer>reader.result, e);
				reader.onload = null;
			};
			reader.readAsArrayBuffer(e.data);
		} else {
			throw new Error(`未知的OnWebSocketMessage(e.data)類型: ${e.data}`);
		}
	}

	private ParseRpcMessage(buffer: ArrayBuffer, e: any) {
		let startIndex = 0, byteLength = buffer.byteLength;
		while (startIndex + 4 < byteLength) {
			let strlen = new DataView(buffer, startIndex, 3).getUint16(0, true);
			let str = Encoding.UTF8.GetString(new Uint8Array(buffer, startIndex + 4, strlen));
			startIndex += strlen + 4;

			try {
				let json = JSON.parse(str);
				let method = <string>json[0];
				let status = <number>json[1][0];
				let data = json[1][1];

				let resp = <INetResponse<any>>{
					Method: method,
					Status: status,
					Data: data,
					IsValid: method && status === 0
				};

				// if (CC_DEBUG && NetConfig.ShowServerLog) {
				if (NetConfig.ShowServerLog) {
					if (data) {
						console.log(`[RPC] 收到server呼叫:(${resp.Status}): ${resp.Method}(${JSON.stringify(resp.Data)})`);
					} else {
						console.log(`[RPC] 收到server呼叫:(${resp.Status}): ${resp.Method}()`);
					}
				}

				let dispatch = true;
				let isCocos = false;
				for (let i = 0, len = this._waitings.length; i < len; i++) {
					let w = this._waitings[i];
					if (w.MethodBack === resp.Method) {
						dispatch = false;
						this._waitings.splice(i, 1);
						w.SetResponse(resp);
						this.OnLoadUIMask.DispatchCallback(false);
						break;
					}
				}

				if (dispatch) {
					this.OnDataReceived.DispatchCallback(resp);
				}
			} catch {
				throw new Error(`[RPC] 無法解析Server回應: ${str}`);
			}
		}
	}

	private OnWebSocketClose(e: CloseEvent) {
		this.WebSocketEnded();
	}

	private OnWebSocketError(e: CloseEvent) {

	}
}

const ErrorResponse: INetResponse<any> = {
	Status: -1,
	Method: "",
	Data: {},
	IsValid: false,
};

class WsConnectEnumerator extends BaseEnumerator {
	private _ws: WebSocket;

	constructor(ws: WebSocket) {
		super();
		this._ws = ws;
	}

	next(value?: any): IteratorResult<any> {
		return {
			done: this._ws.readyState === WebSocket.OPEN || this._ws.readyState === WebSocket.CLOSED,
			value: undefined
		};
	}
}

class WsRequestEnumerator extends BaseEnumerator {
	readonly MethodBack: string;

	private _req: INetRequest<any, any>;
	private _done: boolean = false;

	constructor(req: INetRequest<any, any>) {
		super();

		this._req = req;
		this.MethodBack = req.MethodBack;
	}

	SetResponse(resp: INetResponse<any>) {
		this._req.Result = resp;
		this._done = true;
	}

	next(value?: any): IteratorResult<any> {
		return {
			done: this._done,
			value: undefined
		};
	}
}