(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['socket.io-client'], function (io) {
            return (root.returnExportsGlobal = factory(io));
        });
    } else if (typeof module === 'object' && module.exports) {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like enviroments that support module.exports,
        // like Node.
        module.exports = factory(require('socket.io-client'));
    } else {
        // Browser globals
        root.ZPush = factory(root.io);
    }
}(this, function(io) {

var ZPush;
var DEF_SERVER = 'push2.zzwtec.com';
var DEF_PORT = 9906;
var MSG_MISSING_MESSAGE = 'Missing Message';
var MSG_MISSING_CHANNEL = 'Missing Channel';
var MSG_ERROR_CHANNEL = 'Topic 只支持英文数字下划线，长度不超过128个字符。';
var MSG_MISSING_ALIAS = 'Missing Alias';
var MSG_ERROR_ALIAS = 'Alias 只支持英文数字下划线，长度不超过128个字符。';
var MSG_SUB_FAIL = '订阅失败';
var MSG_MISSING_CALLBACK = 'Missing Callback';
var MSG_SUB_REPEAT_ERROR = '不能重复订阅一个频道';
var MSG_UNSUB_FAIL = '取消订阅操作失败';
var MSG_CONNECT_FAIL = '连接 ZPush 服务失败';
var MSG_DISCONNECT_FAIL = '关闭连接失败';
var MSG_NO_THIS_CHANNEL = '未订阅该频道';
var MSG_PUB_FAIL = '信息发布失败';
var MSG_NEED_CONNECT = '请先连接到 ZPush 服务';
var MSG_NEED_SOCKET_CONNECT = 'JavaScript SDK 与消息服务器已经断开链接，请刷新页面重新链接。';
var MSG_SESSION_IN_USE = 'the session id is in use';

var __error = function (msg) {
    __log(msg);
    return false;
};

var __log = function (msg) {
    if (typeof console != "undefined" && typeof console.log != "undefined") {
        console.log(msg);
    }
};

var __NumUtil = {
    get: function () {
        var randomness = Math.round(Math.random() * 1e16) % Math.pow(2, 23);

        if (randomness.toString(2).length > 23) {
            randomness = (randomness >>> (randomness.toString(2).length - 23)).toString(2);
        } else {
            randomness = (randomness << (23 - randomness.toString(2).length)).toString(2);
        }

        var timestamp = (new Date().getTime()).toString(2);

        return parseInt(timestamp, 2).toString() + parseInt(randomness, 2).toString();
    }
};

var isBrowser = typeof window === 'object' && window.window === window;

if (isBrowser) {
    if (location.protocol == 'https:') {
        DEF_SERVER = 'https://push.zpush.com';
        DEF_PORT = 9906;
    }
}

    function ZPush(setup) {
        setup = setup || {};
        this.secure = setup['secure'] === undefined ? false : setup['secure'];
        this.server = setup['server'] || DEF_SERVER;
        this.port = setup['port'] || DEF_PORT;
        this.auto_reconnect = setup['auto_reconnect'] || false;

        this.connected = false;//mqtt连接状态
        this.socket_connected = false;//socket.io连接状态
    }

    ZPush.prototype.init = function (init_callback, rec_callback) {
        var me = this;
        init_callback = init_callback || function () {
            };
        rec_callback = rec_callback || function () {
            };
        me.message_cb = function (data) {
        };
        me.pull_cb = function(){};
        me.do_push_cb = {};
        me.register_cb = {};
        me.heartbeat_cb = function () {
        };
        me.get_state_cb = function () {
        };

        me.info_status_cb = {};
        me.get_topic_cb = {};
        me.alias=null;
        me.sid=null;

        var socketio_connect = function () {
            try {
                __log('js client start init...');
                me.socket = io.connect(me.server + ':' + me.port);
                //连接回调
                me.socket.on('connect_c', function (sid) {
                    __log('js client init success.'+(sid!=null?'sid:'+sid:''));
                    if(sid!=null){
                    	me.sid=sid;
                    }
                    me.connected = true;
                    me.socket_connected = true;
                    init_callback(true);
                });
                //连接回调
                me.socket.on('connect', function (sid) {
                    __log('js client init success.'+(sid!=null?'sid:'+sid:''));
                    if(sid!=null){
                    	me.sid=sid;
                    }
                    me.connected = true;
                    me.socket_connected = true;
                    init_callback(true);
                });
                //错误事件
                me.socket.on('error', function (e) {
                    if (me.auto_reconnect) {
                        setTimeout(function () {
                            socketio_connect();
                        }, 1000);
                    } else {
                        __log('js client init error:', e);
                        me.socket_connected = false;
                        init_callback(false);
                    }
                });
                //断开事件
                me.socket.on('disconnect', function (sid) {
                    if (me.auto_reconnect) {
                        setTimeout(function () {
                            socketio_connect();
                        }, 1000);
                    } else {
                        __log('js client disconnect.');
                        me.connected = false;
                        me.socket_connected = false;
                        init_callback(false);
                    }
                });

                //推送回调 {'t':-6, 'num':''} 或 {'t':0,'error':'参数不全','num':''}
                me.socket.on('do_push_c', function (result) {
                	if(typeof(result) === 'string'){
                		result = eval('('+result+')');
                	}
                    if (parseInt(result.t)<0 && me.do_push_cb[result.num]) {
                        me.do_push_cb[result.num](true, {num: result.num,t:result.t});
                    } else {
                        if (me.do_push_cb[result.num]) {
                            me.do_push_cb[result.num](false, MSG_PUB_FAIL);
                        }
                        return __error(MSG_PUB_FAIL);
                    }
                });

                //拉取消息回调 { "t":-4, "msg":["消息内容"], "num":"" }
                me.socket.on('pull_c', function (result) {
                	if(typeof(result) === 'string'){
                		result = eval('('+result+')');
                	}
                	me.pull_cb(result.msg);
                });

                //推送消息处理 服务端主动推送消息给客户端 {"t":5,"msg":"","mid":""}
                me.socket.on('push', function (data) {
                	if(typeof(data) === 'string'){
                		data = eval('('+data+')');
                	}
                    if (me.socket_connected === false) {
                        return false;
                    }
                	me.socket.emit('push_c', {'t':-5,'alias':me.alias,'mid':data.mid});
                    me.message_cb(data.msg);
                });

                //心跳回调 { "t":-1 } 或 { "t":0, "error":"错误信息", "num":"" }
                me.socket.on('heartbeat_c', function (data) {
                	if(typeof(data) === 'string'){
                		data = eval('('+data+')');
                	}
                	me.heartbeat_cb(data);
                });

                //注册回调 { "t":-2, "num":"" } 或 { "t":0, "error":"错误信息", "num":"" }
                me.socket.on('register_c', function (result) {
                	if(typeof(result) === 'string'){
                		result = eval('('+result+')');
                	}
                    if (parseInt(result.t) < 0) {
                        me.register_cb[result.num](true);
                    } else {
                        me.register_cb[result.num](false, MSG_SUB_FAIL);
                        return __error(MSG_SUB_FAIL);
                    }
                });

                //获取别名状态回调 {"status":"offline 或 online","list":["频道"],"alias":alias}
                me.socket.on('info_status_c', function (data) {
                	if(typeof(data) === 'string'){
                		data = eval('('+data+')');
                	}
                    me.info_status_cb[data.alias]({
                        success: true,
                        data: data.status,
                        alias: data.alias
                    });
                });

                //获取已订阅频道回调 { "t":-3, "num":"", "alias":alias, "topic":[""] }
                me.socket.on('get_topic_c', function (result) {
                	if(typeof(result) === 'string'){
                		result = eval('('+result+')');
                	}
                    if (parseInt(result.t)<0) {
                        me.get_topic_cb[result.alias](true, {
                            topics: result.topic
                        });
                    } else {
                        me.get_topic_cb[result.alias](false, {
                            error_msg: result.error,
                            num: result.num
                        });
                    }
                });

            } catch (err) {
                if (me.auto_reconnect) {
                    setTimeout(function () {
                        socketio_connect();
                    }, 1000);
                } else {
                    return __error(MSG_CONNECT_FAIL) && init_callback(false, MSG_CONNECT_FAIL);
                }
            }
        };

        socketio_connect();
    };

    /**
     * 连接
     */
    ZPush.prototype.connect = function (callback) {
        if (this.socket_connected === false) {
            return false;
        }
        this.connect_cb = callback;

        try {
            this.socket.emit('connect', {'alias':this.alias});
        } catch (err) {
            return __error(err) && callback(false, err);
        }
    };

    /**
     * 断开连接
     */
    ZPush.prototype.disconnect = function (callback) {
        var self = this;
        if (!self.connected) {
            callback && callback(true);
            return;
        }
        try {
            this.socket.emit('disconnect', {'alias':this.alias});
            self.connected = false;
            callback && callback(true);
        } catch (err) {
            return __error(err) && callback(false, err);
        }
    };

    /**
     * 设置消息接收处理
     */
    ZPush.prototype.set_message_cb = function (cb) {
        this.message_cb = cb;
    };

    /**
     * 注册 订阅频道
     * @param args { "t":2, "topic":["频道"], "num":"" }
     * @return {'t':-2}
     */
    ZPush.prototype.register = function (args, callback) {

        if (this.socket_connected === false) {
            return false;
        }

        var topic = args['topic'];
        var num = args['num'] || __NumUtil.get();
        this.register_cb[num.toString()] = args['callback'] || callback || function () {
            };

        if (!this.connected) {
            return __error(MSG_NEED_CONNECT) && callback(false, MSG_NEED_CONNECT);
        }

        if (!this._validate_topic(topic, callback)) {
            return false;
        }

        try {
            this.socket.emit('register', { 't':2, 'alias': this.alias, 'topic': topic, 'num': num});
        } catch (err) {
            return __error(err) && callback(false, err);
        }
    };

    /**
     * 根据频道推送
     * @param args {'topic':[''],'msg':'','opts':{'time_to_live':60},'num':''}
     * @return {num: '',t:-6}
     */
    ZPush.prototype.publish = function (args, callback) {

        if (this.socket_connected === false) {
            return false;
        }

        if (!this.connected) {
            return __error(MSG_NEED_CONNECT) && callback(false, MSG_NEED_CONNECT);
        }

        var topic = args['topic'] ;
        var msg = args['msg'];
        var opts = args['opts'] || {'time_to_live':60};
        var num = args['num'] || __NumUtil.get();

        this.do_push_cb[num.toString()] = callback;

        var callback = args['callback'] || callback || function () {
            };

        if (!this._validate_topic(topic, callback)) {
            return false;
        } else if (!this._validate_message(msg, callback)) {
            return false;
        }

        try {
            this.socket.emit('do_push', {'t':'6', 'method':'publish', 'topic': topic, 'msg': msg, 'opts':opts, 'num': num});
        } catch (err) {
            return __error(err) && callback(false, err);
        }
    };

    /**
     * 根据别名推送
     * @param args {'alias':[''],'msg':'','opts':{'time_to_live':60},'num':''}
     * @return {num: '',t:-6}
     */
    ZPush.prototype.publish_to_alias = function (args, callback) {

        if (this.socket_connected === false) {
            return false;
        }

        if (!this.connected) {
            return __error(MSG_NEED_CONNECT) && callback(false, MSG_NEED_CONNECT);
        }

        var alias = args['alias'];
        var msg = args['msg'];
        var opts = args['opts'] || {'time_to_live':60};
        var num = args['num'] || __NumUtil.get();
        this.do_push_cb[num.toString()] = callback;

        var callback = args['callback'] || callback || function () {};

        if (!this._validate_alias(alias, callback)) {
            return false;
        } else if (!this._validate_message(msg, callback)) {
            return false;
        }

        try {
            this.socket.emit('do_push', {'t':'6', 'method':'publish_to_alias', 'alias': alias, 'msg': msg, 'opts':opts, 'num': num});
        } catch (err) {
            return __error(err) && callback(false, err);
        }
    };

    /**
     * 心跳
     * @return {'t':-1}
     */
    ZPush.prototype.heartbeat = function (alias, callback) {
        if (!alias) {
            return __error(MSG_MISSING_ALIAS) && callback(false, MSG_MISSING_ALIAS);
        } else if (alias.length > 128 || !/^([a-zA-Z0-9_]*)$/.test(alias)) {
            return __error(MSG_ERROR_ALIAS) && callback(false, MSG_ERROR_ALIAS);
        }
        this.alias = alias;
        this.heartbeat_cb = callback || function(data){};
        this.socket.emit('heartbeat', {'t': '1','alias': alias});
    };

    /**
     * 拉取消息
     * @return { "t":-4, "msg":["消息内容"], "num":"" }
     */
    ZPush.prototype.pull = function (callback) {
        var num = __NumUtil.get();
        this.pull_cb = callback;
        this.socket.emit('pull', {'t': '4', 'alias': this.alias, 'num':num});
    };

    /**
     * 获取别名的状态
     * @return {success: true,data: '',alias: ''}
     */
    ZPush.prototype.get_state = function (alias, callback) {
        this.info_status_cb[alias] = callback || function () {};
        this.socket.emit('info_status', {'alias': alias});
    };

    /**
     * 获取别名订阅的频道
     * @param alias 别名
     * @return {topics: ['']} 或 {error_msg: '',num: ''}
     */
    ZPush.prototype.get_topic_list = function (alias, callback) {
        this.get_topic_cb[alias] = callback || function () {};
        var num = __NumUtil.get();
        this.socket.emit('get_topic', {'t':'3', 'alias': alias, 'num':num});
    };

    /**
     * 验证频道
     */
    ZPush.prototype._validate_topic = function (topics, callback) {
        if (!topics) {
            return __error(MSG_MISSING_CHANNEL) && callback(false, MSG_MISSING_CHANNEL);
        } else{
        	if(topics.length==0){
        		return true;
        	}
        	for(var i=0;i<topics.length;i++){
        		var topic=topics[i];
        		if (topic.length > 128 || !/^([a-zA-Z0-9_]*)$/.test(topic)) {
                    return __error(MSG_ERROR_CHANNEL) && callback(false, MSG_ERROR_CHANNEL);
                }
        		
        	}
        	 
        }
        return true;
    };

    /**
     * 验证别名
     */
    ZPush.prototype._validate_alias = function (alias, callback) {
        if (!alias) {
            return __error(MSG_MISSING_ALIAS) && callback(false, MSG_MISSING_ALIAS);
        }else{
        	if(alias.length==0){
        		return true;
        	}
        	for(var i=0;i<alias.length;i++){
        		var alia=alias[i];
        		if (alia.length > 128 || !/^([a-zA-Z0-9_]*)$/.test(alia)) {
        			return __error(MSG_ERROR_ALIAS) && callback(false, MSG_ERROR_ALIAS);
                }
        		
        	}
        } 
        return true;
    };

    /**
     * 验证消息
     */
    ZPush.prototype._validate_message = function (message, callback) {
        if (!message) {
            return __error(MSG_MISSING_MESSAGE) && callback(false, MSG_MISSING_MESSAGE);
        }
        return true;
    };

    /**
     * 更新路径 重定向
     */
    ZPush.prototype._update_query_string = function (new_query_string) {
        var href = location.href;
        var rurl = (href.indexOf('?') ? href.substr(0, href.indexOf('?')) : href) + new_query_string;
        if (history && typeof history.replaceState === "function") {
            history.replaceState(null, null, rurl);
        } else {
            location.href = rurl;
        }
    };

    return ZPush;
    
}));
