/**
 * This class manages arbitrary data and its relationship to data models. Instances of
 * `ViewModel` are associated with some `Component` and then used by their child items
 * for the purposes of Data Binding.
 * 
 * # Binding
 * 
 * The most commonly used aspect of a `ViewModel` is the `bind` method. This method takes
 * a "bind descriptor" (see below) and a callback to call when the data indicated by the
 * bind descriptor either becomes available or changes.
 *
 * The `bind` method, based on the bind descriptor given, will return different types of
 * "binding" objects. These objects maintain the connection between the requested data and
 * the callback. Bindings ultimately derive from `{@link Ext.app.bind.BaseBinding}`
 * which provides several methods to help manage the binding.
 *
 * Perhaps the most important method is `destroy`. When the binding is no longer needed
 * it is important to remember to `destroy` it. Leaking bindings can cause performance
 * problems or worse when callbacks are called at unexpected times.
 *
 * The types of bindings produced by `bind` are:
 *
 *   * `{@link Ext.app.bind.Binding}`
 *   * `{@link Ext.app.bind.Multi}`
 *   * `{@link Ext.app.bind.TemplateBinding}`
 *
 * ## Bind Descriptors
 * 
 * A "bind descriptor" is a value (a String, an Object or an array of these) that describe
 * the desired data. Any piece of data in the `ViewModel` can be described by a bind
 * descriptor.
 * 
 * ### Textual Bind Descriptors
 * 
 * The simplest and most common form of bind descriptors are strings that look like an
 * `Ext.Template` containing text and tokens surrounded by "{}" with dot notation inside
 * to traverse objects and their properties.
 * 
 * For example:
 * 
 *   * `'Hello {user.name}!'`
 *   * `'You have selected "{selectedItem.text}".'`
 *   * `'{user.groups}'`
 *
 * The first two bindings are `{@link Ext.app.bind.TemplateBinding template bindings}`
 * which use the familiar `Ext.Template` syntax with some slight differences. For more on
 * templates see `{@link Ext.app.bind.Template}`.
 *
 * The third bind descriptor is called a "direct bind descriptor". This special form of
 * bind maps one-to-one to some piece of data in the `ViewModel` and is managed by the
 * `{@link Ext.app.bind.Binding}` class.
 *
 * #### Two-Way Descriptors
 *
 * A direct bind descriptor may be able to write back a value to the `ViewModel` as well
 * as retrieve one. When this is the case, they are said to be "two-way". For example:
 *
 *      var binding = viewModel.bind('{s}', function(x) { console.log('s=' + s); });
 *
 *      binding.setValue('abc');
 *
 * Direct use of `ViewModel` in this way is not commonly needed because `Ext.Component`
 * automates this process. For example, a `textfield` component understands when it is
 * given a "two-way" binding and automatically synchronizes its value bidirectionally using
 * the above technique. For example:
 *
 *      Ext.widget({
 *          items: [{
 *              xtype: 'textfield',
 *              bind: '{s}'  // a two-way / direct bind descriptor
 *          }]
 *      });
 *
 * ### Object and Array Descriptors / Multi-Bind
 *
 * With two exceptions (see below) an Object is interpreted as a "shape" to produce by
 * treating each of its properties as individual bind descriptors. An object of the same
 * shape is passed as the value of the bind except that each property is populated with
 * the appropriate value. Of course, this definition is recursive, so these properties
 * may also be objects.
 *
 * For example:
 *
 *      viewModel.bind({
 *              x: '{x}',
 *              foo: {
 *                  bar: 'Hello {foo.bar}'
 *              }
 *          },
 *          function (obj) {
 *              //  obj = {
 *              //      x: 42,
 *              //      foo: {
 *              //          bar: 'Hello foobar'
 *              //      }
 *              //  }
 *          });
 *
 * Arrays are handled in the same way. Each element of the array is considered a bind
 * descriptor (recursively) and the value produced for the binding is an array with each
 * element set to the bound property.
 *
 * ### Bind Options
 *
 * One exception to the "object is a multi-bind" rule is when that object contains a
 * `bindTo` property. When an object contains a `bindTo` property the object is understood
 * to contain bind options and the value of `bindTo` is considered the actual bind
 * descriptor.
 *
 * For example:
 *
 *      viewModel.bind({
 *              bindTo: '{x}',
 *              single: true
 *          },
 *          function (x) {
 *              console.log('x: ' + x); // only called once
 *          });
 *
 * The available bind options depend on the type of binding, but since all bindings
 * derive from `{@link Ext.app.bind.BaseBinding}` its options are always applicable.
 * For a list of the other types of bindings, see above.
 *
 * #### Deep Binding
 *
 * When a direct bind is made and the bound property is an object, by default the binding
 * callback is only called when that reference changes. This is the most efficient way to
 * understand a bind of this type, but sometimes you may need to be notified if any of the
 * properties of that object change.
 *
 * To do this, we create a "deep bind":
 *
 *      viewModel.bind({
 *              bindTo: '{someObject}',
 *              deep: true
 *          },
 *          function (someObject) {
 *              // called when reference changes or *any* property changes
 *          });
 *
 * #### Binding Timings
 *
 * The `ViewModel` has a {@link #scheduler} attached that is used to coordinate the firing of bindings.
 * It serves 2 main purposes:
 * - To coordinate dependencies between bindings. This means bindings will be fired in an order such that
 * the any dependencies for a binding are fired before the binding itself.
 * - To batch binding firings. The scheduler runs on a short timer, so the following code will only trigger
 * a single binding (the last), the changes in between will never be triggered.
 * 
 *     viewModel.bind('{val}', function(v) {
 *         console.log(v);
 *     });
 *     viewModel.set('val', 1);
 *     viewModel.set('val', 2);
 *     viewModel.set('val', 3);
 *     viewModel.set('val', 4);
 *
 * The `ViewModel` can be forced to process by calling `{@link #notify}`, which will force the
 * scheduler to run immediately in the current state.
 * 
 *     viewModel.bind('{val}', function(v) {
 *         console.log(v);
 *     });
 *     viewModel.set('val', 1);
 *     viewModel.notify();
 *     viewModel.set('val', 2);
 *     viewModel.notify();
 *     viewModel.set('val', 3);
 *     viewModel.notify();
 *     viewModel.set('val', 4);
 *     viewModel.notify();
 *  
 *
 * #### Models, Stores and Associations
 *
 * A {@link Ext.data.Session Session} manages model instances and their associations.
 * The `ViewModel` may be used with or without a `Session`. When a `Session` is attached, the
 * `ViewModel` will always consult the `Session` to ask about records and stores. The `Session`
 * ensures that only a single instance of each model Type/Id combination is created. This is 
 * important when tracking changes in models so that we always have the same reference.
 *
 * A `ViewModel` provides functionality to easily consume the built in data package types
 * {@link Ext.data.Model} and {@link Ext.data.Store}, as well as their associations.
 *
 * ### Model Links
 *
 * A model can be described declaratively using a {@link #links `link`}. In the example code below,
 * We ask the `ViewModel` to construct a record of type `User` with `id: 17`. The model will be loaded
 * from the server and the bindings will trigger once the load has completed. Similarly, we could also
 * attach a model instance to the `ViewModel` data directly.
 *
 *     Ext.define('MyApp.model.User', {
 *         extend: 'Ext.data.Model',
 *         fields: ['name']
 *     });
 *     
 *     var rec = new MyApp.model.User({
 *         id: 12,
 *         name: 'Foo'
 *     });
 *     
 *     var viewModel = new Ext.app.ViewModel({
 *         links: {
 *             theUser: {
 *                 type: 'User',
 *                 id: 17
 *             }
 *         },
 *         data: {
 *             otherUser: rec
 *         }
 *     });
 *     viewModel.bind('{theUser.name}', function(v) {
 *         console.log(v);
 *     });
 *     viewModel.bind('{otherUser.name}', function(v) {
 *         console.log(v);
 *     });
 *
 * ### Model Fields
 *
 * Bindings have the functionality to inspect the parent values and resolve the underlying
 * value dynamically. This behavior allows model fields to be interrogated as part of a binding.
 *
 *     Ext.define('MyApp.model.User', {
 *         extend: 'Ext.data.Model',
 *         fields: ['name', 'age']
 *     });
 *
 *     var viewModel = new Ext.app.ViewModel({
 *         links: {
 *             theUser: {
 *                 type: 'User',
 *                 type: 22
 *             }
 *         }
 *     });
 *
 *     // Server responds with:
 *     {
 *         "id": 22,
 *         "name": "Foo",
 *         "age": 100
 *     }
 *
 *     viewModel.bind('Hello {name}, you are {age} years old', function(v) {
 *         console.log(v);
 *     });
 *
 * ### Associations
 *
 * In the same way as fields, the bindings can also traverse associations in a bind statement.
 * The `ViewModel` will handle the asynchronous loading of data and only present the value once
 * the full path has been loaded. For more information on associations see {@link Ext.data.schema.OneToOne OneToOne} and
 * {@link Ext.data.schema.ManyToOne ManyToOne} associations.
 *
 *     Ext.define('User', {
 *         extend: 'Ext.data.Model',
 *         fields: ['name']
 *     });
 *
 *     Ext.define('Order', {
 *         extend: 'Ext.data.Model',
 *         fields: ['date', {
 *             name: 'userId',
 *             reference: 'User'
 *         }]
 *     });
 *
 *     Ext.define('OrderItem', {
 *         extend: 'Ext.data.Model',
 *         fields: ['price', 'qty', {
 *             name: 'orderId',
 *             reference: 'Order'
 *         }]
 *     });
 *
 *     var viewModel = new Ext.app.ViewModel({
 *         links: {
 *             orderItem: {
 *                 type: 'OrderItem',
 *                 id: 13
 *             }
 *         }
 *     });
 *     // The viewmodel will handle both ways of loading the data:
 *     // a) If the data is loaded inline in a nested fashion it will
 *     //    not make requests for extra data
 *     // b) Only loading a single model at a time. So the Order will be loaded once
 *     //    the OrderItem returns. The User will be loaded once the Order loads.
 *     viewModel.bind('{orderItem.order.user.name}', function(name) {
 *         console.log(name);
 *     });
 *
 * ### Stores
 *
 * Stores can be created as part of the `ViewModel` definition. The definitions are processed
 * like bindings which allows for very powerful dynamic functionality.
 *
 * It is important to ensure that you name viewModel's data keys uniquely. If data is not named  
 * uniquely, binds and formulas may receive information from an unintended data source.  
 * This applies to keys in the viewModel's data block, stores, and links configs.
 *
 *     var viewModel = new Ext.app.ViewModel({
 *         stores: {
 *             users: {
 *                 model: 'User',
 *                 autoLoad: true,
 *                 filters: [{
 *                     property: 'createdDate',
 *                     value: '{createdFilter}',
 *                     operator: '>'
 *                 }]
 *             }
 *         }
 *     });
 *     // Later on in our code, we set the date so that the store is created.
 *     viewModel.set('createdFilter', Ext.Date.subtract(new Date(), Ext.Date.DAY, 7));
 *
 * See {@link #stores} for more detail.
 *
 * #### Formulas
 *
 * Formulas allow for calculated `ViewModel` data values. The dependencies for these formulas
 * are automatically determined so that the formula will not be processed until the required
 * data is present.
 *
 *    var viewModel = new Ext.app.ViewModel({
 *        formulas: {
 *            fullName: function(get) {
 *                return get('firstName') + ' ' + get('lastName');
 *            }
 *        },
 *        data: {firstName: 'John', lastName: 'Smith'}
 *    });
 *
 *    viewModel.bind('{fullName}', function(v) {
 *        console.log(v);
 *    });
 *
 * See {@link #formulas} for more detail.
 */
Ext.define('Ext.app.ViewModel', {
    mixins: [
        'Ext.mixin.Factoryable',
        'Ext.mixin.Identifiable'
    ],

    requires: [
        'Ext.util.Scheduler',
        'Ext.data.Session',
        'Ext.app.bind.RootStub',
        'Ext.app.bind.LinkStub',
        'Ext.app.bind.Multi',
        'Ext.app.bind.Formula',
        'Ext.app.bind.TemplateBinding',
        // TODO: this is an injected dependency in onStoreBind, need to define so 
        // cmd can detect it
        'Ext.data.ChainedStore'
    ],

    alias: 'viewmodel.default', // also configures Factoryable

    isViewModel: true,

    factoryConfig: {
        name: 'viewModel'
    },

    collectTimeout: 100,

    expressionRe: /^(?:\{[!]?(?:(\d+)|([a-z_][\w\-\.]*))\})$/i,

    $configStrict: false, // allow "formulas" to be specified on derived class body
    config: {
        /**
         * @cfg {Object} data
         * This object holds the arbitrary data that populates the `ViewModel` and is
         * then available for binding.
         * @since 5.0.0
         */
        data: true,

        /**
         * @cfg {Object} formulas
         * An object that defines named values whose value is managed by function calls.
         * The names of the properties of this object are assigned as values in the
         * ViewModel.
         *
         * For example:
         *
         *      formulas: {
         *          xy: function (get) { return get('x') * get('y'); }
         *      }
         *
         * For more details about defining a formula, see `{@link Ext.app.bind.Formula}`.
         * @since 5.0.0
         */
        formulas: {
            $value: null,
            merge: function (newValue, currentValue, target, mixinClass) {
                return this.mergeNew(newValue, currentValue, target, mixinClass);
            }
        },

        /**
         * @cfg {Object} links
         * Links provide a way to assign a simple name to a more complex bind. The primary
         * use for this is to assign names to records in the data model.
         *
         *      links: {
         *          theUser: {
         *              type: 'User',
         *              id: 12
         *          }
         *      }
         *
         * It is also possible to force a new phantom record to be created by not specifying an
         * id but passing `create: true` as part of the descriptor. This is often useful when
         * creating a new record for a child session.
         *
         *     links: {
         *         newUser: {
         *             type: 'User',
         *             create: true
         *         }
         *     } 
         *
         * `create` can also be an object containing initial data for the record.
         *
         *     links: {
         *         newUser: {
         *             type: 'User',
         *             create: {
         *                 firstName: 'John',
         *                 lastName: 'Smith'
         *             }
         *         }
         *     } 
         *
         * While that is the typical use, the value of each property in `links` may also be
         * a bind descriptor (see `{@link #method-bind}` for the various forms of bind
         * descriptors).
         * @since 5.0.0
         */
        links: null,

        /**
         * @cfg {Ext.app.ViewModel} parent
         * The parent `ViewModel` of this `ViewModel`. Once set, this cannot be changed.
         * @readonly
         * @since 5.0.0
         */
        parent: null,

        /**
         * @cfg {Ext.app.bind.RootStub} root
         * A reference to the root "stub" (an object that manages bindings).
         * @private
         * @since 5.0.0
         */
        root: true,

        /**
         * @cfg {Ext.util.Scheduler} scheduler
         * The scheduler used to schedule and manage the delivery of notifications for
         * all connections to this `ViewModel` and any other attached to it. The normal
         * process to initialize the `scheduler` is to get the scheduler used by the
         * `parent` or `session` and failing either of those, create one.
         * @readonly
         * @private
         * @since 5.0.0
         */
        scheduler: null,

        /**
         * @cfg {String/Ext.data.schema.Schema} schema
         * The schema to use for getting information about entities.
         */
        schema: 'default',

        /**
         * @cfg {Ext.data.Session} session
         * The session used to manage the data model (records and stores).
         * @since 5.0.0
         */
        session: null,

        // @cmd-auto-dependency {isKeyedObject: true, aliasPrefix: "store.", defaultType: "store"}
        /**
         * @cfg {Object} stores
         * A declaration of `Ext.data.Store` configurations that are first processed as
         * binds to produce an effective store configuration.
         *
         * A simple store definition. We can reference this in our bind statements using the
         * `{users}` as we would with other data values.
         *
         *     new Ext.app.ViewModel({
         *         stores: {
         *             users: {
         *                 model: 'User',
         *                 autoLoad: true
         *             }
         *         }
         *     });
         *
         * This store definition contains a dynamic binding. The store will not be created until
         * the initial value for groupId is set. Once that occurs, the store is created with the appropriate
         * filter configuration. Subsequently, once we change the group value, the old filter will be
         * overwritten with the new value.
         *
         *     var viewModel = new Ext.app.ViewModel({
         *         stores: {
         *             users: {
         *                 model: 'User',
         *                 filters: [{
         *                     property: 'groupId',
         *                     value: '{groupId}'
         *                 }]
         *             }
         *         }
         *     });
         *     viewModel.set('groupId', 1); // This will trigger the store creation with the filter.
         *     viewModel.set('groupId', 2); // The filter value will be changed.
         *
         * This store uses {@link Ext.data.ChainedStore store chaining} to create a store backed by the
         * data in another store. By specifying a string as the store, it will bind our creation and backing
         * to the other store. This functionality is especially useful when wanting to display a different "view"
         * of a store, for example a different sort order or different filters.
         *
         *     var viewModel = new Ext.app.ViewModel({
         *         stores: {
         *             allUsers: {
         *                 model: 'User',
         *                 autoLoad: true
         *             },
         *             children: {
         *                 source: '{allUsers}',
         *                 filters: [{
         *                     property: 'age',
         *                     value: 18,
         *                     operator: '<'
         *                 }]
         *             }
         *         }
         *     });
         *
         * @since 5.0.0
         */
        stores: null,

        /**
         * @cfg {Ext.container.Container} view
         * The Container that owns this `ViewModel` instance.
         * @since 5.0.0
         */
        view: null
    },

    constructor: function (config) {
        this.hadValue = {};
        // Used to track non-stub bindings
        this.bindings = {};
        /*
         *  me.data = {
         *      foo: {
         *      },
         *          
         *      selectedUser: {
         *          name: null
         *      },
         *  }
         *
         *  me.root = new Ext.app.bind.RootStub({
         *      children: {
         *          foo: new Ext.app.bind.Stub(),
         *          selectedUser: new Ext.app.bind.LinkStub({
         *              binding: session.bind(...),
         *              children: {
         *                  name: : new Ext.app.bind.Stub()
         *              }
         *          }),
         *      }
         *  })
         */

        this.initConfig(config);
    },

    destroy: function () {
        var me = this,
            scheduler = me._scheduler,
            stores = me.storeInfo,
            parent = me.getParent(),
            task = me.collectTask,
            children = me.children,
            bindings = me.bindings,
            key, store, autoDestroy;

        me.destroying = true;
        if (task) {
            task.cancel();
            me.collectTask = null;
        }

        // When used with components, they are destroyed bottom up
        // so this scenario is only likely to happen in the case where
        // we're using the VM without any component attachment, in which case
        // we need to clean up here.
        if (children) {
            for (key in children) {
                children[key].destroy();
            }
        }

        if (stores) {
            for (key in stores) {
                store = stores[key];
                autoDestroy = store.autoDestroy;
                if (autoDestroy || (!store.$wasInstance && autoDestroy !== false)) {
                    store.destroy();
                }
                Ext.destroy(store.$binding);
            }
        }

        if (parent) {
            parent.unregisterChild(me);
        }

        
        me.getRoot().destroy();

        for (key in bindings) {
            bindings[key].destroy();
        }

        if (scheduler && scheduler.$owner === me) {
            scheduler.$owner = null;
            scheduler.destroy();
        }

        me.hadValue = me.children = me.storeInfo = me._session = me._view = me._scheduler =
                      me.bindings = me._root = me._parent = me.formulaFn = me.$formulaData = null;

        me.destroying = false;
        me.callParent();
    },

    /**
     * This method requests that data in this `ViewModel` be delivered to the specified
     * `callback`. The data desired is given in a "bind descriptor" which is the first
     * argument.
     *
     * A simple call might look like this:
     *
     *     var binding = vm.bind('{foo}', this.onFoo, this);
     * 
     *     binding.destroy();  // when done with the binding
     *
     * Options for the binding can be provided in the last argument:
     *
     *     var binding = vm.bind('{foo}', this.onFoo, this, {
     *         deep: true
     *     });
     * 
     * Alternatively, bind options can be combined with the bind descriptor using only
     * the first argument:
     *
     *     var binding = vm.bind({
     *         bindTo: '{foo}',  // the presence of bindTo identifies this form
     *         deep: true
     *     }, this.onFoo, this);
     * 
     * See the class documentation for more details on Bind Descriptors and options.
     *
     * @param {String/Object/Array} descriptor The bind descriptor. See class description
     * for details.
     * @param {Function} callback The function to call with the value of the bound property.
     * @param {Object} [scope] The scope (`this` pointer) for the `callback`.
     * @param {Object} [options] Additional options to configure the {@link Ext.app.bind.Binding binding}.
     * If this parameter is provided, the `bindTo` form of combining options and bind descriptor is not
     * recognized.
     * @return {Ext.app.bind.BaseBinding/Ext.app.bind.Binding} The binding.
     */
    bind: function (descriptor, callback, scope, options) {
        var me = this,
            binding, track;

        scope = scope || me;

        if (!options && descriptor.bindTo !== undefined && !Ext.isString(descriptor)) {
            options = descriptor;
            descriptor = options.bindTo;
        }

        if (!Ext.isString(descriptor)) {
            binding = new Ext.app.bind.Multi(descriptor, me, callback, scope, options);
            track = true;
        } else if (me.expressionRe.test(descriptor)) {
            // If we have '{foo}' alone it is a literal
            descriptor = descriptor.substring(1, descriptor.length - 1);
            binding = me.bindExpression(descriptor, callback, scope, options);
        } else {
            binding = new Ext.app.bind.TemplateBinding(descriptor, me, callback, scope, options);
            track = true;
        }

        if (track) {
            me.bindings[binding.id] = binding;
        }

        return binding;
    },

    /**
     * Gets the session attached to this (or a parent) ViewModel. See the {@link #session} configuration.
     * @return {Ext.data.Session} The session. `null` if no session exists.
     */
    getSession: function () {
        var me = this,
            session = me._session,
            parent;

        if (!session && (parent = me.getParent())) {
            me.setSession(session = parent.getSession());
        }

        return session || null;
    },
    
    /**
     * Gets a store configured via the {@link #stores} configuration.
     * @param {String} key The name of the store.
     * @return {Ext.data.Store} The store. `null` if no store exists.
     */
    getStore: function(key) {
        var storeInfo = this.storeInfo,
            store;
        
        if (storeInfo) {
            store = storeInfo[key];
        }
        return store || null;
    },
    
    /**
     * @method getStores
     * @hide
     */

    /**
     * Create a link to a reference. See the {@link #links} configuration.
     * @param {String} key The name for the link.
     * @param {Object} reference The reference descriptor.
     */
    linkTo: function (key, reference) {
        var me = this,
            stub = me.getStub(key),
            create, id, modelType, linkStub, rec;

        //<debug>
        if (stub.depth - me.getRoot().depth > 1) {
            Ext.raise('Links can only be at the top-level: "' + key + '"');
        }
        //</debug>

        if (reference.isModel) {
            reference = {
                type: reference.entityName,
                id: reference.id
            };
        }
        // reference is backwards compat, type is preferred.
        modelType = reference.type || reference.reference;
        create = reference.create;
        if (modelType) {
            // It's a record
            id = reference.id;
            //<debug>
            if (!reference.create && Ext.isEmpty(id)) {
                Ext.raise('No id specified. To create a phantom model, specify "create: true" as part of the reference.');
            }
            //</debug>
            if (create) {
                id = undefined;
            }
            rec = me.getRecord(modelType, id);
            if (Ext.isObject(create)) {
                rec.set(create);
                rec.commit();
                rec.phantom = true;
            }
            stub.set(rec);
        } else {
            if (!stub.isLinkStub) {
                // Pass parent=null since we will graft in this new stub to replace us:
                linkStub = new Ext.app.bind.LinkStub(me, stub.name);
                stub.graft(linkStub);
                stub = linkStub;
            }
            stub.link(reference);
        }
    },

    /**
     * Forces all bindings in this ViewModel hierarchy to evaluate immediately. Use this to do a synchronous flush
     * of all bindings.
     */
    notify: function () {
        this.getScheduler().notify();
    },

    /**
     * Get a value from the data for this viewmodel.
     * @param {String} path The path of the data to retrieve.
     *
     *    var value = vm.get('theUser.address.city');
     *
     * @return {Object} The data stored at the passed path.
     */
    get: function(path) {
        return this.getStub(path).getValue();
    },

    /**
     * Set  a value in the data for this viewmodel.
     * @param {Object/String} path The path of the value to set, or an object literal to set
     * at the root of the viewmodel.
     * @param {Object} value The data to set at the value. If the value is an object literal,
     * any required paths will be created.
     *
     *     // Set a single property at the root level
     *     viewModel.set('expiry', Ext.Date.add(new Date(), Ext.Date.DAY, 7));
     *     console.log(viewModel.get('expiry'));
     *     // Sets a single property in user.address, does not overwrite any hierarchy.
     *     viewModel.set('user.address.city', 'London');
     *     console.log(viewModel.get('user.address.city'));
     *     // Sets 2 properties of "user". Overwrites any existing hierarchy.
     *     viewModel.set('user', {firstName: 'Foo', lastName: 'Bar'});
     *     console.log(viewModel.get('user.firstName'));
     *     // Sets a single property at the root level. Overwrites any existing hierarchy.
     *     viewModel.set({rootKey: 1});
     *     console.log(viewModel.get('rootKey'));
     */
    set: function (path, value) {
        var me = this,
            obj, stub;

        // Force data creation
        me.getData();

        if (value === undefined && path && path.constructor === Object) {
            stub = me.getRoot();
            value = path;
        } else if (path && path.indexOf('.') < 0) {
            obj = {};
            obj[path] = value;
            value = obj;
            stub = me.getRoot();
        } else {
            stub = me.getStub(path);
        }

        stub.set(value);
    },

    //=========================================================================
    privates: {
        registerChild: function(child) {
            var children = this.children;
            if (!children) {
                this.children = children = {};
            }
            children[child.getId()] = child;
        },
        
        unregisterChild: function(child) {
            var children = this.children;
            // If we're destroying we'll be wiping this collection shortly, so
            // just ignore it here
            if (!this.destroying && children) {
                delete children[child.getId()];
            }
        },

        /**
         * Get a record instance given a reference descriptor. Will ask
         * the session if one exists.
         * @param {String/Ext.Class} type The model type.
         * @param {Object} id The model id.
         * @return {Ext.data.Model} The model instance.
         * @private
         */
         getRecord: function(type, id) {
            var session = this.getSession(),
                Model = type,
                hasId = id !== undefined,
                record;

            if (session) {
                if (hasId) {
                    record = session.getRecord(type, id);
                } else {
                    record = session.createRecord(type);
                }
            } else {
                if (!Model.$isClass) {
                    Model = this.getSchema().getEntity(Model);
                    //<debug>
                    if (!Model) {
                        Ext.raise('Invalid model name: ' + type);
                    }
                    //</debug>
                }
                if (hasId) {
                    record = Model.createWithId(id);
                    record.load();
                } else {
                    record = new Model();
                }
            }
            return record;
        },

        notFn: function (v) {
            return !v;
        },

        bindExpression: function (descriptor, callback, scope, options) {
            var ch = descriptor.charAt(0),
                not = (ch === '!'),
                path = not ? descriptor.substring(1) : descriptor,
                stub = this.getStub(path),
                binding;

            binding =  stub.bind(callback, scope, options);
            if (not) {
                binding.transform = this.notFn;
            }

            return binding;
        },

        applyScheduler: function (scheduler) {
            if (scheduler && !scheduler.isInstance) {
                scheduler = new Ext.util.Scheduler(scheduler);
                scheduler.$owner = this;
            }
            return scheduler;
        },

        getScheduler: function () {
            var me = this,
                scheduler = me._scheduler,
                parent,
                session;

            if (!scheduler) {
                if (!(parent = me.getParent())) {
                    scheduler = new Ext.util.Scheduler({
                        // See Session#scheduler
                        preSort: 'kind,-depth'
                    });
                    scheduler.$owner = me;
                } else {
                    scheduler = parent.getScheduler();
                }

                me.setScheduler(scheduler);
            }

            return scheduler;
        },

        /**
         * This method looks up the `Stub` for a single bind descriptor.
         * @param {String/Object} bindDescr The bind descriptor.
         * @return {Ext.app.bind.AbstractStub} The `Stub` associated to the bind descriptor.
         * @private
         */
        getStub: function (bindDescr) {
            var root = this.getRoot();
            return bindDescr ? root.getChild(bindDescr) : root;
        },

        collect: function() {
            var me = this,
                parent = me.getParent(),
                task = me.collectTask;

            if (parent) {
                parent.collect();
                return;
            }

            if (!task) {
                task = me.collectTask = new Ext.util.DelayedTask(me.doCollect, me);
            }

            // Useful for testing
            if (me.collectTimeout === 0) {
                me.doCollect();
            } else {
                task.delay(me.collectTimeout);
            }
        },

        doCollect: function() {
            var children = this.children,
                key;
            
            // We need to loop over the children first, since they may have link stubs
            // that create bindings inside our VM. Attempt to clean them up first.
            if (children) {
                for (key in children) {
                    children[key].doCollect();
                }
            }
            this.getRoot().collect();
        },

        onBindDestroy: function(binding, fromChild) {
            var me = this,
                parent;

            if (me.destroying) {
                return;
            }

            if (!fromChild) {
                delete me.bindings[binding.id];
            }

            parent = me.getParent();
            if (parent) {
                parent.onBindDestroy(binding, true);
            } else {
                me.collect();
            }
        },

        //-------------------------------------------------------------------------
        // Config
        // <editor-fold>

        applyData: function (newData, data) {
            var me = this,
                linkData, parent;

            // Force any session to be invoked so we can access it
            me.getSession();
            if (!data) {
                parent = me.getParent();

                /**
                 * @property {Object} linkData
                 * This object is used to hold the result of a linked value. This is done
                 * so that the data object hasOwnProperty equates to whether or not this
                 * property is owned by this instance or inherited.
                 * @private
                 * @readonly
                 * @since 5.0.0
                 */
                me.linkData = linkData = parent ? Ext.Object.chain(parent.getData()) : {};

                /**
                 * @property {Object} data
                 * This object holds all of the properties of this `ViewModel`. It is
                 * prototype chained to the `linkData` which is, in turn, prototype chained
                 * to (if present) the `data` object of the parent `ViewModel`.
                 * @private
                 * @readonly
                 * @since 5.0.0
                 */
                me.data = me._data = Ext.Object.chain(linkData);
            }

            if (newData && newData.constructor === Object) {
                me.getRoot().set(newData);
            }
        },

        applyParent: function(parent) {
            if (parent) {
                parent.registerChild(this);
            }
            return parent;
        },
        
        applyStores: function(stores) {
            var me = this,
                root = me.getRoot(),
                key, cfg, storeBind, stub, listeners, isStatic;
            
            me.storeInfo = {};
            me.listenerScopeFn = function() {
                return me.getView().getInheritedConfig('defaultListenerScope');
            };
            for (key in stores) {
                cfg = stores[key];
                if (cfg.isStore) {
                    cfg.$wasInstance = true;
                    me.setupStore(cfg, key);
                    continue;
                } else if (Ext.isString(cfg)) {
                    cfg = {
                        source: cfg
                    };
                } else {
                    cfg = Ext.apply({}, cfg);
                }
                // Get rid of listeners so they don't get considered as a bind
                listeners = cfg.listeners;
                delete cfg.listeners;
                storeBind = me.bind(cfg, me.onStoreBind, me, {trackStatics: true});
                if (storeBind.isStatic()) {
                    // Everything is static, we don't need to wait, so remove the
                    // binding because it will only fire the first time.
                    storeBind.destroy();
                    me.createStore(key, cfg, listeners);
                } else {
                    storeBind.$storeKey = key;
                    storeBind.$listeners = listeners;
                    stub = root.createStubChild(key);
                    stub.setStore(storeBind);
                }
            }
        },
        
        onStoreBind: function(cfg, oldValue, binding) {
            var info = this.storeInfo,
                key = binding.$storeKey,
                store = info[key],
                proxy;

            if (!store) {
                this.createStore(key, cfg, binding.$listeners, binding);
            } else {
                cfg = Ext.merge({}, binding.pruneStaticKeys());
                proxy = cfg.proxy;
                delete cfg.type;
                delete cfg.model;
                delete cfg.fields;
                delete cfg.proxy;
                delete cfg.listeners;
                
                // TODO: possibly optimize this so we can figure out what has changed
                // instead of smashing the whole lot
                if (proxy) {
                    delete proxy.reader;
                    delete proxy.writer;
                    store.getProxy().setConfig(proxy);
                }
                store.blockLoad();
                store.setConfig(cfg);
                store.unblockLoad(true);
            }
        },

        createStore: function(key, cfg, listeners, binding) {
            var session = this.getSession(),
                store;

            cfg = Ext.apply({}, cfg);
            if (cfg.session) {
                cfg.session = session;
            }
            if (cfg.source) {
                cfg.type = cfg.type || 'chained';
            }
            // Restore the listeners from applyStores here
            cfg.listeners = listeners;
            store = Ext.Factory.store(cfg);
            store.$binding = binding;
            this.setupStore(store, key);
        },

        setupStore: function(store, key) {
            store.resolveListenerScope = this.listenerScopeFn;
            this.storeInfo[key] = store;
            this.set(key, store);
        },

        applyFormulas: function (formulas) {
            var me = this,
                root = me.getRoot(),
                name, stub;

            me.getData(); // make sure our data is setup first

            for (name in formulas) {
                //<debug>
                if (name.indexOf('.') >= 0) {
                    Ext.raise('Formula names cannot contain dots: ' + name);
                }
                //</debug>
                
                // Force a stub to be created
                root.createStubChild(name);

                stub = me.getStub(name);
                stub.setFormula(formulas[name]);
            }
            return formulas;
        },

        applyLinks: function (links) {
            for (var link in links) {
                this.linkTo(link, links[link]);
            }
        },

        applySchema: function (schema) {
            return Ext.data.schema.Schema.get(schema);
        },

        applyRoot: function () {
            var root = new Ext.app.bind.RootStub(this),
                parent = this.getParent();

            if (parent) {
                // We are assigning the root of a child VM such that its bindings will be
                // pre-sorted after the bindings of the parent VM.
                root.depth = parent.getRoot().depth - 1000;
            }

            return root;
        },

        getFormulaFn: function(data) {
            var me = this,
                fn = me.formulaFn;

            if (!fn) {
                fn = me.formulaFn = function(name) {
                    // Note that the `this` pointer here is the view model because
                    // the VM calls it in the VM scope.
                    return me.$formulaData[name];
                };
            }
            me.$formulaData = data;
            return fn;
        }

        // </editor-fold>
    }
});
