//  example:
//
//  var ws = new WebSocketClient("ws://localhost");
//  ws.onmessage((data) => {
//      console.log("received", data);
//  }

function WebSocketClient(url) {
    // readyState
    // 0 (WebSocket.CONNECTING) // 正在链接中
    // 1 (WebSocket.OPEN) // 已经链接并且可以通讯
    // 2 (WebSocket.CLOSING) // 连接正在关闭
    // 3 (WebSocket.CLOSED) // 连接已关闭或者没有链接成功

    var _url = url;
    var _instance = null;
    var _provider = window.WebSocket || window.MozWebSocket;
    var _onmessage = [];
    var _onopen = [];
    var _onclose = [];

    function addEvent(queue, callback) {
        if (typeof callback == "function") {
            queue.push(callback);
        }
    }

    function raiseEvent(queue, data, event) {
        for (var i = 0; i < queue.length; i++) {
            var result = queue[i](data);
            if (result) break;
        }
    }

    function connect(url) {
        if (typeof url == "string" && url.length) _url = url;

        try {
            _instance = new _provider(_url);
            _instance.onopen = (event) => {
                ping(_instance);
                raiseEvent(_onopen, event);
            };
            _instance.onclose = (event) => {
                setTimeout(connect, 200);
                raiseEvent(_onclose, event);
            };
            _instance.onerror = (event) => {
                switch (_instance.readyState) {
                    case 2:
                        _instance.onmessage = null;
                        break;
                    case 3:
                        _instance.onopen = null;
                        _instance.onclose = null;
                        _instance.onmessage = null;
                        _instance.close();
                        connect();
                        break;
                }
            };
            _instance.onmessage = (event) => {
                var data = event.data;
                if (!data) return;

                // ping / pong
                if (typeof data == "string" && data.length == 4) {
                    var lower = data.toLowerCase();
                    switch (lower) {
                        case "ping":
                            return send("pong");
                        case "pong":
                            return;
                    }
                }

                try { data = JSON.parse(data); } catch { }
                raiseEvent(_onmessage, data, event);
            };
        } catch (error) {
            setTimeout(connect, 1000);
        }
    }

    function ping(instance) {
        if (_instance != instance) return;
        switch (_instance.readyState) {
            case 1:
                setTimeout(() => ping(instance), 10000);
                _instance.send("ping");
                break;
        }
    }

    function send(text) {
        if (_instance == null) return;
        _instance.send(text);
    }

    function onmessage(callback) {
        addEvent(_onmessage, callback);
    }

    function onopen(callback) {
        addEvent(_onopen, callback);
    }

    function onclose(callback) {
        addEvent(_onclose, callback);
    }

    if (_url && _provider) connect();
    return {
        connect,
        send,
        onmessage: (callback) => addEvent(_onmessage, callback),
        onopen: (callback) => addEvent(_onopen, callback),
        onclose: (callback) => addEvent(_onclose, callback)
    };
}
