/**
 * @author Ed Spencer
 * @aside guide models
 *
 * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
 * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
 * express like this:
 *
 *     Ext.define('MyApp.model.User', {
 *         extend: 'Ext.data.Model',
 *
 *         config: {
 *             fields: ['id', 'name', 'email'],
 *             hasMany: {
 *                 model: 'MyApp.model.Order',
 *                 name: 'orders'
 *             }
 *         }
 *     });
 *
 *     Ext.define('MyApp.model.Order', {
 *         extend: 'Ext.data.Model',
 *
 *         config: {
 *             fields: ['id', 'user_id', 'status', 'price'],
 *             belongsTo: 'MyApp.model.User'
 *         }
 *     });
 *
 * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
 * each Model as you need using the two default types - {@link Ext.data.association.HasMany hasMany} and
 * {@link Ext.data.association.BelongsTo belongsTo}. There's much more detail on the usage of each of those inside their
 * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
 *
 * ## Further Reading
 *
 * <ul style="list-style-type: disc; padding-left: 20px;">
 *   <li>{@link Ext.data.association.HasMany hasMany associations}</li>
 *   <li>{@link Ext.data.association.BelongsTo belongsTo associations}</li>
 *   <li>{@link Ext.data.association.HasOne hasOne associations}</li>
 *   <li>{@link Ext.data.Model using Models}</li>
 * </ul>
 *
 * ### Self-associating Models
 *
 * We can also have models that create parent/child associations between the same type. Below is an example, where
 * groups can be nested inside other groups:
 *
 *     // Server Data
 *     {
 *         "groups": {
 *             "id": 10,
 *             "parent_id": 100,
 *             "name": "Main Group",
 *             "parent_group": {
 *                 "id": 100,
 *                 "parent_id": null,
 *                 "name": "Parent Group"
 *             },
 *             "child_groups": [{
 *                 "id": 2,
 *                 "parent_id": 10,
 *                 "name": "Child Group 1"
 *             },{
 *                 "id": 3,
 *                 "parent_id": 10,
 *                 "name": "Child Group 2"
 *             },{
 *                 "id": 4,
 *                 "parent_id": 10,
 *                 "name": "Child Group 3"
 *             }]
 *         }
 *     }
 *
 *     // Client code
 *     Ext.define('MyApp.model.Group', {
 *         extend: 'Ext.data.Model',
 *         config: {
 *             fields: ['id', 'parent_id', 'name'],
 *             proxy: {
 *                 type: 'ajax',
 *                 url: 'data.json',
 *                 reader: {
 *                     type: 'json',
 *                     root: 'groups'
 *                 }
 *             },
 *             associations: [{
 *                 type: 'hasMany',
 *                 model: 'MyApp.model.Group',
 *                 primaryKey: 'id',
 *                 foreignKey: 'parent_id',
 *                 autoLoad: true,
 *                 associationKey: 'child_groups' // read child data from child_groups
 *             }, {
 *                 type: 'belongsTo',
 *                 model: 'MyApp.model.Group',
 *                 primaryKey: 'id',
 *                 foreignKey: 'parent_id',
 *                 associationKey: 'parent_group' // read parent data from parent_group
 *             }]
 *         }
 *     });
 *
 *
 *     Ext.onReady(function(){
 *         MyApp.model.Group.load(10, {
 *             success: function(group){
 *                 console.log(group.getGroup().get('name'));
 *
 *                 group.groups().each(function(rec){
 *                     console.log(rec.get('name'));
 *                 });
 *             }
 *         });
 *
 *     });
 */
Ext.define('Ext.data.association.Association', {
    alternateClassName: 'Ext.data.Association',

    requires: ['Ext.data.ModelManager'],

    config: {
        /**
         * @cfg {Ext.data.Model/String} ownerModel The full class name or reference to the class that owns this
         * associations. This is a required configuration on every association.
         * @accessor
         */
        ownerModel: null,

        /*
         * @cfg {String} ownerName The name for the owner model. This defaults to the last part
         * of the class name of the {@link #ownerModel}.
         */
        ownerName: undefined,

        /**
         * @cfg {String} associatedModel The full class name or reference to the class that the {@link #ownerModel}
         * is being associated with. This is a required configuration on every association.
         * @accessor
         */
        associatedModel: null,

        /**
         * @cfg {String} associatedName The name for the associated model. This defaults to the last part
         * of the class name of the {@link #associatedModel}.
         * @accessor
         */
        associatedName: undefined,


        /**
         * @cfg {String} associationKey The name of the property in the data to read the association from.
         * Defaults to the {@link #associatedName} plus _id.
         */
        associationKey: undefined,

        /**
         * @cfg {String} primaryKey The name of the primary key on the associated model. Defaults to 'id'.
         * In general this will be the {@link Ext.data.Model#idProperty} of the Model.
         */
        primaryKey: 'id',

        /**
         * @cfg {Ext.data.reader.Reader} reader A special reader to read associated data
         */
        reader: null,

        /**
         * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
         * Use 'hasMany' to create a HasMany association
         * <pre><code>
    associations: [{
        type: 'hasMany',
        model: 'User'
    }]
         * </code></pre>
         */
        type: null,

        name: undefined
    },

    statics: {
        create: function(association) {
            if (!association.isAssociation) {
                if (Ext.isString(association)) {
                    association = {
                        type: association
                    };
                }
                association.type = association.type.toLowerCase();
                return Ext.factory(association, Ext.data.association.Association, null, 'association');
            }

            return association;
        }
    },

    /**
     * Creates the Association object.
     * @param {Object} config (optional) Config object.
     */
    constructor: function(config) {
        this.initConfig(config);
    },

    applyName: function(name) {
        if (!name) {
            name = this.getAssociatedName();
        }
        return name;
    },

    applyOwnerModel: function(ownerName) {
        var ownerModel = Ext.data.ModelManager.getModel(ownerName);
        if (ownerModel === undefined) {
            Ext.Logger.error('The configured ownerModel was not valid (you tried ' + ownerName + ')');
        }
        return ownerModel;
    },

    applyOwnerName: function(ownerName) {
        if (!ownerName) {
            ownerName = this.getOwnerModel().modelName;
        }
        ownerName = ownerName.slice(ownerName.lastIndexOf('.')+1);
        return ownerName;
    },

    updateOwnerModel: function(ownerModel, oldOwnerModel) {
        if (oldOwnerModel) {
            this.setOwnerName(ownerModel.modelName);
        }
    },

    applyAssociatedModel: function(associatedName) {
        var associatedModel = Ext.data.ModelManager.types[associatedName];
        if (associatedModel === undefined) {
            Ext.Logger.error('The configured associatedModel was not valid (you tried ' + associatedName + ')');
        }
        return associatedModel;
    },

    applyAssociatedName: function(associatedName) {
        if (!associatedName) {
            associatedName = this.getAssociatedModel().modelName;
        }
        associatedName = associatedName.slice(associatedName.lastIndexOf('.')+1);
        return associatedName;
    },

    updateAssociatedModel: function(associatedModel, oldAssociatedModel) {
        if (oldAssociatedModel) {
            this.setAssociatedName(associatedModel.modelName);
        }
    },

    applyReader: function(reader) {
        if (reader) {
            if (Ext.isString(reader)) {
                reader = {
                    type: reader
                };
            }

            if (!reader.isReader) {
                Ext.applyIf(reader, {
                    type: 'json'
                });
            }
        }

        return Ext.factory(reader, Ext.data.Reader, this.getReader(), 'reader');
    },

    updateReader: function(reader) {
        reader.setModel(this.getAssociatedModel());
    }

    // Convert old properties in data into a config object
    // <deprecated product=touch since=2.0>
    ,onClassExtended: function(cls, data, hooks) {
        var Component = this,
            defaultConfig = Component.prototype.config,
            config = data.config || {},
            key;


        for (key in defaultConfig) {
            if (key in data) {
                config[key] = data[key];
                delete data[key];
                // <debug warn>
                Ext.Logger.deprecate(key + ' is deprecated as a property directly on the Association prototype. ' +
                    'Please put it inside the config object.');
                // </debug>
            }
        }

        data.config = config;
    }
    // </deprecated>
});