var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
/// <reference path="leaf.ts" />
var Leaf;
(function (Leaf) {
    var States = (function (_super) {
        __extends(States, _super);
        function States() {
            var _this = _super.call(this) || this;
            _this.state = "void";
            _this.sole = 1;
            _this.soleEmitted = 1;
            _this.lastException = null;
            _this.rescues = [];
            _this.data = {};
            _this.feeds = {};
            _this.isDebugging = false;
            _this.clearHandler = null;
            _this.panicError = null;
            _this.panicState = null;
            _this.reset();
            return _this;
        }
        States.prototype.reset = function () {
            this.data = {};
            this.feeds = {};
            this.respawn();
            this.emit("reset");
        };
        States.prototype.setState = function (state) {
            var args = [];
            for (var _i = 1; _i < arguments.length; _i++) {
                args[_i - 1] = arguments[_i];
            }
            this._setState.apply(this, [state].concat(args));
        };
        States.prototype._setState = function (state) {
            var args = [];
            for (var _i = 1; _i < arguments.length; _i++) {
                args[_i - 1] = arguments[_i];
            }
            if (this.state === "panic" && state !== "void") {
                return;
            }
            // set state syncly from previous state
            // so we emit the previous state here
            if (this.soleEmitted < this.sole) {
                this.emit("state", this.state);
                this.emit("state/" + this.state);
                this.soleEmitted = this.sole;
            }
            // clear all consumers
            if (this.feeds) {
                for (var prop in this.feeds) {
                    var feed = this.feeds[prop];
                    feed.feedListener = null;
                }
            }
            // new sole
            this.sole += 1;
            this.previousState = this.state;
            this.state = state;
            if (this.isDebugging) {
                this.debugStateHandler();
            }
            var stateHandler = "at" + state[0].toUpperCase() + state.substring(1);
            if (this[stateHandler]) {
                var sole_1 = this.sole;
                this[stateHandler].apply(this, [function () {
                        return sole_1 !== this.sole;
                    }.bind(this)].concat(args));
                // didn't set state during the process, we can safely emit state here
                if (sole_1 === this.sole) {
                    this.soleEmitted = sole_1;
                    this.emit("state", state);
                    this.emit("state/" + state);
                }
            }
            else if (state !== "void") {
                this.error(new Error("InvalidState " + state));
            }
        };
        States.prototype.error = function (err) {
            this.panicError = err;
            this.panicState = this.state;
            if (this.panicError) {
                this.setState("panic");
            }
        };
        States.prototype.atPanic = function () {
            console.error(this.panicState, this.panicError);
            this.emit("panic", this.panicError, this.panicState);
        };
        States.prototype.recover = function (recoverState) {
            var error = this.panicError;
            var state = this.panicState;
            this.respawn();
            if (!recoverState) {
                recoverState = ("void");
            }
            this.setState(recoverState);
            return { error: error, state: state };
        };
        States.prototype.respawn = function () {
            this.sole = this.sole || 1;
            this.sole += 1;
            this.panicError = null;
            this.panicState = null;
            this.setState("void");
            this.clear();
        };
        States.prototype.clear = function (handler) {
            if (handler) {
                if (this.clearHandler) {
                    throw new Error("Already clearing with a handler");
                }
                this.clearHandler = handler;
            }
            else {
                handler = this.clearHandler;
                this.clearHandler = null;
                if (handler) {
                    handler();
                }
            }
        };
        States.prototype.ensureFeed = function (name) {
            if (this.feeds[name])
                return this.feeds[name];
            var feed = this.feeds[name] = {
                feedListener: null,
                signals: [],
                name: name
            };
            return feed;
        };
        States.prototype.feed = function (name, signal) {
            if (!this.feeds[name]) {
                this.feeds[name] = {
                    feedListener: null,
                    signals: [],
                    name: name
                };
            }
            var feed = this.feeds[name];
            feed.signals.push(signal);
            var listener = feed.feedListener;
            if (listener) {
                feed.feedListener = null;
                listener();
            }
        };
        States.prototype.consumeAll = function (name) {
            var feed = this.feeds[name];
            if (feed) {
                var length_1 = feed.signals.length;
                feed.signals.length = 0;
                return length_1;
            }
            return 0;
        };
        States.prototype.hasFeed = function (name) {
            return this.feeds[name] && this.feeds[name].signals.length > 0;
        };
        States.prototype.consume = function (name) {
            if (!this.hasFeed(name))
                return null;
            var feed = this.feeds[name];
            if (feed) {
                return feed.signals.shift() || true;
            }
            return null;
        };
        States.prototype.consumeWhenAvailableMergeToLast = function (name, callback) {
            var _this = this;
            this.consumeWhenAvailable(name, function (detail) {
                var last = null;
                while (true) {
                    last = _this.consume(name);
                    if (!last)
                        break;
                }
                if (last) {
                    callback(last);
                }
                else {
                    callback(detail);
                }
            });
        };
        States.prototype.consumeWhenAvailable = function (name, callback) {
            var _this = this;
            var feed = this.feeds[name];
            if (!feed) {
                feed = this.ensureFeed(name);
            }
            if (feed.signals.length > 0) {
                callback(this.consume(name));
            }
            else {
                feed.feedListener = function () { return callback(_this.consume(name)); };
                this.emit("starve", name);
                this.emit("starve/" + name);
            }
        };
        States.prototype.debug = function (option) {
            this.isDebugging = true;
        };
        States.prototype.debugStateHandler = function (option, message) {
            option = option || {};
            var name = option.name || this.name || "Anonymous";
            var log = console.debug || console.log;
            log.call(console, name + ": state - " + this.state);
        };
        return States;
    }(Leaf.EventEmitter));
    Leaf.States = States;
})(Leaf || (Leaf = {}));
//# sourceMappingURL=states.js.map