(function(root) {
    root.Fnatic = {};
    root.Fnatic.utils = {
        guid: function() {
            //表示全局唯一标识符 (GUID)。
            function Guid(g) {
                var arr = new Array(); //存放32位数值的数组
                if (typeof(g) == "string") { //如果构造函数的参数为字符串
                    InitByString(arr, g);
                } else {
                    InitByOther(arr);
                }
                //返回一个值，该值指示 Guid 的两个实例是否表示同一个值。
                this.Equals = function(o) {
                    if (o && o.IsGuid) {
                        return this.ToString() == o.ToString();
                    } else {
                        return false;
                    }
                };
                //Guid对象的标记
                this.IsGuid = function() {};
                //返回 Guid 类的此实例值的 String 表示形式。
                this.ToString = function(format) {
                    if (typeof(format) == "string") {
                        if (format == "N" || format == "D" || format == "B" || format == "P") {
                            return ToStringWithFormat(arr, format);
                        } else {
                            return ToStringWithFormat(arr, "D");
                        }
                    } else {
                        return ToStringWithFormat(arr, "D");
                    }
                };
                //由字符串加载
                function InitByString(arr, g) {
                    g = g.replace(/\{|\(|\)|\}|-/g, "");
                    g = g.toLowerCase();
                    if (g.length != 32 || g.search(/[^0-9,a-f]/i) != -1) {
                        InitByOther(arr);
                    } else {
                        for (var i = 0; i < g.length; i++) {
                            arr.push(g[i]);
                        }
                    }
                };
                //由其他类型加载
                function InitByOther(arr) {
                    var i = 32;
                    while (i--) {
                        arr.push("0");
                    }

                };
                /*
                根据所提供的格式说明符，返回此 Guid 实例值的 String 表示形式。
                N  32 位： xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
                D  由连字符分隔的 32 位数字 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
                B  括在大括号中、由连字符分隔的 32 位数字：{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
                P  括在圆括号中、由连字符分隔的 32 位数字：(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
                */
                function ToStringWithFormat(arr, format) {
                    switch (format) {
                        case "N":
                            return arr.toString().replace(/,/g, "");
                        case "D":
                            var str = arr.slice(0, 8) + "-" + arr.slice(8, 12) + "-" + arr.slice(12, 16) + "-" + arr.slice(16, 20) + "-" + arr.slice(20, 32);
                            str = str.replace(/,/g, "");
                            return str;
                        case "B":
                            var str = ToStringWithFormat(arr, "D");
                            str = "{" + str + "}";
                            return str;
                        case "P":
                            var str = ToStringWithFormat(arr, "D");
                            str = "(" + str + ")";
                            return str;
                        default:
                            return new Guid();
                    }
                }
            };
            //Guid 类的默认实例，其值保证均为零。
            Guid.Empty = new Guid();
            //初始化 Guid 类的一个新实例。
            Guid.create = function() {
                var g = "";
                var i = 32;
                while (i--) {
                    g += Math.floor(Math.random() * 16.0).toString(16);
                }
                return new Guid(g);
            }

            return Guid;
        },
        dataformat: function(timestamp, format) {
            var time = new Date(timestamp * 1e3);
            var o = {
                "M+": time.getMonth() + 1,
                "d+": time.getDate(),
                "h+": time.getHours(),
                "m+": time.getMinutes(),
                "s+": time.getSeconds(),
                "q+": Math.floor((time.getMonth() + 3) / 3),
                "S": time.getMilliseconds()
            };
            if (/(y+)/.test(format) || /(Y+)/.test(format)) {
                format = format.replace(RegExp.$1, (time.getFullYear() + "").substr(4 - RegExp.$1.length));
            }
            for (var k in o) {
                if (new RegExp("(" + k + ")").test(format)) {
                    format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
                }
            }
            return format;
        }
    }
})(this);


(function(root) {

    var color = {
        success: 'green',
        failure: 'red',
        warning: '#e95ea1',
        event: 'blue'
    }
    var sdkgroup = {
        app: 'application',
        top: 'top'
    }

    var status = {
        1: 'ERROR',
        2: 'SUCCESS'
    }

    function getLoginuser() {
        return invoke(sdkgroup.app, 'getLoginuser').call();
    }

    function getVersion() {
        return invoke(sdkgroup.app, 'getVersion').call();
    }

    function app(cmd, param) {
        return invoke(sdkgroup.app, cmd, param).call();
    }

    function top(cmd, param) {
        return invoke(sdkgroup.top, cmd, param).call();
    }

    function invoke(sdkgroup, cmd, param) {
        var defer = $.Deferred();
        return function() {
            if (Fnatic.request.debugModel.all || Fnatic.request.debugModel.top && _mock && _mock[cmd]) {
                defer.resolve(_mock[cmd]);
            } else {
                QN[sdkgroup].invoke({
                    cmd: cmd,
                    param: param || {},
                    success: function(rsp) {
                        log(rsp, color.success);
                        defer.resolve(rsp);
                    },
                    error: function() {
                        log(arguments, color.failure, 0);
                        defer.resolve(arguments);
                    }
                })
            }

            return defer.promise();
        }
    }

    function registeEvent(regEvt, callback) {
        if (Fnatic.request.debugModel.all || Fnatic.request.debugModel.event) {
            Fnatic.debugPush[regEvt.replace(/\./g, '_')] = callback;
        }
        QN.event.regEvent({
            eventId: regEvt,
            notify: function(data, eventId) {
                log(arguments, color.event, 2);
                callback.call(this, data, eventId);
            }
        });
    }

    function log(msg, color, s) {
        console.log('%c【' + status[s === undefined ? 1 : s ? s : 0] + '】', 'color: ' + color + ';', msg);
    }

    root.Fnatic.debugPush = {};

    root.Fnatic.request = {
        debugModel: {
            top: false,
            event: false,
            all: false
        },
        getLoginuser: getLoginuser,
        getVersion: getVersion,
        top: top,
        app: app,
        registeEvent: registeEvent
    }
})(this);

(function(root) {
    var color = {
        success: 'green',
        failure: 'red',
        warning: '#e95ea1',
        info: 'blue'
    }
    var record = {
        success: function(message) {
            logger(message, color.success, 'SUCCESS');
        },
        failure: function(message) {
            logger(message, color.failure, 'FAILURE');
        },
        warning: function(message) {
            logger(message, color.warning, 'WARNING');
        },
        info: function(message) {
            logger(message, color.info, 'INFO');
        }
    }

    function logger(message, color, type) {
        console.log('%c【' + type + '】', 'color:' + color + ';', message);
    }
    root.Fnatic.record = record;
})(this);

(function(root) {
    root.Fnatic.css = {
        hasClass: function(obj, cls) {
            return obj.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
        },
        addClass: function(obj, cls) {
            if (!Fnatic.css.hasClass(obj, cls)) obj.className += " " + cls;
        },
        removeClass: function(obj, cls) {
            if (Fnatic.css.hasClass(obj, cls)) {
                var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
                obj.className = obj.className.replace(reg, ' ');
            }
        },
        toggleClass: function(obj, cls) {
            if (Fnatic.css.hasClass(obj, cls)) {
                Fnatic.css.removeClass(obj, cls);
            } else {
                Fnatic.css.addClass(obj, cls);
            }
        }
    }
})(this);

(function(root) {
    root.Fnatic.ui = {
        autoscroll: function(el) {
            setTimeout(function() {
                if (el.scrollHeight > el.scrollTop + el.offsetHeight) {
                    $(el).animate({
                        scrollTop: el.scrollHeight - el.offsetHeight
                    }, 300);
                }
            }, 100);
        },
        waterfall: function(el, callback) {
            var preload = 100;
            if (el.scrollHeight < el.scrollTop + el.offsetHeight + preload) {
                callback.call();
            }
        },
        dynload: function(config) {

            var Load = function() {
                this.el = config.el;
                this.$el = $(this.el);
                this.callback = config.callback;
                this.partial = null;

                this.onload = false;

                this.init();
            }

            Load.constant = {
                init: '正在加载，请稍后...',
                loaded: '加载完成',
                nomore: '没有更多数据了',
                className: 'pull-load'
            }

            Load.prototype = {
                init: function() {
                    var self = this;

                    this.desc = document.querySelector('.pull-load .desc');
                    this.close = document.querySelector('.pull-load .close');
                    this.partial = document.querySelector('.pull-load');

                    this.close.addEventListener('click', function(){
                        self.end();
                    })

                    this.desc.innerHTML = Load.constant.init;
                    this.el.addEventListener('mousewheel', function(evt) {
                        // console.log(evt)
                        if (!self.onload && !this.scrollTop && evt.wheelDelta > 0) {
                            self.onload = true;
                            Fnatic.css.addClass(self.partial, 'show');
                            self.callback();
                        }

                        if(evt.wheelDelta < 0 && self.onload){
                            self.end();
                        }
                    })
                },
                run: function() {
                    this.onload = false;
                    this.end();
                },
                stop: function() {
                    this.onload = true;
                },
                remind: function(str) {
                    this.desc.innerHTML = str;
                },
                reset: function(){
                    this.desc.innerHTML = Load.constant.init;
                    this.end();
                },
                end: function() {
                    this.onload = false;
                    Fnatic.css.removeClass(this.partial, 'show');
                }
            }
            return new Load();
        }
    }
})(this);

(function(root) {
    Fnatic.extend = function() {
        var isEs6 = Object.create instanceof Function;
        var isJq = jQuery instanceof Object;
        var args = [].slice.call(arguments);

        var originObject = args.shift();
        var object = Object.create(originObject);

        args.forEach(function(o) {
            if (isEs6) {
                object = Object.create(o);
            }
            if (isJq) {
                object = _.extend(object, o);
            }
        })

        return object;
    }
})(this);

(function(root) {

    var Task = function(config) {
        this.seqid = 0;
        _.extend(this, config);
    }

    _.extend(Task.prototype, {
        fire: function() {
            this.handler.apply(this, this.param);
            // Fnatic.record.success('task执行成功');
        }
    })

    var Queue = function() {
        this.stack = [];
        this.interval = 200;
        this.cursor = null;
    }

    _.extend(Queue.prototype, {
        isTask: function(task) {
            return task instanceof Task;
        },
        add: function(task) {
            if (this.isTask(task)) {
                this.stack.unshift(task);
                this.clearTimeout(this.cursor);
                this.dispatch();
            } else {
                Fnatic.record.failure('不是Task对象的实例');
            }
        },
        clearTimeout: function(reference) {
            if (reference) {
                clearTimeout(reference);
            }
        },
        dispatch: function() {
            var self = this;
            this.cursor = setTimeout(function() {
                self.fire.call(self);
            }, this.interval);
        },
        fire: function() {
            if (this.stack.length) {
                var task = this.stack.pop();
                task.fire();
            }
        },
        remove: function() {

        },
        clear: function() {
            this.stack = [];
        }
    })

    root.Fnatic.Task = Task;
    root.Fnatic.Queue = Queue;

})(this);
(function(root) {
    function NumberSuffix(value) {
        var suffix = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
        var unit = [1, 1e3, 1e6, 1e9, 1e12, 1e15];
        var str = String(value);
        var level = Math.floor(str.length / 3);
        return (str / unit[level]).toFixed(2) + suffix[level];
    }
    root.Fnatic.NumberSuffix = NumberSuffix;
})(this);