/// <reference path="leaf.ts" />
var Leaf;
(function (Leaf) {
    Leaf.RPCErrors = Leaf.ErrorDoc.create()
        .define("APINotExists")
        .define("Timeout")
        .define("Conflict")
        .define("BrokenConnection")
        .generate();
    var RPCCenter = (function () {
        function RPCCenter(option) {
            if (option === void 0) { option = {}; }
            this.option = option;
            this.declares = {};
            this.requestOffset = 10000;
            this.methods = {};
            this.remoteEvents = new Leaf.EventEmitter();
            this.responseHandlers = [];
            if (!(typeof this.option.defaultTimeout === "number")) {
                this.option.defaultTimeout = 0;
            }
        }
        RPCCenter.prototype.allocateId = function () { return this.requestOffset++; };
        RPCCenter.prototype.setConnection = function (con) {
            var _this = this;
            this.connection = con;
            this.connection.events.listenBy(this, "message", function (message) {
                _this.handleMessage(message);
            });
            this.connection.events.listenBy(this, "close", function () {
                _this.unsetConnection();
            });
        };
        RPCCenter.prototype.unsetConnection = function () {
            this.connection.events.stopListenBy(this);
            this.connection = null;
        };
        RPCCenter.prototype.registerRPCMethod = function (name, handler) {
            if (this.methods[name]) {
                throw new Leaf.RPCErrors.Conflict("RPC Method \"" + name + "\" already exists");
            }
            this.methods[name] = handler;
        };
        RPCCenter.prototype.createRPCDeclare = function (option) {
            var api = new RPCDeclare(this, option);
            this.declares[option.name] = api;
            return api;
        };
        RPCCenter.prototype.createRPCInvokeInterface = function (option) {
            var api = this.createRPCDeclare(option);
            this[option.name] = api;
            return api.getFunction();
        };
        RPCCenter.prototype.emitRemoteEvent = function (name, data) {
            var blob = {
                name: name,
                data: data,
                type: RPCProtocolBlobType.EVENT
            };
            this.send(this.normalize(blob));
        };
        RPCCenter.prototype.invoke = function (name, data, callback, option) {
            var _this = this;
            if (option === void 0) { option = {}; }
            if (!callback) {
                callback = function () { };
            }
            if (!this.connection) {
                callback(new Leaf.RPCErrors.BrokenConnection("Connection not exists"));
                return;
            }
            var handler = {
                id: this.allocateId(),
                callback: callback,
                finished: false,
                timer: null
            };
            var timeout = option.timeout || this.option.defaultTimeout;
            if (timeout > 0) {
                handler.timer = setTimeout(function () {
                    if (!handler.finished) {
                        _this.finishHandler(handler.id, new Leaf.RPCErrors.Timeout("RPC timeout after " + _this.option.defaultTimeout));
                    }
                }, timeout);
            }
            this.responseHandlers.push(handler);
            var blob = {
                id: handler.id,
                method: name,
                data: data,
                type: RPCProtocolBlobType.INVOKE
            };
            this.send(this.normalize(blob));
        };
        RPCCenter.prototype.send = function (data) {
            if (typeof data !== "string") {
                data = JSON.stringify(data);
            }
            this.connection.send(data);
        };
        RPCCenter.prototype.handleMessage = function (rawString) {
            var data = null;
            try {
                var raw = JSON.parse(rawString);
                data = this.denormalize(raw);
            }
            catch (e) {
                return;
            }
            if (data.type === RPCProtocolBlobType.INVOKE) {
                this.handleInvoke(data);
            }
            else if (data.type === RPCProtocolBlobType.RESPONSE) {
                this.handleResponse(data);
            }
            else if (data.type === RPCProtocolBlobType.EVENT) {
                this.handleEvent(data);
            }
        };
        RPCCenter.prototype.handleInvoke = function (req) {
            var _this = this;
            var response = {
                id: req.id,
                type: RPCProtocolBlobType.RESPONSE
            };
            if (!this.methods[req.method]) {
                response.error = new Leaf.RPCErrors.APINotExists("Requesting API " + req.method + " not exists");
                this.send(this.normalize(response));
                return;
            }
            var call = this.methods[req.method];
            call(req.data, function (err, data) {
                response.error = err;
                response.data = data;
                _this.send(_this.normalize(response));
            });
        };
        RPCCenter.prototype.handleResponse = function (res) {
            this.finishHandler(res.id, res.error, res.data);
        };
        RPCCenter.prototype.handleEvent = function (event) {
            this.remoteEvents.emit(event.name, event.data);
        };
        RPCCenter.prototype.transformObject = function (data, handler) {
            var _this = this;
            if (typeof data !== "object") {
                return data;
            }
            if (data instanceof Array) {
                return data.map(function (item) { return _this.transformObject(item, handler); });
            }
            if (!data)
                return data;
            var norm = data;
            if (handler) {
                norm = handler(norm);
            }
            // changed by custom norm
            if (norm && norm !== data) {
                return norm;
            }
            for (var prop in data) {
                data[prop] = this.transformObject(data[prop], handler);
            }
            return data;
        };
        RPCCenter.prototype.normalize = function (data) {
            return this.transformObject(data, this.customNormalize);
        };
        RPCCenter.prototype.denormalize = function (data) {
            return this.transformObject(data, this.customDenormalize);
        };
        RPCCenter.prototype.finishHandler = function (id, err, value) {
            this.responseHandlers = this.responseHandlers.filter(function (handler) {
                if (handler.id === id) {
                    handler.finished = true;
                    if (handler.timer) {
                        clearTimeout(handler.timer);
                        handler.timer = null;
                    }
                    setTimeout(function () {
                        handler.callback(err, value);
                    }, 0);
                    return false;
                }
                return true;
            });
        };
        return RPCCenter;
    }());
    Leaf.RPCCenter = RPCCenter;
    var RPCDeclare = (function () {
        function RPCDeclare(center, option) {
            this.center = center;
            this.option = option;
        }
        RPCDeclare.prototype.getFunction = function () {
            var fn = this.invoke.bind(this);
            fn.RPC = this;
            return fn;
        };
        RPCDeclare.prototype.invoke = function (data, callback) {
            if (data === void 0) { data = {}; }
            this.center.invoke(this.option.name, data, callback, {
                timeout: this.option.timeout || 0
            });
        };
        return RPCDeclare;
    }());
    Leaf.RPCDeclare = RPCDeclare;
    var RPCConnectionBase = (function () {
        function RPCConnectionBase() {
            this.events = new Leaf.EventEmitter();
        }
        return RPCConnectionBase;
    }());
    Leaf.RPCConnectionBase = RPCConnectionBase;
    var RPCProtocolBlobType;
    (function (RPCProtocolBlobType) {
        RPCProtocolBlobType[RPCProtocolBlobType["INVOKE"] = 1] = "INVOKE";
        RPCProtocolBlobType[RPCProtocolBlobType["RESPONSE"] = 2] = "RESPONSE";
        RPCProtocolBlobType[RPCProtocolBlobType["EVENT"] = 3] = "EVENT";
    })(RPCProtocolBlobType = Leaf.RPCProtocolBlobType || (Leaf.RPCProtocolBlobType = {}));
})(Leaf || (Leaf = {}));
//# sourceMappingURL=rpcCenter.js.map