//@tag dom,core
//@define Ext.Element-all
//@define Ext.Element

/**
 * Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.
 *
 * ## Usage
 *
 *     // by id
 *     var el = Ext.get("my-div");
 *
 *     // by DOM element reference
 *     var el = Ext.get(myDivElement);
 *
 * ## Composite (Collections of) Elements
 *
 * For working with collections of Elements, see {@link Ext.CompositeElement}.
 *
 * @mixins Ext.mixin.Observable
 */
Ext.define('Ext.dom.Element', {
    alternateClassName: 'Ext.Element',

    mixins: [
        'Ext.mixin.Identifiable'
    ],

    requires: [
        'Ext.dom.Query',
        'Ext.dom.Helper'
    ],

    observableType: 'element',

    xtype: 'element',

    statics: {
        CREATE_ATTRIBUTES: {
            style: 'style',
            className: 'className',
            cls: 'cls',
            classList: 'classList',
            text: 'text',
            hidden: 'hidden',
            html: 'html',
            children: 'children'
        },

        create: function(attributes, domNode) {
            var ATTRIBUTES = this.CREATE_ATTRIBUTES,
                element, elementStyle, tag, value, name, i, ln;

            if (!attributes) {
                attributes = {};
            }

            if (attributes.isElement) {
                return attributes.dom;
            }
            else if ('nodeType' in attributes) {
                return attributes;
            }

            if (typeof attributes == 'string') {
                return document.createTextNode(attributes);
            }

            tag = attributes.tag;

            if (!tag) {
                tag = 'div';
            }
            if (attributes.namespace) {
                element = document.createElementNS(attributes.namespace, tag);
            } else {
                element = document.createElement(tag);
            }
            elementStyle = element.style;

            for (name in attributes) {
                if (name != 'tag') {
                    value = attributes[name];

                    switch (name) {
                        case ATTRIBUTES.style:
                                if (typeof value == 'string') {
                                    element.setAttribute(name, value);
                                }
                                else {
                                    for (i in value) {
                                        if (value.hasOwnProperty(i)) {
                                            elementStyle[i] = value[i];
                                        }
                                    }
                                }
                            break;

                        case ATTRIBUTES.className:
                        case ATTRIBUTES.cls:
                            element.className = value;
                            break;

                        case ATTRIBUTES.classList:
                            element.className = value.join(' ');
                            break;

                        case ATTRIBUTES.text:
                            element.textContent = value;
                            break;

                        case ATTRIBUTES.hidden:
                            if (value) {
                                element.style.display = 'none';
                            }
                            break;

                        case ATTRIBUTES.html:
                            element.innerHTML = value;
                            break;

                        case ATTRIBUTES.children:
                            for (i = 0,ln = value.length; i < ln; i++) {
                                element.appendChild(this.create(value[i], true));
                            }
                            break;

                        default:
                            element.setAttribute(name, value);
                    }
                }
            }

            if (domNode) {
                return element;
            }
            else {
                return this.get(element);
            }
        },

        documentElement: null,

        cache: {},

        /**
         * Retrieves Ext.dom.Element objects. {@link Ext#get} is alias for {@link Ext.dom.Element#get}.
         *
         * Uses simple caching to consistently return the same object. Automatically fixes if an object was recreated with
         * the same id via AJAX or DOM.
         *
         * @param {String/HTMLElement/Ext.Element} element The `id` of the node, a DOM Node or an existing Element.
         * @return {Ext.dom.Element} The Element object (or `null` if no matching element was found).
         * @static
         * @inheritable
         */
        get: function(element) {
            var cache = this.cache,
                instance, dom, id;

            if (!element) {
                return null;
            }

            // DOM Id
            if (typeof element == 'string') {
                dom = document.getElementById(element);

                if (cache.hasOwnProperty(element)) {
                    instance = cache[element];
                }

                // Is this in the DOM proper
                if (dom) {
                    // Update our Ext Element dom reference with the true DOM (it may have changed)
                    if (instance) {
                        instance.dom = dom;
                    }
                    else {
                        // Create a new instance of Ext Element
                        instance = cache[element] = new this(dom);
                    }
                }
                // Not in the DOM, but if its in the cache, we can still use that as a DOM fragment reference, otherwise null
                else if (!instance) {
                    instance = null;
                }

                return instance;
            }

             // DOM element
            if ('tagName' in element) {
                id = element.id;

                if (cache.hasOwnProperty(id)) {
                    instance = cache[id];
                    instance.dom = element;
                    return instance;
                }
                else {
                    instance = new this(element);
                    cache[instance.getId()] = instance;
                }

                return instance;
            }

            // Ext Element
            if (element.isElement) {
                return element;
            }

            // Ext Composite Element
            if (element.isComposite) {
                return element;
            }

            // Array passed
            if (Ext.isArray(element)) {
                return this.select(element);
            }

            // DOM Document
            if (element === document) {
                // create a bogus element object representing the document object
                if (!this.documentElement) {
                    this.documentElement = new this(document.documentElement);
                    this.documentElement.setId('ext-application');
                }

                return this.documentElement;
            }

            return null;
        },

        data: function(element, key, value) {
            var cache = Ext.cache,
                id, data;

            element = this.get(element);

            if (!element) {
                return null;
            }

            id = element.id;

            data = cache[id].data;

            if (!data) {
                cache[id].data = data = {};
            }

            if (arguments.length == 2) {
                return data[key];
            }
            else {
                return (data[key] = value);
            }
        },

        /**
         * Serializes a DOM form into a url encoded string
         * @param {Object} form The form
         * @return {String} The url encoded form
         */
        serializeForm : function(form) {
            var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements,
                hasSubmit = false,
                encoder = encodeURIComponent,
                data = '',
                eLen = fElements.length,
                element, name, type, options, hasValue, e,
                o, oLen, opt;

            for (e = 0; e < eLen; e++) {
                element = fElements[e];
                name = element.name;
                type = element.type;
                options = element.options;

                if (!element.disabled && name) {
                    if (/select-(one|multiple)/i.test(type)) {
                        oLen = options.length;
                        for (o = 0; o < oLen; o++) {
                            opt = options[o];
                            if (opt.selected) {
                                hasValue = opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttributeNode('value').specified;
                                data += Ext.String.format('{0}={1}&', encoder(name), encoder(hasValue ? opt.value : opt.text));
                            }
                        }
                    } else if (!(/file|undefined|reset|button/i.test(type))) {
                        if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)) {
                            data += encoder(name) + '=' + encoder(element.value) + '&';
                            hasSubmit = /submit/i.test(type);
                        }
                    }
                }
            }
            return data.substr(0, data.length - 1);
        },

        /**
         * Serializes a DOM element and its children recursively into a string.
         * @param {Object} node DOM element to serialize.
         * @returns {String}
         */
        serializeNode: function (node) {
            var result = '',
                i, n, attr, child;
            if (node.nodeType === document.TEXT_NODE) {
                return node.nodeValue;
            }
            result += '<' + node.nodeName;
            if (node.attributes.length) {
                for (i = 0, n = node.attributes.length; i < n; i++) {
                    attr = node.attributes[i];
                    result += ' ' + attr.name + '="' + attr.value + '"';
                }
            }
            result += '>';
            if (node.childNodes && node.childNodes.length) {
                for (i = 0, n = node.childNodes.length; i < n; i++) {
                    child = node.childNodes[i];
                    result += this.serializeNode(child);
                }
            }
            result += '</' + node.nodeName + '>';
            return result;
        }
    },

    isElement: true,

    /**
     * @event painted
     * Fires whenever this Element actually becomes visible (painted) on the screen. This is useful when you need to
     * perform 'read' operations on the DOM element, i.e: calculating natural sizes and positioning.
     *
     * __Note:__ This event is not available to be used with event delegation. Instead `painted` only fires if you explicitly
     * add at least one listener to it, for performance reasons.
     *
     * @param {Ext.Element} this The component instance.
     */

    /**
     * @event resize
     * Important note: For the best performance on mobile devices, use this only when you absolutely need to monitor
     * a Element's size.
     *
     * __Note:__ This event is not available to be used with event delegation. Instead `resize` only fires if you explicitly
     * add at least one listener to it, for performance reasons.
     *
     * @param {Ext.Element} this The component instance.
     */

    constructor: function(dom) {
        if (typeof dom == 'string') {
            dom = document.getElementById(dom);
        }

        if (!dom) {
            throw new Error("Invalid domNode reference or an id of an existing domNode: " + dom);
        }

        /**
         * The DOM element
         * @property dom
         * @type HTMLElement
         */
        this.dom = dom;

        this.getUniqueId();
    },

    attach: function (dom) {
        this.dom = dom;
        this.id = dom.id;
        return this;
    },

    getUniqueId: function() {
        var id = this.id,
            dom;

        if (!id) {
            dom = this.dom;

            if (dom.id.length > 0) {
                this.id = id = dom.id;
            }
            else {
                dom.id = id = this.mixins.identifiable.getUniqueId.call(this);
            }

            Ext.Element.cache[id] = this;
        }

        return id;
    },

    setId: function(id) {
        var currentId = this.id,
            cache = Ext.Element.cache;

        if (currentId) {
            delete cache[currentId];
        }

        this.dom.id = id;

        /**
         * The DOM element ID
         * @property id
         * @type String
         */
        this.id = id;

        cache[id] = this;

        return this;
    },

    /**
     * Sets the `innerHTML` of this element.
     * @param {String} html The new HTML.
     */
    setHtml: function(html) {
        this.dom.innerHTML = html;
    },

    /**
     * Returns the `innerHTML` of an element.
     * @return {String}
     */
    getHtml: function() {
        return this.dom.innerHTML;
    },

    setText: function(text) {
        this.dom.textContent = text;
    },

    redraw: function() {
        var dom = this.dom,
            domStyle = dom.style;

        domStyle.display = 'none';
        dom.offsetHeight;
        domStyle.display = '';
    },

    isPainted: (function() {
        return !Ext.browser.is.IE ? function() {
            var dom = this.dom;
            return Boolean(dom && dom.offsetParent);
        } : function() {
            var dom = this.dom;
            return Boolean(dom && (dom.offsetHeight !== 0 && dom.offsetWidth !== 0));
        }
    })(),

    /**
     * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function).
     * @param {Object} attributes The object with the attributes.
     * @param {Boolean} [useSet=true] `false` to override the default `setAttribute` to use expandos.
     * @return {Ext.dom.Element} this
     */
    set: function(attributes, useSet) {
        var dom = this.dom,
            attribute, value;

        for (attribute in attributes) {
            if (attributes.hasOwnProperty(attribute)) {
                value = attributes[attribute];

                if (attribute == 'style') {
                    this.applyStyles(value);
                }
                else if (attribute == 'cls') {
                    dom.className = value;
                }
                else if (useSet !== false) {
                    if (value === undefined) {
                        dom.removeAttribute(attribute);
                    } else {
                        dom.setAttribute(attribute, value);
                    }
                }
                else {
                    dom[attribute] = value;
                }
            }
        }

        return this;
    },

    /**
     * Returns `true` if this element matches the passed simple selector (e.g. 'div.some-class' or 'span:first-child').
     * @param {String} selector The simple selector to test.
     * @return {Boolean} `true` if this element matches the selector, else `false`.
     */
    is: function(selector) {
        return Ext.DomQuery.is(this.dom, selector);
    },

    /**
     * Returns the value of the `value` attribute.
     * @param {Boolean} asNumber `true` to parse the value as a number.
     * @return {String/Number}
     */
    getValue: function(asNumber) {
        var value = this.dom.value;

        return asNumber ? parseInt(value, 10) : value;
    },

    /**
     * Returns the value of an attribute from the element's underlying DOM node.
     * @param {String} name The attribute name.
     * @param {String} [namespace] The namespace in which to look for the attribute.
     * @return {String} The attribute value.
     */
    getAttribute: function(name, namespace) {
        var dom = this.dom;

        return dom.getAttributeNS(namespace, name) || dom.getAttribute(namespace + ":" + name)
               || dom.getAttribute(name) || dom[name];
    },

    setSizeState: function(state) {
        var classes = ['x-sized', 'x-unsized', 'x-stretched'],
            states = [true, false, null],
            index = states.indexOf(state),
            addedClass;

        if (index !== -1) {
            addedClass = classes[index];
            classes.splice(index, 1);
            this.addCls(addedClass);
        }

        this.removeCls(classes);

        return this;
    },

    /**
     * Removes this element's DOM reference. Note that event and cache removal is handled at {@link Ext#removeNode}
     */
    destroy: function() {
        this.isDestroyed = true;

        var cache = Ext.Element.cache,
            dom = this.dom;

        if (dom && dom.parentNode && dom.tagName != 'BODY') {
            dom.parentNode.removeChild(dom);
        }

        delete cache[this.id];
        delete this.dom;
    }

}, function(Element) {
    Ext.elements = Ext.cache = Element.cache;

    this.addStatics({
        Fly: new Ext.Class({
            extend: Element,

            constructor: function(dom) {
                this.dom = dom;
            }
        }),

        _flyweights: {},

        /**
         * Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference
         * to this element - the dom node can be overwritten by other code. {@link Ext#fly} is alias for
         * {@link Ext.dom.Element#fly}.
         *
         * Use this to make one-time references to DOM elements which are not going to be accessed again either by
         * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get Ext.get}
         * will be more appropriate to take advantage of the caching provided by the {@link Ext.dom.Element}
         * class.
         *
         * @param {String/HTMLElement} element The DOM node or `id`.
         * @param {String} [named] Allows for creation of named reusable flyweights to prevent conflicts (e.g.
         * internally Ext uses "_global").
         * @return {Ext.dom.Element} The shared Element object (or `null` if no matching element was found).
         * @static
         */
        fly: function(element, named) {
            var fly = null,
                flyweights = Element._flyweights,
                cachedElement;

            named = named || '_global';

            element = Ext.getDom(element);

            if (element) {
                fly = flyweights[named] || (flyweights[named] = new Element.Fly());
                fly.dom = element;
                fly.isSynchronized = false;
                cachedElement = Ext.cache[element.id];
                if (cachedElement && cachedElement.isElement) {
                    cachedElement.isSynchronized = false;
                }
            }

            return fly;
        }
    });

    /**
     * @member Ext
     * @method get
     * @alias Ext.dom.Element#get
     */
    Ext.get = function(element) {
        return Element.get(element);
    };

    /**
     * @member Ext
     * @method fly
     * @alias Ext.dom.Element#fly
     */
    Ext.fly = function() {
        return Element.fly.apply(Element, arguments);
    };

    Ext.ClassManager.onCreated(function() {
        Element.mixin('observable', Ext.mixin.Observable);
    }, null, 'Ext.mixin.Observable');

    //<deprecated product=touch since=2.0>
    Ext.deprecateClassMethod(this, {
        /**
         * @member Ext.dom.Element
         * @method remove
         * @inheritdoc Ext.dom.Element#destroy
         * @deprecated 2.0.0 Please use {@link #destroy} instead.
         */
        remove: 'destroy',
        /**
         * @member Ext.dom.Element
         * @method setHTML
         * @inheritdoc Ext.dom.Element#setHtml
         * @deprecated 2.0.0 Please use {@link #setHtml} instead.
         */
        setHTML: 'setHtml',
        /**
         * @member Ext.dom.Element
         * @method update
         * @inheritdoc Ext.dom.Element#setHtml
         * @deprecated 2.0.0 Please use {@link #setHtml} instead.
         */
        update: 'setHtml',
        /**
         * @member Ext.dom.Element
         * @method getHTML
         * @inheritdoc Ext.dom.Element#getHtml
         * @deprecated 2.0.0 Please use {@link #getHtml} instead.
         */
        getHTML: 'getHtml',
        /**
         * @member Ext.dom.Element
         * @method purgeAllListeners
         * @inheritdoc Ext.dom.Element#clearListeners
         * @deprecated 2.0.0 Please use {@link #clearListeners} instead.
         */
        purgeAllListeners: 'clearListeners',
        /**
         * @member Ext.dom.Element
         * @method removeAllListeners
         * @inheritdoc Ext.dom.Element#clearListeners
         * @deprecated 2.0.0 Please use {@link #clearListeners} instead.
         */
        removeAllListeners: 'clearListeners'
    });

    /**
     * @member Ext.dom.Element
     * @method cssTranslate
     * Translates an element using CSS 3 in 2D.
     * @removed 2.0.0
     */
    Ext.deprecateMethod(Ext.dom.Element, 'cssTranslate', null, "Ext.dom.Element.cssTranslate() has been removed");

    /**
     * @member Ext.dom.Element
     * @method getOuterHeight
     * Retrieves the height of the element account for the top and bottom margins.
     * @removed 2.0.0
     */
    Ext.deprecateMethod(Ext.dom.Element, 'getOuterHeight', null, "Ext.dom.Element.getOuterHeight() has been removed");

    /**
     * @member Ext.dom.Element
     * @method getOuterWidth
     * Retrieves the width of the element accounting for the left and right margins.
     * @removed 2.0.0
     */
    Ext.deprecateMethod(Ext.dom.Element, 'getOuterWidth', null, "Ext.dom.Element.getOuterWidth() has been removed");

    /**
     * @member Ext.dom.Element
     * @method getScrollParent
     * Gets the Scroller instance of the first parent that has one.
     * @removed 2.0.0
     */
    Ext.deprecateMethod(Ext.dom.Element, 'getScrollParent', null, "Ext.dom.Element.getScrollParent() has been removed");

    /**
     * @member Ext.dom.Element
     * @method isDescendent
     * Determines if this element is a descendant of the passed in Element.
     * @removed 2.0.0
     */
    Ext.deprecateMethod(Ext.dom.Element, 'isDescendent', null, "Ext.dom.Element.isDescendent() has been removed");

    /**
     * @member Ext.dom.Element
     * @method mask
     * Puts a mask over this element to disable user interaction.
     * @removed 2.0.0
     */
    Ext.deprecateMethod(Ext.dom.Element, 'mask', null, "Ext.dom.Element.mask() has been removed");

    /**
     * @member Ext.dom.Element
     * @method setTopLeft
     * Sets the element's top and left positions directly using CSS style.
     * @removed 2.0.0
     */
    Ext.deprecateMethod(Ext.dom.Element, 'setTopLeft', null, "Ext.dom.Element.setTopLeft() has been removed");

    /**
     * @member Ext.dom.Element
     * @method unmask
     * Removes a previously applied mask.
     * @removed 2.0.0
     */
    Ext.deprecateMethod(Ext.dom.Element, 'unmask', null, "Ext.dom.Element.unmask() has been removed");
    //</deprecated>

});


'use strict';

var EventEmitter = require('events').EventEmitter;
var Promise = require('bluebird');

/** A client-side router for `stateful-controller` using the history API
 * @module stateful-controller-browser-router
 * @author Joris van der Wel <joris@jorisvanderwel.com>
 */

/** Construct a new router
 *
 * @constructor
 * @augments EventEmitter
 * @alias module:stateful-controller-browser-router
 * @param {!Window} window The window containing your DOM Document. In the browser you should simple pass `window` here
 * @param {!URLStateMap} urlStateMap An object implementing two functions: `toURL(StateList) -> string` and `fromURL(string) -> StateList`
 * @param {!module:stateful-controller/lib/Controller} frontController The controller to trigger transitions on. This is the ancestor of all your other controllers.
 */
function Router(window, urlStateMap, frontController)
{
        EventEmitter.call(this);
        this.window = window;
        this.urlStateMap = urlStateMap;
        this.frontController = frontController;
        this.currentStateList = null;

        this._pendingTransitionPromise = null;
        this._pendingReplace = null;
        this._initialHistoryState = null;
        this._queue = {
                hasEntry: false,
                stateList: null,
                fromHistory: false,
                upgrade: false,
                push: false,
                promise: null,
                resolve: null,
                reject: null
        };

        if (!window || !urlStateMap || !frontController)
        {
                throw Error('Missing argument');
        }

        if (typeof this.urlStateMap.toURL !== 'function' ||
            typeof this.urlStateMap.fromURL !== 'function')
        {
                throw Error('Argument `urlStateMap` must implement toURL(states) and fromURL(url)');
        }

        if (!Router.isSupported(this.window))
        {
                throw Error('Argument `window` does not support the history API');
        }

        if (this.frontController.isStatefulController1 !== true)
        {
                throw Error('Argument `frontController` is not a stateful-controller');
        }

        this._onpopstate = this._onpopstate.bind(this);
}

/**
 * Test if the current environment supportes the history API
 * @param {!Window} window The window containing your DOM Document. In the browser you should simple pass `window` here
 * @return {Boolean}
 */
Router.isSupported = function(window)
{
        return !!(window.history &&
                  typeof window.history.pushState === 'function' &&
                  typeof window.history.replaceState === 'function');
};

module.exports = Router;
require('inherits')(Router, EventEmitter);

/** The current list of states that the controllers are in.
 * During a state transition, this value represents the state that we are transitioning from.
 * @member {?ControllerStateList} currentStateList
 * @memberOf module:stateful-controller-browser-router
 * @instance
 */

/**
 * Fired when the initial state is being upgraded
 *
 * @event module:stateful-controller-browser-router#upgradeInitialState
 * @param {!ControllerStateList} stateList
 * @param {String} url
 * @param {!Promise} promise Resolves when this transition is complete
 *
 */

/**
 * Fired when the popstate event is triggering a state transition in this router.
 *
 * @event module:stateful-controller-browser-router#historyPopState
 * @param {!ControllerStateList} stateList
 * @param {String} url
 * @param {!Promise} promise Resolves when this transition is complete
 *
 */

/**
 * Fired when a transition has completed
 *
 * @event module:stateful-controller-browser-router#transitionComplete
 * @param {!ControllerStateList} stateList
 * @param {String} url
 *
 */

/**
 * Fired when a transition has failed
 *
 * @event module:stateful-controller-browser-router#transitionFailed
 * @param {!ControllerStateList} stateList
 * @param {Error} err
 */

/**
 * Fired when a transition caused by the "popstate" event has failed.
 * ("transitionFailed" is also fired).
 * If you do not listen for this event, failed "popstate" transitions will
 * cause an unhandled rejection.
 *
 * @event module:stateful-controller-browser-router#historyPopStateFailed
 * @param {!ControllerStateList} stateList
 * @param {Error} err
 */

/**
 * Start listening for popstate events (e.g. the user uses the back button)
 */
Router.prototype.attachPopStateListener = function()
{
        this.window.addEventListener('popstate', this._onpopstate);

        if (!this._initialHistoryState)
        {
                this._saveStateAsInitial();
        }
};

/**
 * Determine the current state of the page (by looking at `history.state` or `location`)
 * and send an "upgrade" state transition to the front controller.
 * This method should be called if the server has sent you a html document of a specific state list that you would like to wrap.
 * @returns {!Promise} Resolves when the state transition of the front controller is done.
 * @fires module:stateful-controller-browser-router#upgradeInitialState
 */
Router.prototype.upgradeInitialState = function()
{
        this._saveStateAsInitial();
        return this._handleHistoryState(this._initialHistoryState, true, 'upgradeInitialState');
};

/**
 * Trigger a state transition to the given stateList using the front controller.
 *
 * @param {!ControllerStateList} stateList
 * @param {Boolean} [pushHistory=true] If this value is `true`, a new entry will be
 *                  added to the browser history
 * @return {!Promise} Resolves when the state transition of the front controller is done.
 *         If a state transition is pending, this promise will reject.
 * @fires module:stateful-controller-browser-router#transitionComplete
 */
Router.prototype.enterStates = Promise.method(function enterStates(stateList, pushHistory)
{
        if (pushHistory === void 123)
        {
                pushHistory = true;
        }

        if (this.pending)
        {
                throw Error('A previous state transition is still pending');
        }

        this._pendingTransitionPromise = this.frontController.state(stateList)
        .bind(this)
        .finally(function()
        {
                this._pendingTransitionPromise = null;
        })
        .then(function()
        {
                this.currentStateList = this._pendingReplace || stateList;
                var url = this._pushHistoryState(this.currentStateList, pushHistory);
                this.emit('transitionComplete', stateList, url);
        }, function(err)
        {
                this.emit('transitionFailed', stateList, err);
                return Promise.reject(err);
        })
        .finally(function()
        {
                this._pendingReplace = null;
                this._doQueuedTransition();
        }).return(null);

        return this._pendingTransitionPromise;
});

/**
 * Trigger a state transition to the given stateList using the front controller.
 * If a state transition is currently in progress (see the `pending` attribute), the new state
 * transition will be deferred until the previous one is complete.
 * If there already is a state transition queued, the `stateList` given in this method call will
 * overwrite the previously queued transition. (in other words, the queue has a max size of one).
 *
 * This method is most useful when responding to user input. If a user rapidly clicks on different
 * buttons, only the last one he clicked on should be the state he ends up with. There is no need to
 * go through all the transitions in between.
 *
 * @param {!ControllerStateList} stateList
 * @param {Boolean} [pushHistory=true] If this value is `true`, a new entry will be
 *                  added to the browser history
 * @return {!Promise} Resolves when the state transition of the front controller is done
 * @fires module:stateful-controller-browser-router#transitionComplete
 */
Router.prototype.queueEnterStates = function(stateList, pushHistory)
{
        if (pushHistory === void 123)
        {
                pushHistory = true;
        }

        if (this.pending)
        {
                return this._addToQueue(stateList, false, false, pushHistory);
        }
        else
        {
                return this.enterStates(stateList, pushHistory);
        }
};

/**
 * This method does not perform a state transition, it only sets a new URL for the current state.
 * @param {!ControllerStateList} stateList
 */
Router.prototype.replaceStateList = function(stateList)
{
        if (this.pending)
        {
                this._pendingReplace = stateList;
        }
        else
        {
                this.currentStateList = stateList;
                this._pushHistoryState(this.currentStateList, false);
        }
};

/** Is there a state transition currently pending?
 * @member {!boolean} pending
 * @memberOf module:stateful-controller-browser-router
 * @instance
 */
Object.defineProperty(Router.prototype, 'pending', {
        get: function()
        {
                return !!(this._pendingTransitionPromise || this._queue.hasEntry);
        }
});

Router.prototype._saveStateAsInitial = function()
{
        var history = this.window.history;
        if (history.state && history.state.statefulControllerRouterUrl)
        {
                this._initialHistoryState = history.state;
        }
        else
        {
                var path = this.window.location.pathname + this.window.location.search;
                this._initialHistoryState = this._urlToHistoryState(path);
        }
};

Router.prototype._addToQueue = function(stateList, fromHistory, upgrade, push)
{
        // only one entry in the queue
        this._queue.hasEntry = true;
        this._queue.stateList = stateList;
        this._queue.fromHistory = fromHistory;
        this._queue.upgrade = !!upgrade;
        this._queue.push = !!push;

        if (!this._queue.promise)
        {
                this._queue.promise = new Promise(function(resolve, reject)
                {
                        this._queue.resolve = resolve;
                        this._queue.reject = reject;
                }.bind(this));
        }

        return this._queue.promise;
};

Router.prototype._onpopstate = function(e)
{
        // If we end up at the first history entry, e.state will be null
        var historyState = e.state || this._initialHistoryState;
        this._handleHistoryState(historyState, false, 'historyPopState');
};

Router.prototype._urlToHistoryState = function(url)
{
        return {
                statefulControllerRouterUrl: {
                        url: url
                }
        };
};

Router.prototype._pushHistoryState = function(stateList, push)
{
        var url = this.urlStateMap.toURL(stateList);
        var historyTitle = ''; // todo: title
        var historyState = this._urlToHistoryState(url);

        if (push)
        {
                this.window.history.pushState(historyState, historyTitle, url);
        }
        else
        {
                this.window.history.replaceState(historyState, historyTitle, url);
        }

        return url;
};

Router.prototype._handleHistoryState = function(historyState, upgrade, event)
{
        if (!historyState ||
            !historyState.statefulControllerRouterUrl)
        {
                return;
        }

        var url = historyState.statefulControllerRouterUrl.url;
        var stateList = this.urlStateMap.fromURL(url);

        var promise;

        if (this.pending)
        {
                promise = this._addToQueue(stateList, url, upgrade, false);
        }
        else
        {
                this._pendingTransitionPromise = this.frontController.state(stateList, upgrade)
                .bind(this)
                .finally(function()
                {
                        this._pendingTransitionPromise = null;
                })
                .then(function()
                {
                        this.currentStateList = stateList;
                        this.emit('transitionComplete', stateList, url);
                }, function(err)
                {
                        this.emit('transitionFailed', stateList, err);

                        if (event === 'historyPopState')
                        {
                                if (this.emit('historyPopStateFailed', stateList, err))
                                {
                                        // Event was handled, stop propagating the error
                                        return;
                                }
                        }

                        return Promise.reject(err);
                })
                .finally(function()
                {
                        this._doQueuedTransition();
                }).return(null);

                promise = this._pendingTransitionPromise;
        }

        /* istanbul ignore else : internal use */
        if (event)
        {
                this.emit(event, stateList, url, promise);
        }

        return promise;
};

Router.prototype._doQueuedTransition = function()
{
        if (!this._queue.hasEntry)
        {
                return;
        }

        var stateList = this._queue.stateList;
        var fromHistory = this._queue.fromHistory;
        var upgrade = this._queue.upgrade;
        var push = this._queue.push;
        var resolve = this._queue.resolve;
        var reject = this._queue.reject;

        this._queue = {
                hasEntry: false,
                stateList: null,
                fromHistory: false, // or url
                push: false,
                promise: null,
                resolve: null,
                reject: null
        };

        this._pendingTransitionPromise = this.frontController.state(stateList, upgrade)
        .bind(this)
        .finally(function()
        {
                this._pendingTransitionPromise = null;
        })
        .then(function()
        {
                var url;

                this.currentStateList = this._pendingReplace || stateList;

                if (fromHistory === false)
                {
                        url = this._pushHistoryState(this.currentStateList, push);
                }
                else
                {
                        url = fromHistory;
                }

                this.emit('transitionComplete', stateList, url);
        }, function(err)
        {
                this.emit('transitionFailed', stateList, err);
                return Promise.reject(err);
        })
        .finally(function()
        {
                this._pendingReplace = null;
                this._doQueuedTransition();
        })
        .return(null)
        .then(resolve, reject)
        .return(null);
};


/**
 * @docauthor Tommy Maintz <tommy@sencha.com>
 *
 * A mixin which allows a data component to be sorted. This is used by e.g. {@link Ext.data.Store} and {@link Ext.data.TreeStore}.
 *
 * __Note:__ This mixin is mainly for internal library use and most users should not need to use it directly. It
 * is more likely you will want to use one of the component classes that import this mixin, such as
 * {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
 */
Ext.define("Ext.util.Sortable", {
    extend: 'Ext.mixin.Mixin',
    /**
     * @property {Boolean} isSortable
     * Flag denoting that this object is sortable. Always `true`.
     * @readonly
     */
    isSortable: true,

    mixinConfig: {
        hooks: {
            destroy: 'destroy'
        }
    },

    /**
     * @property {String} defaultSortDirection
     * The default sort direction to use if one is not specified.
     */
    defaultSortDirection: "ASC",

    requires: [
        'Ext.util.Sorter'
    ],

    /**
     * @property {String} sortRoot
     * The property in each item that contains the data to sort.
     */

    /**
     * Performs initialization of this mixin. Component classes using this mixin should call this method during their
     * own initialization.
     */
    initSortable: function() {
        var me = this,
            sorters = me.sorters;

        /**
         * @property {Ext.util.MixedCollection} sorters
         * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
         */
        me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
            return item.id || item.property;
        });

        if (sorters) {
            me.sorters.addAll(me.decodeSorters(sorters));
        }
    },

    /**
     * Sorts the data in the Store by one or more of its properties. Example usage:
     *
     *     //sort by a single field
     *     myStore.sort('myField', 'DESC');
     *
     *     //sorting by multiple fields
     *     myStore.sort([
     *         {
     *             property : 'age',
     *             direction: 'ASC'
     *         },
     *         {
     *             property : 'name',
     *             direction: 'DESC'
     *         }
     *     ]);
     *
     * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
     * the actual sorting to its internal {@link Ext.util.MixedCollection}.
     *
     * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
     *
     *     store.sort('myField');
     *     store.sort('myField');
     *
     * Is equivalent to this code, because Store handles the toggling automatically:
     *
     *     store.sort('myField', 'ASC');
     *     store.sort('myField', 'DESC');
     *
     * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
     * {@link Ext.data.Model Model}, or an array of sorter configurations.
     * @param {String} [direction="ASC"] The overall direction to sort the data by.
     * @param {String} [where]
     * @param {Boolean} [doSort]
     * @return {Ext.util.Sorter[]}
     */
    sort: function(sorters, direction, where, doSort) {
        var me = this,
            sorter, sorterFn,
            newSorters;

        if (Ext.isArray(sorters)) {
            doSort = where;
            where = direction;
            newSorters = sorters;
        }
        else if (Ext.isObject(sorters)) {
            doSort = where;
            where = direction;
            newSorters = [sorters];
        }
        else if (Ext.isString(sorters)) {
            sorter = me.sorters.get(sorters);

            if (!sorter) {
                sorter = {
                    property : sorters,
                    direction: direction
                };
                newSorters = [sorter];
            }
            else if (direction === undefined) {
                sorter.toggle();
            }
            else {
                sorter.setDirection(direction);
            }
        }

        if (newSorters && newSorters.length) {
            newSorters = me.decodeSorters(newSorters);
            if (Ext.isString(where)) {
                if (where === 'prepend') {
                    sorters = me.sorters.clone().items;

                    me.sorters.clear();
                    me.sorters.addAll(newSorters);
                    me.sorters.addAll(sorters);
                }
                else {
                    me.sorters.addAll(newSorters);
                }
            }
            else {
                me.sorters.clear();
                me.sorters.addAll(newSorters);
            }

            if (doSort !== false) {
                me.onBeforeSort(newSorters);
            }
        }

        if (doSort !== false) {
            sorters = me.sorters.items;
            if (sorters.length) {
                //construct an amalgamated sorter function which combines all of the Sorters passed
                sorterFn = function(r1, r2) {
                    var result = sorters[0].sort(r1, r2),
                        length = sorters.length,
                        i;

                        //if we have more than one sorter, OR any additional sorter functions together
                        for (i = 1; i < length; i++) {
                            result = result || sorters[i].sort.call(this, r1, r2);
                        }

                    return result;
                };

                me.doSort(sorterFn);
            }
        }

        return sorters;
    },

    onBeforeSort: Ext.emptyFn,

    /**
     * @private
     * Normalizes an array of sorter objects, ensuring that they are all {@link Ext.util.Sorter} instances.
     * @param {Array} sorters The sorters array.
     * @return {Array} Array of {@link Ext.util.Sorter} objects.
     */
    decodeSorters: function(sorters) {
        if (!Ext.isArray(sorters)) {
            if (sorters === undefined) {
                sorters = [];
            } else {
                sorters = [sorters];
            }
        }

        var length = sorters.length,
            Sorter = Ext.util.Sorter,
            fields = this.model ? this.model.prototype.fields : null,
            field,
            config, i;

        for (i = 0; i < length; i++) {
            config = sorters[i];

            if (!(config instanceof Sorter)) {
                if (Ext.isString(config)) {
                    config = {
                        property: config
                    };
                }

                Ext.applyIf(config, {
                    root     : this.sortRoot,
                    direction: "ASC"
                });

                if (config.fn) {
                    config.sorterFn = config.fn;
                }

                //support a function to be passed as a sorter definition
                if (typeof config == 'function') {
                    config = {
                        sorterFn: config
                    };
                }

                // ensure sortType gets pushed on if necessary
                if (fields && !config.transform) {
                    field = fields.get(config.property);
                    config.transform = field ? field.sortType : undefined;
                }
                sorters[i] = Ext.create('Ext.util.Sorter', config);
            }
        }

        return sorters;
    },

    getSorters: function() {
        return this.sorters.items;
    },

    destroy: function () {
        this.callSuper();
        Ext.destroy(this.sorters);
    }
});


"use strict";

var base = require('./base');
var path = require('path');
var fs = require('fs');
var handlebars = require('handlebars');

function addExportObj(obj, root, lstexport, option) {
    if (base.isBaseType(obj.type)) {
        return lstexport;
    }

    if (obj.type == 'type') {
        if (!base.isBaseType(obj.val)) {
            var curobj = base.getGlobalObj(obj.val);
            lstexport = addExportObj(curobj, root, lstexport);
        }
    }
    else if (obj.type == 'enum') {

    }
    else {
        for (var i = 0; i < obj.val.length; ++i) {
            if (!base.isBaseType(obj.val[i].type)) {
                if (option.isclient) {
                    if (obj.val[i].name.name.indexOf('_') == 0) {
                        continue ;
                    }
                }

                var curobj = base.getGlobalObj(obj.val[i].type, root);
                lstexport = addExportObj(curobj, root, lstexport, option);
            }
        }
    }

    if (lstexport.indexOf(obj.name) == -1) {
        lstexport.push(obj.name);
    }

    return lstexport;
}

function procStaticTable(objname, root, option) {
    var obj = base.getGlobalObj(objname, root);
    if (obj != undefined) {
        for (var i = 0; i < obj.val.length; ++i) {
            if (option.isclient) {
                if (obj.val[i].name.name.indexOf('_') == 0) {
                    continue ;
                }
            }

            if (base.isStatic(obj.val[i].type, root)) {
                obj.hasstatic = true;
            }
            else if (base.isStruct(obj.val[i].type, root)) {
                var cobj = procStaticTable(obj.val[i].type, root, option);
                if (cobj != undefined) {
                    if (cobj.hasOwnProperty('hasstatic') && cobj.hasstatic) {
                        obj.hasstatic = true;
                    }
                }
            }
        }
    }

    return obj;
}

function replaceStr(str) {
    while(str.indexOf('<') >= 0) {
        str = str.replace('<', '<');
    }

    while(str.indexOf('>') >= 0) {
        str = str.replace('>', '>');
    }

    while(str.indexOf('"') >= 0) {
        str = str.replace('"', '"');
    }

    while(str.indexOf('=') >= 0) {
        str = str.replace('=', '=');
    }

    while(str.indexOf('&') >= 0) {
        str = str.replace('&', '&');
    }

    return str;
}

// plugins = {exportHead: func, exportEnd: func, exportTypedef: func, exportEnum: func, exportStruct: func, exportStatic: func, getExportFile: func}
// callback(isok, errinfo)
function exportCode(projname, root, plugins, objname, callback, option) {
    if (option == undefined) {
        option = {isclient: false, mainobj: objname, projname: projname};
    }
    else {
        if (!option.hasOwnProperty('isclient')) {
            option.isclient = false;
        }

        option.mainobj = objname;
        option.projname = projname;
    }

    var lstexport = [];
    var obj = base.getGlobalObj(objname, root);
    if (obj != undefined) {
        lstexport = addExportObj(obj, root, lstexport, option);
    }

    procStaticTable(objname, root, option);

    if (plugins != undefined) {
        if (!fs.existsSync(projname)) {
            fs.mkdirSync(projname);
        }

        var curparams = {projname_up: projname.toUpperCase(), projname: projname};

        //------------------------------------------------------------------------------------
        // typedef

        curparams.block_typedef = [];

        var typedefarr = undefined;
        for (var i = 0; i < lstexport.length; ++i) {
            var obj = base.getGlobalObj(lstexport[i], root);
            if (obj != undefined) {
                if (obj.type == 'type') {
                    var cs = plugins.exportTypedef(obj, root, callback, option);
                    if (cs != undefined) {
                        if (typedefarr == undefined) {
                            typedefarr = [];
                            for (var ai = 0; ai < cs.length; ++ai) {
                                typedefarr.push([]);
                            }
                        }

                        for (var ai = 0; ai < cs.length; ++ai) {
                            typedefarr[ai].push(cs[ai]);
                        }
                    }
                }
            }
        }

        if (typedefarr != undefined) {
            curparams.block_typedef = alignCodeEx(typedefarr, '');
        }

        //------------------------------------------------------------------------------------
        // enum

        curparams.block_enum = [];

        for (var i = 0; i < lstexport.length; ++i) {
            var obj = base.getGlobalObj(lstexport[i], root);
            if (obj != undefined) {
                if (obj.type == 'enum') {
                    var enumobj = plugins.exportEnum(obj, root, callback, option);
                    if (enumobj != undefined) {
                        curparams.block_enum.push(enumobj);
                    }
                }
            }
        }

        //------------------------------------------------------------------------------------
        // struct

        curparams.block_struct = [];
        curparams.csvloader = [];

        for (var i = 0; i < lstexport.length; ++i) {
            var obj = base.getGlobalObj(lstexport[i], root);
            if (obj != undefined) {
                if (obj.name == objname) {
                    var mainobj = plugins.exportMainObj(obj, root, callback, option);
                    if (mainobj != undefined) {
                        curparams.mainobj = mainobj;
                    }

                    base.forEachStruct(obj.name, obj, root, function (structname, cobj, root) {
                        if (option.isclient) {
                            if (cobj.name.name.indexOf('_') == 0) {
                                return;
                            }
                        }

                        if (base.isStatic(cobj.type, root)) {
                            var csvloader = plugins.exportCSVLoader(cobj, root, callback, option);
                            if (csvloader != undefined) {
                                curparams.csvloader.push(csvloader);
                            }
                        }
                    });
                }
                else if (obj.type == 'struct') {
                    var structobj = plugins.exportStruct(obj, root, callback, option);
                    if (structobj != undefined) {
                        curparams.block_struct.push(structobj);
                    }
                }
                else if (obj.type == 'static') {
                    var structobj = plugins.exportStatic(obj, root, callback, option);
                    if (structobj != undefined) {
                        curparams.block_struct.push(structobj);
                    }
                }
            }
        }

        //------------------------------------------------------------------------------------
        // message

        curparams.block_sendmsg = [];
        curparams.block_onmsg = [];

        for (var i = 0; i < root.length; ++i) {
            if (root[i].type == 'message') {
                if (base.isReqMsg(root[i].name)) {
                    var co = plugins.exportSendMsg(root[i], root, callback, option);
                    curparams.block_sendmsg.push(co);
                }
                else if (base.isResMsg(root[i].name)) {
                    var co = plugins.exportOnMsg(root[i], root, callback, option);
                    curparams.block_onmsg.push(co);
                }
            }
        }

        //------------------------------------------------------------------------------------
        // template

        var tmpfilename = plugins.getTemplate(projname, option);
        var tmpbuf = fs.readFileSync(path.join(__dirname, 'plugins', tmpfilename), 'utf-8');
        var tmphb = handlebars.compile(tmpbuf);
        tmpbuf = tmphb(curparams);
        tmpbuf = replaceStr(tmpbuf);
        var tmparr = JSON.parse(tmpbuf);
        if (tmparr != undefined) {
            for (var ti = 0; ti < tmparr.length; ++ti) {
                var curtmpbuf = fs.readFileSync(path.join(__dirname, 'plugins', tmparr[ti].srcfile), 'utf-8');
                var curtemplate = handlebars.compile(curtmpbuf);
                var strbuf = curtemplate(curparams);
                strbuf = replaceStr(strbuf);
                fs.writeFileSync(projname + '/' + tmparr[ti].filename, strbuf, 'utf-8');
            }
        }

        return tmparr;
    }

    return undefined;
}

function alignCode(arr, beginstr) {
    var str = '';
    var lenarr = [];

    for (var i = 0; i < arr.length; ++i) {
        var cl = 0;
        for (var j = 0; j < arr[i].length; ++j) {
            if (cl < arr[i][j].length) {
                cl = arr[i][j].length;
            }
        }

        lenarr.push(cl);
    }

    for (var i = 0; i < arr[0].length; ++i) {
        str += beginstr;

        for (var j = 0; j < arr.length; ++j) {
            str += arr[j][i];

            if (j < arr.length - 1) {
                for (let k = arr[j][i].length; k < lenarr[j] + 1; ++k) {
                    str += ' ';
                }
            }
        }

        str += '\r\n';
    }

    return str;
}

function alignCodeEx(arr, beginstr) {
    var strarr = [];
    var lenarr = [];

    for (var i = 0; i < arr.length; ++i) {
        var cl = 0;
        for (var j = 0; j < arr[i].length; ++j) {
            if (cl < arr[i][j].length) {
                cl = arr[i][j].length;
            }
        }

        lenarr.push(cl);
    }

    for (var i = 0; i < arr[0].length; ++i) {
        strarr.push('');

        strarr[i] += beginstr;

        for (var j = 0; j < arr.length; ++j) {
            strarr[i] += arr[j][i];

            if (j < arr.length - 1) {
                for (let k = arr[j][i].length; k < lenarr[j] + 1; ++k) {
                    strarr[i] += ' ';
                }
            }
        }
    }

    return strarr;
}

exports.exportCode = exportCode;
exports.alignCode = alignCode;
exports.alignCodeEx = alignCodeEx;


"use strict";

var utils = require('./lib/utils'),
    extend = utils.extend,
    forEach = utils.forEach,
    isArray = utils.isArray,
    isObject = utils.isObject,
    isEnumerable = utils.isEnumerable,
    isArrayOfObjects = utils.isArrayOfObjects,
    isArrayOfFunctions = utils.isArrayOfFunctions,
    isArrayOfNonEnumerables = utils.isArrayOfNonEnumerables;

// An eventual schema exists for a key-value object.
var EventualSchema = function (rules) {
  this._instantiatedDate = new Date();
  this._instanceCount = 0;
  this._propertyCount = 0;

  this._collatedInstances = null;

  this._rules = (rules && this._checkRules(rules)) || [];

  this.initEventualSchema();
};

EventualSchema.prototype._checkRules = function (rules) {
  var isRulesListOfFunctions = isArrayOfFunctions(rules);
  if (!isRulesListOfFunctions) {
    throw new Error("EventualSchema's rules argument should only be passed a list of functions.");
  }

  return rules;
};

EventualSchema.prototype.initEventualSchema = function (eventualSchema, isFrozen) {
  this._eventualSchema = null;
  this.frozen = false;

  if (isFrozen) {
    this._eventualSchema = eventualSchema || {};
    this.frozen = isFrozen;
  }
};

EventualSchema.prototype.get = function () {
  if (!this.frozen) {
    throw new Error("You cannot get the _eventualSchema until the necessary rules have been passed.");
  }
  return this._eventualSchema;
};

// It does have to recurse when adding.
//
// Given three of these:
// ```json
// {
//   a: {
//     num: 7,
//     arr: []
//   },
//   b: {
//       arr: [
//         {
//             name: 'hello world'
//         }
//       ],
//       value: {
//         type: 'person',
//         name: 'gilly'
//       }
//   },
//   c: {
//     arr: []
//   }
// }
// ```
// Create this:
// ```json
// {
//   a: {
//     _propertyCount: 3,
//     num: { _propertyCount: 3 },
//     arr: { _propertyCount: 3 }
//   },
//   b: {
//     _propertyCount: 3,
//     arr: [
//       {
//         name: { _propertyCount: 3 }
//       }
//     ],
//     value: {
//       _propertyCount: 3,
//       type: { _propertyCount: 3 },
//       name: { _propertyCount: 3 }
//     }
//   },
//   c: {
//     _propertyCount: 3,
//     arr: { _propertyCount: 3 }
//   }
// }
// ```
// I chose this representation because even-though I need to recurse to create it,
// when I try to recurse later on to simplify the structure I can do so easily.
// It also allows more information and does not impart a syntax onto the idea of deep
// property hierarchies and is therefore generalised to more use-cases.
EventualSchema.prototype.add = function (instance) {
  this._checkIfFrozen();

  this._collatedInstances = this._addInstance(this._collatedInstances, instance);
  this._instanceCount += 1;

  if (this._isReadyToFreeze()) {
    this._freeze();
  }

  return this;
};

EventualSchema.prototype.freeze = function () {
  this._checkIfFrozen();

  this._freeze();

  return this;
};

EventualSchema.prototype._checkIfFrozen = function () {
  if (this.frozen) {
    throw new Error("Once frozen EventualSchema#get() is the only callable method.");
  }
};

EventualSchema.prototype._addInstance = function (collatedInstances, instance, increaseCount) {
  var self = this;

  increaseCount = increaseCount === undefined ? true : increaseCount;

  // If we are operating upon just a value, then we just return this.
  if (!isEnumerable(instance) || isArrayOfNonEnumerables(instance)) {
    collatedInstances = collatedInstances || {};

    return collatedInstances;
  }

  // If what we are operating upon is an object or an array then we execute this.
  if (isObject(instance)) {
    collatedInstances = collatedInstances || {};
    forEach(instance, function (instanceValue, instanceKey) {
      var currentEventualSchemaLevel = collatedInstances[instanceKey] || {};

      var keyAddedForFirstTime = !(instanceKey in collatedInstances);
      increaseCount = !keyAddedForFirstTime;

      currentEventualSchemaLevel = self._addInstance(currentEventualSchemaLevel, instanceValue, true);
      currentEventualSchemaLevel._propertyCount = (increaseCount && currentEventualSchemaLevel._propertyCount) ? currentEventualSchemaLevel._propertyCount + 1 : 1;

      if (keyAddedForFirstTime) {
        self._propertyCount += 1;
      }

      collatedInstances[instanceKey] = currentEventualSchemaLevel;
    });

    return collatedInstances;
  }

  if (isArrayOfObjects(instance)) {
    collatedInstances = collatedInstances || {};
    forEach(instance, function (instanceValue, instanceKey) {
      var currentEventualSchemaLevel = {};

      currentEventualSchemaLevel._arrayObjects = self._addInstance(extend({}, collatedInstances._arrayObjects), instanceValue, false);
      currentEventualSchemaLevel._propertyCount = collatedInstances._propertyCount ? collatedInstances._propertyCount : 0; // @todo: the reason for it being set to 0 here is that it wil be increased immediately afterwards.

      collatedInstances = currentEventualSchemaLevel;
    });

    // @todo: BUG: the array of objects code is causing the property to be counted wrongly in two places.
    return collatedInstances;
  }

  // The only point we reach this line is if we've finished recursing and are on the first level of the instance.
  return collatedInstances;
};

// If any of the rules return `true` we are ready to freeze.
// No rules: no freezing.
EventualSchema.prototype._isReadyToFreeze = function (ctx) {
  var self = this;

  ctx = ctx || {};

  // If *any* of these rules is true, then it shall freeze is true, otherwise it will be false.
  var isReadyToFreeze =  this._rules.length > 0 ? this._rules.reduce(function (acc, fn) {
    return acc || fn.call(self, ctx);
  }, false) : false;

  return isReadyToFreeze;
};

EventualSchema.prototype._freeze = function () {
  this._generateEventualSchema(this._collatedInstances);
  this.frozen = true;

  return this.frozen;
};

EventualSchema.prototype._generateEventualSchema = function (collatedInstances) {
  this._eventualSchema = collatedInstances;

  return this._eventualSchema;
};

exports = module.exports = EventualSchema;


/**
 * Represents a one to one association with another model. The owner model is expected to have
 * a foreign key which references the primary key of the associated model:
 *
 *     Ext.define('Person', {
 *         extend: 'Ext.data.Model',
 *         config: {
 *             fields: [
 *                 { name: 'id', type: 'int' },
 *                 { name: 'name', type: 'string' },
 *                 { name: 'address_id', type: 'int'}
 *             ],
 *
 *             // we can use the hasOne shortcut on the model to create a hasOne association
 *             hasOne: {
 *                 model: 'Address',
 *                 name : 'address'
 *             }
 *         }
 *     });
 *
 *     Ext.define('Address', {
 *         extend: 'Ext.data.Model',
 *         config: {
 *             fields: [
 *                 { name: 'id', type: 'int' },
 *                 { name: 'number', type: 'string' },
 *                 { name: 'street', type: 'string' },
 *                 { name: 'city', type: 'string' },
 *                 { name: 'zip', type: 'string' }
 *             ]
 *         }
 *     });
 *
 * In the example above we have created models for People and Addresses, and linked them together
 * by saying that each Person has a single Address. This automatically links each Person to an Address
 * based on the Persons address_id, and provides new functions on the Person model:
 *
 * ## Generated getter function
 *
 * The first function that is added to the owner model is a getter function:
 *
 *     var person = Ext.create('Person', {
 *         id: 100,
 *         address_id: 20,
 *         name: 'John Smith'
 *     });
 *
 *     person.getAddress(function(address, operation) {
 *         // do something with the address object
 *         alert(address.get('id')); // alerts 20
 *     }, this);
 *
 * The getAddress function was created on the Person model when we defined the association. This uses the
 * Persons configured {@link Ext.data.proxy.Proxy proxy} to load the Address asynchronously, calling the provided
 * callback when it has loaded.
 *
 * The new getAddress function will also accept an object containing success, failure and callback properties
 * - callback will always be called, success will only be called if the associated model was loaded successfully
 * and failure will only be called if the associated model could not be loaded:
 *
 *     person.getAddress({
 *         reload: true, // force a reload if the owner model is already cached
 *         callback: function(address, operation) {}, // a function that will always be called
 *         success : function(address, operation) {}, // a function that will only be called if the load succeeded
 *         failure : function(address, operation) {}, // a function that will only be called if the load did not succeed
 *         scope   : this // optionally pass in a scope object to execute the callbacks in
 *     });
 *
 * In each case above the callbacks are called with two arguments - the associated model instance and the
 * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
 * useful when the instance could not be loaded.
 *
 * Once the getter has been called on the model, it will be cached if the getter is called a second time. To
 * force the model to reload, specify reload: true in the options object.
 *
 * ## Generated setter function
 *
 * The second generated function sets the associated model instance - if only a single argument is passed to
 * the setter then the following two calls are identical:
 *
 *     // this call...
 *     person.setAddress(10);
 *
 *     // is equivalent to this call:
 *     person.set('address_id', 10);
 *
 * An instance of the owner model can also be passed as a parameter.
 *
 * If we pass in a second argument, the model will be automatically saved and the second argument passed to
 * the owner model's {@link Ext.data.Model#save save} method:
 *
 *     person.setAddress(10, function(address, operation) {
 *         // the address has been saved
 *         alert(address.get('address_id')); //now alerts 10
 *     });
 *
 *     //alternative syntax:
 *     person.setAddress(10, {
 *         callback: function(address, operation) {}, // a function that will always be called
 *         success : function(address, operation) {}, // a function that will only be called if the load succeeded
 *         failure : function(address, operation) {}, // a function that will only be called if the load did not succeed
 *         scope   : this //optionally pass in a scope object to execute the callbacks in
 *     });
 *
 * ## Customization
 *
 * Associations reflect on the models they are linking to automatically set up properties such as the
 * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
 *
 *     Ext.define('Person', {
 *         extend: 'Ext.data.Model',
 *         config: {
 *             fields: [
 *                 // ...
 *             ],
 *
 *             hasOne: {
 *                 model     : 'Address',
 *                 primaryKey: 'unique_id',
 *                 foreignKey: 'addr_id'
 *             }
 *         }
 *     });
 *
 * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'address_id')
 * with our own settings. Usually this will not be needed.
 *
 * ###Further Reading
 * [Sencha Touch Models and Associations](../../../core_concepts/data/models.html)
 */
Ext.define('Ext.data.association.HasOne', {
    extend: 'Ext.data.association.Association',
    alternateClassName: 'Ext.data.HasOneAssociation',

    alias: 'association.hasone',

    config: {
        /**
         * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
         * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
         * model called Person would set up a address_id foreign key.
         *
         *     Ext.define('Person', {
         *         extend: 'Ext.data.Model',
         *         fields: ['id', 'name', 'address_id'], // refers to the id of the address object
         *         hasOne: 'Address'
         *     });
         *
         *     Ext.define('Address', {
         *         extend: 'Ext.data.Model',
         *         fields: ['id', 'number', 'street', 'city', 'zip'],
         *         belongsTo: 'Person'
         *     });
         *     var Person = new Person({
         *         id: 1,
         *         name: 'John Smith',
         *         address_id: 13
         *     }, 1);
         *     person.getAddress(); // Will make a call to the server asking for address_id 13
         *
         */
        foreignKey: undefined,

        /**
         * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
         * Defaults to 'get' + the name of the foreign model, e.g. getAddress
         */
        getterName: undefined,

        /**
         * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
         * Defaults to 'set' + the name of the foreign model, e.g. setAddress
         */
        setterName: undefined,

        instanceName: undefined
    },

    applyForeignKey: function(foreignKey) {
        if (!foreignKey) {
            var inverse = this.getInverseAssociation();
            if (inverse) {
                foreignKey = inverse.getForeignKey();
            } else {
                foreignKey = this.getAssociatedName().toLowerCase() + '_id';
            }
        }
        return foreignKey;
    },

    updateForeignKey: function(foreignKey, oldForeignKey) {
        var fields = this.getOwnerModel().getFields(),
            field = fields.get(foreignKey);

        if (!field) {
            field = new Ext.data.Field({
                name: foreignKey
            });
            fields.add(field);
            fields.isDirty = true;
        }

        if (oldForeignKey) {
            field = fields.get(oldForeignKey);
            if (field) {
                fields.remove(field);
                fields.isDirty = true;
            }
        }
    },

    applyInstanceName: function(instanceName) {
        if (!instanceName) {
            instanceName = this.getAssociatedName() + 'HasOneInstance';
        }
        return instanceName;
    },

    applyAssociationKey: function(associationKey) {
        if (!associationKey) {
            var associatedName = this.getAssociatedName();
            associationKey = associatedName[0].toLowerCase() + associatedName.slice(1);
        }
        return associationKey;
    },

    applyGetterName: function(getterName) {
        if (!getterName) {
            var associatedName = this.getAssociatedName();
            getterName = 'get' + associatedName[0].toUpperCase() + associatedName.slice(1);
        }
        return getterName;
    },

    applySetterName: function(setterName) {
        if (!setterName) {
            var associatedName = this.getAssociatedName();
            setterName = 'set' + associatedName[0].toUpperCase() + associatedName.slice(1);
        }
        return setterName;
    },

    updateGetterName: function(getterName, oldGetterName) {
        var ownerProto = this.getOwnerModel().prototype;
        if (oldGetterName) {
            delete ownerProto[oldGetterName];
        }
        if (getterName) {
            ownerProto[getterName] = this.createGetter();
        }
    },

    updateSetterName: function(setterName, oldSetterName) {
        var ownerProto = this.getOwnerModel().prototype;
        if (oldSetterName) {
            delete ownerProto[oldSetterName];
        }
        if (setterName) {
            ownerProto[setterName] = this.createSetter();
        }
    },

    /**
     * @private
     * Returns a setter function to be placed on the owner model's prototype
     * @return {Function} The setter function
     */
    createSetter: function() {
        var me              = this,
            foreignKey      = me.getForeignKey(),
            instanceName    = me.getInstanceName(),
            associatedModel = me.getAssociatedModel();

        //'this' refers to the Model instance inside this function
        return function(value, options, scope) {
            var Model = Ext.data.Model,
                record;

            if (value && value.isModel) {
                value = value.getId();
            }

            this.set(foreignKey, value);

            if (value || value === 0) {
                record = Model.cache[Model.generateCacheId(associatedModel.modelName, value)];
                if (record) {
                    this[instanceName] = record;
                }
            } else {
                delete this[instanceName];
            }

            if (Ext.isFunction(options)) {
                options = {
                    callback: options,
                    scope: scope || this
                };
            }

            if (Ext.isObject(options)) {
                return this.save(options);
            }

            return this;
        };
    },

    /**
     * @private
     * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
     * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
     * @return {Function} The getter function
     */
    createGetter: function() {
        var me              = this,
            associatedModel = me.getAssociatedModel(),
            foreignKey      = me.getForeignKey(),
            instanceName    = me.getInstanceName();

        //'this' refers to the Model instance inside this function
        return function(options, scope) {
            options = options || {};

            var model = this,
                foreignKeyId = model.get(foreignKey),
                success, instance, args;

            if (options.reload === true || model[instanceName] === undefined) {
                if (typeof options == 'function') {
                    options = {
                        callback: options,
                        scope: scope || model
                    };
                }

                // Overwrite the success handler so we can assign the current instance
                success = options.success;
                options.success = function(rec){
                    model[instanceName] = rec;
                    if (success) {
                        success.apply(this, arguments);
                    }
                };

                associatedModel.load(foreignKeyId, options);
            } else {
                instance = model[instanceName];
                args = [instance];
                scope = scope || model;

                Ext.callback(options, scope, args);
                Ext.callback(options.success, scope, args);
                Ext.callback(options.failure, scope, args);
                Ext.callback(options.callback, scope, args);

                return instance;
            }
        };
    },

    /**
     * Read associated data
     * @private
     * @param {Ext.data.Model} record The record we're writing to
     * @param {Ext.data.reader.Reader} reader The reader for the associated model
     * @param {Object} associationData The raw associated data
     */
    read: function(record, reader, associationData) {
        var inverse = this.getInverseAssociation(),
            newRecord = reader.read([associationData]).getRecords()[0];

        record[this.getSetterName()].call(record, newRecord);

        //if the inverse association was found, set it now on each record we've just created
        if (inverse) {
            newRecord[inverse.getInstanceName()] = record;
        }
    },

    getInverseAssociation: function() {
        var ownerName = this.getOwnerModel().modelName;

        return this.getAssociatedModel().associations.findBy(function(assoc) {
            return assoc.getType().toLowerCase() === 'belongsto' && assoc.getAssociatedModel().modelName === ownerName;
        });
    }
});


/**
 * @private
 */
Ext.define('Ext.mixin.Sortable', {
    extend: 'Ext.mixin.Mixin',

    requires: [
        'Ext.util.Sorter'
    ],

    mixinConfig: {
        id: 'sortable'
    },

    config: {
        /**
         * @cfg {Array} sorters
         * An array with sorters. A sorter can be an instance of {@link Ext.util.Sorter}, a string
         * indicating a property name, an object representing an Ext.util.Sorter configuration,
         * or a sort function.
         */
        sorters: null,

        /**
         * @cfg {String} defaultSortDirection
         * The default sort direction to use if one is not specified.
         */
        defaultSortDirection: "ASC",

        /**
         * @cfg {String} sortRoot
         * The root inside each item in which the properties exist that we want to sort on.
         * This is useful for sorting records in which the data exists inside a `data` property.
         */
        sortRoot: null
    },

    /**
     * @property {Boolean} dirtySortFn
     * A flag indicating whether the currently cashed sort function is still valid.
     * @readonly
     */
    dirtySortFn: false,

    /**
     * @property currentSortFn
     * This is the cached sorting function which is a generated function that calls all the
     * configured sorters in the correct order.
     * @readonly
     */
    sortFn: null,

    /**
     * @property {Boolean} sorted
     * A read-only flag indicating if this object is sorted.
     * @readonly
     */
    sorted: false,

    applySorters: function(sorters, collection) {
        if (!collection) {
            collection = this.createSortersCollection();
        }

        collection.clear();
        this.sorted = false;

        if (sorters) {
            this.addSorters(sorters);
        }

        return collection;
    },

    createSortersCollection: function() {
        this._sorters = Ext.create('Ext.util.Collection', function(sorter) {
            return sorter.getId();
        });
        return this._sorters;
    },

    /**
     * This method adds a sorter.
     * @param {Ext.util.Sorter/String/Function/Object} sorter Can be an instance of
     * Ext.util.Sorter, a string indicating a property name, an object representing an Ext.util.Sorter
     * configuration, or a sort function.
     * @param {String} defaultDirection The default direction for each sorter in the array. Defaults
     * to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
     */
    addSorter: function(sorter, defaultDirection) {
        this.addSorters([sorter], defaultDirection);
    },

    /**
     * This method adds all the sorters in a passed array.
     * @param {Array} sorters An array with sorters. A sorter can be an instance of Ext.util.Sorter, a string
     * indicating a property name, an object representing an Ext.util.Sorter configuration,
     * or a sort function.
     * @param {String} defaultDirection The default direction for each sorter in the array. Defaults
     * to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
     */
    addSorters: function(sorters, defaultDirection) {
        var currentSorters = this.getSorters();
        return this.insertSorters(currentSorters ? currentSorters.length : 0, sorters, defaultDirection);
    },

    /**
     * This method adds a sorter at a given index.
     * @param {Number} index The index at which to insert the sorter.
     * @param {Ext.util.Sorter/String/Function/Object} sorter Can be an instance of Ext.util.Sorter,
     * a string indicating a property name, an object representing an Ext.util.Sorter configuration,
     * or a sort function.
     * @param {String} defaultDirection The default direction for each sorter in the array. Defaults
     * to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
     */
    insertSorter: function(index, sorter, defaultDirection) {
        return this.insertSorters(index, [sorter], defaultDirection);
    },

    /**
     * This method inserts all the sorters in the passed array at the given index.
     * @param {Number} index The index at which to insert the sorters.
     * @param {Array} sorters Can be an instance of Ext.util.Sorter, a string indicating a property name,
     * an object representing an Ext.util.Sorter configuration, or a sort function.
     * @param {String} defaultDirection The default direction for each sorter in the array. Defaults
     * to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
     */
    insertSorters: function(index, sorters, defaultDirection) {
        // We begin by making sure we are dealing with an array of sorters
        if (!Ext.isArray(sorters)) {
            sorters = [sorters];
        }

        var ln = sorters.length,
            direction = defaultDirection || this.getDefaultSortDirection(),
            sortRoot = this.getSortRoot(),
            currentSorters = this.getSorters(),
            newSorters = [],
            sorterConfig, i, sorter, currentSorter;

        if (!currentSorters) {
            // This will guarantee that we get the collection
            currentSorters = this.createSortersCollection();
        }

        // We first have to convert every sorter into a proper Sorter instance
        for (i = 0; i < ln; i++) {
            sorter = sorters[i];
            sorterConfig = {
                direction: direction,
                root: sortRoot
            };

            // If we are dealing with a string we assume it is a property they want to sort on.
            if (typeof sorter === 'string') {
                currentSorter = currentSorters.get(sorter);

                if (!currentSorter) {
                    sorterConfig.property = sorter;
                } else {
                    if (defaultDirection) {
                        currentSorter.setDirection(defaultDirection);
                    } else {
                        // If we already have a sorter for this property we just toggle its direction.
                        currentSorter.toggle();
                    }
                    continue;
                }
            }
            // If it is a function, we assume its a sorting function.
            else if (Ext.isFunction(sorter)) {
                sorterConfig.sorterFn = sorter;
            }
            // If we are dealing with an object, we assume its a Sorter configuration. In this case
            // we create an instance of Sorter passing this configuration.
            else if (Ext.isObject(sorter)) {
                if (!sorter.isSorter) {
                    if (sorter.fn) {
                        sorter.sorterFn = sorter.fn;
                        delete sorter.fn;
                    }

                    sorterConfig = Ext.apply(sorterConfig, sorter);
                }
                else {
                    newSorters.push(sorter);
                    if (!sorter.getRoot()) {
                        sorter.setRoot(sortRoot);
                    }
                    continue;
                }
            }
            // Finally we get to the point where it has to be invalid
            // <debug>
            else {
                Ext.Logger.warn('Invalid sorter specified:', sorter);
            }
            // </debug>

            // If a sorter config was created, make it an instance
            sorter = Ext.create('Ext.util.Sorter', sorterConfig);
            newSorters.push(sorter);
        }

        // Now lets add the newly created sorters.
        for (i = 0, ln = newSorters.length; i < ln; i++) {
            currentSorters.insert(index + i, newSorters[i]);
        }

        this.dirtySortFn = true;

        if (currentSorters.length) {
            this.sorted = true;
        }
        return currentSorters;
    },

    /**
     * This method removes a sorter.
     * @param {Ext.util.Sorter/String/Function/Object} sorter Can be an instance of Ext.util.Sorter,
     * a string indicating a property name, an object representing an Ext.util.Sorter configuration,
     * or a sort function.
     */
    removeSorter: function(sorter) {
        return this.removeSorters([sorter]);
    },

    /**
     * This method removes all the sorters in a passed array.
     * @param {Array} sorters Each value in the array can be a string (property name),
     * function (sorterFn) or {@link Ext.util.Sorter Sorter} instance.
     */
    removeSorters: function(sorters) {
        // We begin by making sure we are dealing with an array of sorters
        if (!Ext.isArray(sorters)) {
            sorters = [sorters];
        }

        var ln = sorters.length,
            currentSorters = this.getSorters(),
            i, sorter;

        for (i = 0; i < ln; i++) {
            sorter = sorters[i];

            if (typeof sorter === 'string') {
                currentSorters.removeAtKey(sorter);
            }
            else if (typeof sorter === 'function') {
                currentSorters.each(function(item) {
                    if (item.getSorterFn() === sorter) {
                        currentSorters.remove(item);
                    }
                });
            }
            else if (sorter.isSorter) {
                currentSorters.remove(sorter);
            }
        }

        if (!currentSorters.length) {
            this.sorted = false;
        }
    },

    /**
     * This updates the cached sortFn based on the current sorters.
     * @return {Function} The generated sort function.
     * @private
     */
    updateSortFn: function() {
        var sorters = this.getSorters().items;

        this.sortFn = function(r1, r2) {
            var ln = sorters.length,
                result, i;

            // We loop over each sorter and check if r1 should be before or after r2
            for (i = 0; i < ln; i++) {
                result = sorters[i].sort.call(this, r1, r2);

                // If the result is -1 or 1 at this point it means that the sort is done.
                // Only if they are equal (0) we continue to see if a next sort function
                // actually might find a winner.
                if (result !== 0) {
                    break;
                }
            }

            return result;
        };

        this.dirtySortFn = false;
        return this.sortFn;
    },

    /**
     * Returns an up to date sort function.
     * @return {Function} The sort function.
     */
    getSortFn: function() {
        if (this.dirtySortFn) {
            return this.updateSortFn();
        }
        return this.sortFn;
    },

    /**
     * This method will sort an array based on the currently configured {@link #sorters}.
     * @param {Array} data The array you want to have sorted.
     * @return {Array} The array you passed after it is sorted.
     */
    sort: function(data) {
        Ext.Array.sort(data, this.getSortFn());
        return data;
    },

    /**
     * This method returns the index that a given item would be inserted into a
     * given array based on the current sorters.
     * @param {Array} items The array that you want to insert the item into.
     * @param {Mixed} item The item that you want to insert into the items array.
     * @param {Function} [sortFn] external sorting function
     * @param {Boolean} containsItem Sencha's implementation assumes the item have to be excluded from the array before searching.
     *      OT implementation allow avoid this kind of restriction. `true` assumes the item can be existed in the `items` array.
     * @return {Number} The index for the given item in the given array based on
     * the current sorters.
     */
    findInsertionIndex: function(items, item, sortFn, containsItem) {
        var start = 0,
            end   = items.length - 1,
            sorterFn = sortFn || this.getSortFn(),
            middle,
            comparison;

        while (start < end || start === end && !containsItem) {
            middle = (start + end) >> 1;

            var middleItem = items[middle];

            if (middleItem === item) {
                start = middle;
                break;
            }
            comparison = sorterFn(item, middleItem);
            if (comparison > 0 || (!containsItem && comparison === 0)) {
                start = middle + 1;
            } else if (comparison < 0) {
                end = middle - 1;
            } else if (containsItem && (start !== end)) {
                start = middle + 1;
            }
        }

        return start;
    }
});


/**
 * @class kad-telemetry/metrics/reliability
 */

'use strict'

var Message = require('../../kad/index.js').Message
var inherits = require('util').inherits
var Metric = require('../metric')

/**
 * Defines a metric that measures reliability by success/error responses
 * @constructor
 */
function ReliabilityMetric () {
  if (!(this instanceof ReliabilityMetric)) {
    return new ReliabilityMetric()
  }

  Metric.call(this)

  this.key = 'reliability'
  this.default = [0, 0] // [success,error]
  this.hooks = [{
    trigger: 'before',
    event: 'receive',
    handler: this._recordResponseType
  }]
}

inherits(ReliabilityMetric, Metric)

/**
 * Begins the reliability test
 * #_recordResponseType
 * @param {telemetry.metrics.LatencyMetric} self
 * @param {telemetry.Persistence} profiles
 * @returns {Function}
 */
ReliabilityMetric.prototype._recordResponseType = function (self, profiles) {
  return function (message, contact, next) {
    var cb = function (err, profile) {
      var metric = self.getMetric(profile)

      if (Message.isRequest(message)) {
        return next()
      }

      metric[message.error ? 1 : 0]++

      self.setMetric(profile, metric)
      profiles.setProfile(contact, profile, function () {
        next()
      })
    }
    profiles.getProfile(contact, cb)
  }
}

/**
 * Returns the score for a given metric value
 * #score
 * @param {Array} value
 * @returns {Number}
 */
ReliabilityMetric.score = function (value) {
  return value[0] / (value[0] + value[1])
}

module.exports = ReliabilityMetric


/**
*
*	COMPUTE: cmin
*
*
*	DESCRIPTION:
*		- Computes the cumulative minimum of a numeric array.
*
*
*	NOTES:
*		[1]
*
*
*	TODO:
*		[1]
*
*
*	LICENSE:
*		MIT
*
*	Copyright (c) 2014. Athan Reines.
*
*
*	AUTHOR:
*		Athan Reines. kgryte@gmail.com. 2014.
*
*/

'use strict';

/**
* FUNCTION: cmin( arr )
*	Computes the cumulative minimum of a numeric array.
*
* @param {Array} arr - numeric array
* @returns {Array} cumulative min
*/
function cmin( arr ) {
	if ( !Array.isArray( arr ) ) {
		throw new TypeError( 'cmin()::invalid input argument. Must provide an array.' );
	}
	var len = arr.length,
		v = new Array( len ),
		min;

	min = arr[ 0 ];
	v[ 0 ] = min;
	for ( var i = 1; i < len; i++ ) {
		if ( arr[ i ] < min ) {
			min = arr[ i ];
		}
		v[ i ] = min;
	}
	return v;
} // end FUNCTION cmin()

// EXPORTS //

module.exports = cmin;


"use strict";

var base = require('./base');
var code = require('./code');

function getTableName(str) {
    return str.toLowerCase();
}

// callback(isok, err)
function exportTable(obj, callback, root) {
    if (obj.type == 'struct') {
        var isautoinc = false;
        var autoincval = 1;
        var str = "-- " + obj.comment + '\r\n';
        str += "CREATE TABLE IF NOT EXISTS `" + getTableName(obj.name) + "` (" + '\r\n';

        var lastcomment = '';

        var lstmember = [];
        base.forEachStruct(obj.name, obj, root, function (structname, memberobj, root) {
            lstmember.push(memberobj);
        });

        var validindex = [];
        for (var i = 0; i < lstmember.length; ++i) {
            var membername = lstmember[i].name.name;
            // type
            var t = base.getRealType_Enum(lstmember[i].type, root);
            if (t == '[ERR]') {
                callback(false, 'struct ' + obj.name + '.' + membername + ': type is Error!');

                return ;
            }
            else if (t == 'int') {
                validindex.push(i);
            }
            else if (t == 'string') {
                validindex.push(i);
            }
            else if (t == 'time') {
                validindex.push(i);
            }
            else if (base.isStruct(t, root)) {

            }
            else {
                callback(false, 'struct ' + obj.name + '.' + membername + ': type not defined!');

                return ;
            }
        }

        var keyblocknums = 0;

        // PRIMARY & INDEX
        for (var i = 0; i < lstmember.length; ++i) {
            if (lstmember[i].hasOwnProperty('type2')) {
                var membername = lstmember[i].name.name;
                var fn = base.getMemberName(membername);

                if (lstmember[i].type2 == 'primary') {
                    keyblocknums++;
                }
                else if (lstmember[i].type2 == 'primary0' || lstmember[i].type2 == 'primary1' || lstmember[i].type2 == 'index') {
                    keyblocknums++;
                }
                if (lstmember[i].type2 == 'unique') {
                    keyblocknums++;
                }
            }
        }

        var tarr = [[], []];
        for (var k = 0; k < validindex.length; ++k) {
            var i = validindex[k];
            var membername = lstmember[i].name.name;
            var fn = base.getMemberName(membername);

            // name
            var cstr = "`" + fn + "` ";

            // type
            var t = base.getRealType_Enum(lstmember[i].type, root);
            if (t == '[ERR]') {
                callback(false, 'struct ' + obj.name + '.' + membername + ': type is Error!');

                return ;
            }
            else if (t == 'int') {
                cstr += "int ";
            }
            else if (t == 'string') {
                cstr += "varchar ";
            }
            else if (t == 'time') {
                cstr += "timestamp ";
            }
            else {
                callback(false, 'struct ' + obj.name + '.' + membername + ': type not defined!');

                return ;
            }

            // NULL
            if (typeof(lstmember[i].val) == 'object' && lstmember[i].val.type == 'NULL') {
                cstr += 'NULL';
            }
            else {
                cstr += 'NOT NULL';
            }

            // AUTO_INCREMENT
            if (typeof(lstmember[i].val) == 'object' && lstmember[i].val.val == 'AUTOINC') {
                cstr += ' AUTO_INCREMENT';
                isautoinc = true;

                if (lstmember[i].val.hasOwnProperty('autoinc')) {
                    autoincval = lstmember[i].val.autoinc;
                }
            }
            // DEFAULT CURRENT_TIMESTAMP
            else if (typeof(lstmember[i].val) == 'object' && lstmember[i].val.val == 'NOW') {
                cstr += ' DEFAULT CURRENT_TIMESTAMP';
            }

            if (k < validindex.length - 1 || keyblocknums > 0) {
                cstr += ',';
            }

            tarr[0].push(cstr);
            tarr[1].push('-- ' + lstmember[i].comment);
        }

        str += code.alignCode(tarr, '  ');

        // PRIMARY & INDEX
        if (keyblocknums > 0) {
            for (var i = 0; i < lstmember.length; ++i) {
                if (lstmember[i].hasOwnProperty('type2')) {
                    var membername = lstmember[i].name.name;
                    var fn = base.getMemberName(membername);
                    var newline = false;

                    if (lstmember[i].type2 == 'primary') {
                        str += "  PRIMARY KEY (`" + fn + "`)";
                        newline = true;
                    }
                    else if (lstmember[i].type2 == 'primary0' || lstmember[i].type2 == 'primary1' || lstmember[i].type2 == 'index') {
                        str += "  KEY (`" + fn + "`)";
                        newline = true;
                    }
                    if (lstmember[i].type2 == 'unique') {
                        str += "  UNIQUE (`" + fn + "`)";
                        newline = true;
                    }

                    if (newline) {
                        keyblocknums--;

                        if (keyblocknums > 0) {
                            str += ',\r\n';
                        }
                        else {
                            str += '\r\n';
                        }
                    }
                }
            }
        }

        str += ") ENGINE=InnoDB DEFAULT CHARSET=utf8";

        if (isautoinc) {
            str += ' AUTO_INCREMENT=' + autoincval;
        }

        str += ';\r\n';

        return str;
    }
}

// callback(isok, err)
function exportSql(obj, callback) {
    var str = '';
    if (Array.isArray(obj)) {
        for (var i = 0; i < obj.length; ++i) {
            if (obj[i].type == 'struct' && base.isExportTypeString(obj[i].name)) {
                var cs = exportTable(obj[i], callback, obj);
                if (cs == undefined) {
                    return ;
                }

                str += cs + '\r\n';
            }
        }

        return str;
    }

    return ;
}

exports.exportSql = exportSql;