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 Namespace = (function () {
        function Namespace() {
            this.Widgets = [];
        }
        Namespace.prototype.include = function (W) {
            if (W.prototype instanceof WidgetBase) {
                this.Widgets.push(W);
            }
            else {
                console.warn(W, "not inherit WidgetBase");
            }
        };
        Namespace.prototype.getWidgetByName = function (name) {
            for (var _i = 0, _a = this.Widgets; _i < _a.length; _i++) {
                var W = _a[_i];
                if (W.name === name || W.prototype.name === name || W.constructor.name == name) {
                    return W;
                }
            }
            return null;
        };
        Namespace.prototype.createWidgetByName = function (name, el) {
            var W = this.getWidgetByName(name);
            if (!W)
                return;
            return new W(el);
        };
        return Namespace;
    }());
    Leaf.Namespace = Namespace;
    var bubbleEventNames = ["click", "mouseup", "mousedown", "mousemove", "mouseleave", "mouseenter", "mouseover", "keydown", "keyup", "keypress"];
    var unbubbleEvents = [{
            name: "focus",
            bubble: false,
            target: ["input", "textarea"]
        }, {
            name: "blur",
            bubble: false,
            target: ["input", "textarea"]
        }, {
            name: "submit",
            bubble: false,
            target: ["form"]
        }, {
            name: "change",
            bubble: false,
            target: ["input", "textarea", "select"]
        }];
    var WidgetBase = (function () {
        function WidgetBase(template, name, _VMFields) {
            var _this = this;
            this.template = template;
            this._VMFields = _VMFields;
            this.namespace = WidgetBase.namespace;
            this.availableEvents = [];
            this.events = new Leaf.EventEmitter();
            this.UI = {};
            this.interestingUnbubblingEvents = unbubbleEvents;
            this.interestingBubblingEvents = bubbleEventNames.map(function (name) {
                return {
                    name: name, bubble: true,
                };
            });
            if (name) {
                this.name = this.name || (this.constructor.name) || "AnonymouseWidget";
            }
            if (!this._VMFields) {
                this._VMFields = [];
            }
            this.ViewModel = new Leaf.Model(this.name, this._VMFields);
            Object.defineProperty(this, "VM", {
                get: function () {
                    return _this.ViewModel.data;
                },
                set: function (values) {
                    return _this.ViewModel.sets(values);
                }
            });
            if (this.template) {
                this.initTemplate(this.template);
            }
        }
        WidgetBase.prototype.before = function (who) {
            var node;
            if (who instanceof Widget) {
                node = who.node;
            }
            else {
                node = who;
            }
            if (!node.parentElement)
                return false;
            node.parentElement.insertBefore(this.node, node);
            return true;
        };
        WidgetBase.prototype.after = function (who) {
            var node;
            if (who instanceof Widget) {
                node = who.node;
            }
            else {
                node = who;
            }
            if (!node.parentElement)
                return false;
            if (node.nextSibling) {
                node.parentElement.insertBefore(this.node, node.nextSibling);
            }
            else {
                node.parentElement.appendChild(this.node);
            }
            return true;
        };
        WidgetBase.prototype.prependTo = function (who) {
            var node;
            if (who instanceof Widget) {
                node = who.node;
            }
            else {
                node = who;
            }
            if (node.children.length === 0) {
                node.appendChild(this.node);
            }
            else {
                var first = node.children[0];
                node.insertBefore(this.node, first);
            }
            return true;
        };
        WidgetBase.prototype.appendTo = function (who) {
            var node;
            if (who instanceof Widget) {
                node = who.node;
            }
            else {
                node = who;
            }
            node.appendChild(this.node);
            return true;
        };
        WidgetBase.prototype.initTemplate = function (template) {
            if (!template) {
                template = document.createElement("div");
            }
            if (typeof template === "string") {
                var tempNode = document.createElement("div");
                tempNode.innerHTML = template.trim();
                this.node = tempNode.children[0];
                tempNode.removeChild(this.node);
            }
            else if (template instanceof Element) {
                this.node = template;
            }
            if (!this.node) {
                console.error("Invalid Widget.node constructed with template", template);
                throw new Error("BrokenWidgetTemplate");
            }
            if (this.node.nodeType === this.node.TEXT_NODE)
                return;
            this.initSubWidget(this.node);
            for (var i = 0; i < this.node.children.length; i++) {
                this.initCommonWidget(this.node.children[i]);
            }
            this.initUI();
            this.initBubblingDelegates();
            this.initUnbubblingDelegates();
        };
        WidgetBase.prototype.initSubWidget = function (_el) {
            var el = _el;
            if (el.selfWidget) {
                return;
            }
            var tagName = el.tagName.toLowerCase();
            var name = el.getAttribute("data-widget");
            if (tagName === "widget" && this[name] instanceof WidgetBase) {
                var widget = this[name];
                el.parentElement.insertBefore(widget.node, el);
                el.parentElement.removeChild(el);
                widget.parentWidget = this;
                this.syncElementToChildWidget(el, widget);
                return;
            }
            if (!el.children)
                return;
            for (var i = 0; i < el.children.length; i++) {
                this.initSubWidget(el.children[i]);
            }
        };
        WidgetBase.prototype.initCommonWidget = function (_el) {
            var el = _el;
            if (!el.children)
                return;
            // already initialized
            if (el.selfWidget)
                return;
            var tagName = el.tagName.toLowerCase();
            var name = Leaf.Util.slugToCamel(tagName);
            name = Leaf.Util.capitalize(name);
            var widget = this.namespace.createWidgetByName(name, el);
            if (widget) {
                el.parentElement.insertBefore(widget.node, el);
                el.parentElement.removeChild(el);
                widget.parentWidget = this;
                this.syncElementToChildWidget(el, widget);
                return;
            }
            if (!el.children)
                return;
            for (var i = 0; i < el.children.length; i++) {
                this.initCommonWidget(el.children[i]);
            }
        };
        WidgetBase.prototype.syncElementToChildWidget = function (el, widget) {
            var id = el.getAttribute("data-id");
            if (id) {
                widget.node.setAttribute("data-id", id);
            }
            if (el.getAttribute("class")) {
                this.mixChildWidgetClassRole(el, widget);
            }
        };
        WidgetBase.prototype.mixChildWidgetClassRole = function (el, child) {
        };
        WidgetBase.prototype.initUI = function () {
            var _this = this;
            var node = this.node;
            var elcol = node.querySelectorAll("[data-id]");
            var elems = [].slice.call(elcol);
            elems.forEach(function (sn) {
                var subNode = sn;
                var id = subNode.getAttribute("data-id");
                // skip subnode that are controlled by others 
                if (subNode.controllingWidget) {
                    return;
                }
                if (subNode)
                    if (id) {
                        _this.bindUI(subNode, id);
                    }
            });
            this.UI["self"] = node;
            node.selfWidget = this;
        };
        WidgetBase.prototype.bindUI = function (node, id) {
            this.UI[id] = node;
            node.widgetId = id;
            node.controllingWidget = this;
        };
        WidgetBase.prototype.delegateTo = function (name, event) {
            var fnName = "on" + Leaf.Util.capitalize(event.type) + Leaf.Util.capitalize(name);
            var fn = this[fnName];
            if (!fn) {
                return true;
            }
            return fn.call(this, event);
        };
        WidgetBase.prototype.initBubblingDelegates = function () {
            var _this = this;
            this.interestingBubblingEvents.forEach(function (eventInfo) {
                _this.node.addEventListener(eventInfo.name, function (e) {
                    e.capture = function () {
                        e.stopImmediatePropagation();
                        e.preventDefault();
                    };
                    var src = (e.target || e.srcElement);
                    if (!src) {
                        return;
                    }
                    var result;
                    while (src && !e.defaultPrevented) {
                        e.bubblingTarget = src;
                        if (src.selfWidget === _this) {
                            result = _this.delegateTo("", e);
                            if (result === false) {
                                e.capture();
                            }
                            return result;
                        }
                        if (src.controllingWidget !== _this) {
                            src = src.parentElement;
                            continue;
                        }
                        result = _this.delegateTo(src.widgetId, e);
                        if (result === false || e.defaultPrevented) {
                            e.capture();
                            return result;
                        }
                        src = src.parentElement;
                        continue;
                    }
                });
            });
        };
        WidgetBase.prototype.initUnbubblingDelegates = function () {
            var _this = this;
            var _loop_1 = function (name_1) {
                var el = this_1.UI[name_1];
                var tagName = el.tagName.toLowerCase();
                var subWidget = el.selfWidget || null;
                var events = this_1.interestingUnbubblingEvents.slice(0);
                if (subWidget && subWidget.availableEvents) {
                    events = events.concat(subWidget.availableEvents);
                }
                events.forEach(function (info) {
                    if (!info.target || !info.target.indexOf || info.target.indexOf(tagName) >= 0) {
                        if (name_1 === "self")
                            name_1 = "";
                        el.addEventListener(info.name, function (e) {
                            e.capture = function () {
                                e.stopImmediatePropagation();
                                e.preventDefault();
                            };
                            _this.delegateTo(name_1, e);
                        });
                    }
                });
            };
            var this_1 = this;
            for (var name_1 in this.UI) {
                _loop_1(name_1);
            }
        };
        return WidgetBase;
    }());
    WidgetBase.namespace = new Namespace();
    Leaf.WidgetBase = WidgetBase;
    var Widget = (function (_super) {
        __extends(Widget, _super);
        function Widget() {
            return _super.apply(this, arguments) || this;
        }
        Widget.makeList = function (el) {
            return new Leaf.List(el);
        };
        Widget.prototype.initTemplate = function (template) {
            _super.prototype.initTemplate.call(this, template);
            this.initDataRole(this.node);
        };
        Widget.prototype.initDataRole = function (el) {
            // 1. traverse DOM
            // 2. for text response {{text}}
            // 3. for classes we respect {{className}}
            if (el.nodeType !== el.ELEMENT_NODE) {
                return;
            }
            this.initElementRole(el);
            for (var index = 0; index < el.childNodes.length; index += 1) {
                var node = el.childNodes[index];
                if (node.nodeType === node.TEXT_NODE) {
                    this.initTextRole(node);
                }
                else if (node.nodeType === node.ELEMENT_NODE) {
                    this.initDataRole(node);
                }
            }
        };
        Widget.prototype.initElementRole = function (el) {
            var _this = this;
            var changed = false;
            var _loop_2 = function (index) {
                var attr = el.attributes[index];
                var templates = this_2.createSyncTemplates(attr.value);
                if (!templates)
                    return "continue";
                changed = true;
                templates.forEach(function (t) {
                    if (t.type !== "value")
                        return;
                    _this.ViewModel.events.listenBy(_this, "change/" + t.content, function (value) {
                        _this.syncElementProperty(el, {
                            name: attr.name,
                            templates: templates
                        });
                    });
                });
                this_2.syncElementProperty(el, { name: attr.name, templates: templates });
            };
            var this_2 = this;
            for (var index = 0; index < el.attributes.length; index++) {
                _loop_2(index);
            }
            return changed;
        };
        Widget.prototype.initTextRole = function (text) {
            var _this = this;
            var templates = this.createSyncTemplates(text.textContent);
            if (!templates)
                return false;
            templates.forEach(function (t) {
                if (t.type !== "value")
                    return;
                _this.ViewModel.events.listenBy(_this, "change/" + t.content, function (value) {
                    _this.syncTextProperty(text, {
                        templates: templates
                    });
                });
            });
            this.syncTextProperty(text, { templates: templates });
            return true;
        };
        Widget.prototype.createSyncTemplates = function (templateString) {
            var templates = [];
            var reg = /\{\{\s*!?([a-z]+(:[a-z]+)?)\s*\}\}/ig;
            var match;
            var lastIndex = 0;
            var hasValue = false;
            while (match = reg.exec(templateString)) {
                if (match.index > lastIndex) {
                    templates.push({
                        type: "raw",
                        content: templateString.slice(lastIndex, match.index)
                    });
                }
                var content = match[0].slice(2).slice(0, -2);
                if (!content)
                    continue;
                var _a = content.split(":").map(function (item) { return item.trim(); }), k = _a[0], v = _a[1];
                var reverse = false;
                if (k[0] == "!") {
                    k = k.slice(1);
                    reverse = true;
                }
                templates.push({
                    type: "value",
                    content: k,
                    value: v,
                    reverse: reverse
                });
                hasValue = true;
                lastIndex = match.index + match[0].length;
            }
            if (!hasValue)
                return null;
            if (lastIndex < templateString.length) {
                templates.push({
                    type: "raw",
                    content: templateString.slice(lastIndex)
                });
            }
            return templates;
        };
        Widget.prototype.syncElementProperty = function (el, rule) {
            var _this = this;
            var value = el.getAttribute(rule.name);
            var strs = rule.templates.map(function (t) {
                return _this.explain(t);
            });
            var result = "";
            if (rule.name === "class") {
                strs = strs.filter(function (item) { return !!item; });
                strs.forEach(function (str, index) {
                    result += str;
                    if (str.slice(-1) !== "-") {
                        if (index != strs.length - 1)
                            result += " ";
                    }
                });
            }
            else {
                result = strs.join("");
            }
            if (result != value) {
                el.setAttribute(rule.name, result);
            }
        };
        Widget.prototype.explain = function (t) {
            if (t.type === "raw") {
                return t.content;
            }
            else {
                var mayReverse = function (v) {
                    if (t.reverse)
                        return !v;
                    return v;
                };
                var vmValue = this.ViewModel.get(t.content);
                var value = t.value || null;
                // for {{class}} value as true, use name as className
                if (mayReverse(vmValue) === true) {
                    if (value) {
                        return value;
                    }
                    value = Leaf.Util.camelToSlug(t.content);
                    return value;
                }
                else if (mayReverse(vmValue) === false) {
                    return "";
                }
                if (mayReverse(vmValue) && value) {
                    return value;
                }
                return vmValue || "";
            }
        };
        Widget.prototype.syncTextProperty = function (text, rule) {
            var _this = this;
            // Should add pollyfill for IE8/7
            // https://developer.mozilla.org/en/docs/Web/API/Node/textContent
            var value = text.textContent;
            var strs = rule.templates.map(function (t) {
                return _this.explain(t);
            });
            var result = strs.join("");
            if (result !== value) {
                text.textContent = result;
            }
        };
        Widget.prototype.emitDOMEvent = function (name, bubble, props) {
            if (props === void 0) { props = {}; }
            var valid = this.availableEvents.some(function (info) {
                return info.name === name;
            });
            if (!valid) {
                throw new Error("Should add events " + name + "to available events at construtor");
            }
            var e = new CustomEvent(name, {
                bubbles: bubble,
                cancelable: true
            });
            for (var prop in props) {
                e[prop] = props[prop];
            }
            this.node.dispatchEvent(e);
        };
        Widget.prototype.emitWidgetEvent = function (name, value) {
            if (value === void 0) { value = {}; }
            this.events.emit(name, value);
            this.emitDOMEvent(name, false, value);
        };
        Widget.prototype.include = function (W) {
            this.namespace.include(W);
        };
        Widget.prototype.expose = function (name, remoteName) {
            var _this = this;
            if (!remoteName) {
                remoteName = name;
            }
            if (typeof this[name] === "function") {
                Object.defineProperty(this.node, remoteName, {
                    get: function () {
                        return _this[name].bind(_this);
                    }
                });
                return;
            }
            var cap = Leaf.Util.capitalize(name);
            var getterName = "onGet" + cap;
            var setterName = "onSet" + cap;
            Object.defineProperty(this.node, remoteName, {
                get: function () {
                    if (_this[getterName])
                        return _this[getterName]();
                    else
                        return _this[name];
                },
                set: function (value) {
                    if (_this[setterName])
                        return _this[setterName](value);
                    else
                        return _this[name];
                }
            });
        };
        Widget.prototype.makeList = function (who) {
            var _this = this;
            var el;
            var name;
            if (typeof who === "string") {
                el = this.UI[who];
                name = who;
            }
            else {
                for (var prop in this.UI) {
                    if (this.UI[prop] === who) {
                        el = who;
                        name = prop;
                        break;
                    }
                }
                if (!name) {
                    return;
                }
            }
            var list = Leaf.Widget.makeList(el);
            list.events.listenBy(this, "child/add", function (item) {
                var fn = _this["onChildAdd" + Leaf.Util.capitalize(name)];
                if (fn) {
                    fn.call(_this, item);
                }
            });
            list.events.listenBy(this, "child/remove", function (item) {
                var fn = _this["onChildRemove" + Leaf.Util.capitalize(name)];
                if (fn) {
                    fn.call(_this, item);
                }
            });
            if (!this[name]) {
                this[name] = list;
            }
            return list;
        };
        return Widget;
    }(WidgetBase));
    Leaf.Widget = Widget;
})(Leaf || (Leaf = {}));
//# sourceMappingURL=widget.js.map