/**
 * @ignore
 * enhanced base for model with sync
 * @author yiminghe@gmail.com
 */
KISSY.add("mvc/model", function (S, Base) {
    var blacklist = [
        "idAttribute",
        "destroyed",
        "plugins",
        "listeners",
        "clientId",
        "urlRoot",
        "url",
        "parse",
        "sync"
    ];

    /**
     * Model represent a data record.
     * @class KISSY.MVC.Model
     * @extends KISSY.Base
     */
   return Base.extend({
       initializer:function(){
           /*
            *Change should bubble to its collections
            */
           this.collections = {};
       },

            /**
             * Add current model instance to a specified collection.
             * @param {KISSY.MVC.Collection} c
             */
            addToCollection:function (c) {
                this.collections[S.stamp(c)] = c;
                this.addTarget(c);
            },
            /**
             * Remove current model instance from a specified collection.
             * @param {KISSY.MVC.Collection} c
             */
            removeFromCollection:function (c) {
                delete this.collections[S.stamp(c)];
                this.removeTarget(c);
            },

            /**
             * Get current model 's id.
             */
            getId:function () {
                return this.get(this.get("idAttribute"));
            },

            /**
             * Set current model 's id.
             * @param id
             */
            'setId':function (id) {
                return this.set(this.get("idAttribute"), id);
            },

            setInternal:function () {
                this.__isModified = 1;
                return this.callSuper.apply(this,arguments);
            },

            /**
             * whether it is newly created.
             * @return {Boolean}
             */
            isNew:function () {
                return !this.getId();
            },

            /**
             * whether has been modified since last save.
             * @return {Boolean}
             */
            isModified:function () {
                return !!(this.isNew() || this.__isModified);
            },

            /**
             * destroy this model and sync with server.
             * @param {Object} [opts] destroy config.
             * @param {Function} opts.success callback when action is done successfully.
             * @param {Function} opts.error callback when error occurs at action.
             * @param {Function} opts.complete callback when action is complete.
             * @chainable
             */
            destroy:function (opts) {
                var self = this;
                opts = opts || {};
                var success = opts.success;
                /**
                 * @ignore
                 */
                opts.success = function (resp) {
                    var lists = self.collections;
                    if (resp) {
                        var v = self.get("parse").call(self, resp);
                        if (v) {
                            self.set(v, opts);
                        }
                    }
                    for (var l in lists) {
                        lists[l].remove(self, opts);
                    }
                    self.fire("destroy");
                    success && success.apply(this, arguments);
                };
                if (!self.isNew() && opts['delete']) {
                    self.get("sync").call(self, self, 'delete', opts);
                } else {
                    opts.success();
                    if (opts.complete) {
                        opts.complete();
                    }
                }

                return self;
            },

            /**
             * Load model data from server.
             * @param {Object} opts Load config.
             * @param {Function} opts.success callback when action is done successfully.
             * @param {Function} opts.error callback when error occurs at action.
             * @param {Function} opts.complete callback when action is complete.
             * @chainable
             */
            load:function (opts) {
                var self = this;
                opts = opts || {};
                var success = opts.success;
                /**
                 * @ignore
                 */
                opts.success = function (resp) {
                    if (resp) {
                        var v = self.get("parse").call(self, resp);
                        if (v) {
                            self.set(v, opts);
                        }
                    }
                    self.__isModified = 0;
                    success && success.apply(this, arguments);
                };
                self.get("sync").call(self, self, 'read', opts);
                return self;
            },

            /**
             * Save current model 's data to server using sync.
             * @param {Object} opts Save config.
             * @param {Function} opts.success callback when action is done successfully.
             * @param {Function} opts.error callback when error occurs at action.
             * @param {Function} opts.complete callback when action is complete.
             * @chainable
             */
            save:function (opts) {
                var self = this;
                opts = opts || {};
                var success = opts.success;
                /**
                 * @ignore
                 */
                opts.success = function (resp) {
                    if (resp) {
                        var v = self.get("parse").call(self, resp);
                        if (v) {
                            self.set(v, opts);
                        }
                    }
                    self.__isModified = 0;
                    success && success.apply(this, arguments);
                };
                self.get("sync").call(self, self, self.isNew() ? 'create' : 'update', opts);
                return self;
            },

            /**
             * Get json representation for current model.
             * @return {Object}
             */
            toJSON:function () {
                var ret = this.getAttrVals();
                S.each(blacklist, function (b) {
                    delete ret[b];
                });
                return ret;
            }

        }, {
            ATTRS:{
                /**
                 * Attribute name used to store id from server.
                 * Defaults to: "id".
                 * @type {String}
                 */
                idAttribute:{
                    value:'id'
                },

                /**
                 * Generated client id.
                 * Default call S.guid()
                 * @type {Function}
                 */
                clientId:{
                    valueFn:function () {
                        return S.guid("mvc-client");
                    }
                },
                /**
                 * Called to get url for delete/edit/new current model.
                 * Defaults to: collection.url+"/"+mode.id
                 * @type {Function}
                 */
                url:{
                    value:url
                },
                /**
                 * If current model does not belong to any collection.
                 * Use this attribute value as collection.url in {@link KISSY.MVC.Model#url}
                 * @type {String}
                 */
                urlRoot:{
                    value:""
                },
                /**
                 * Sync model data with server.
                 * Default to call {@link KISSY.MVC#sync}
                 * @type {Function}
                 */
                sync:{
                    value:function () {
                        S.require("mvc").sync.apply(this, arguments);
                    }
                },
                /**
                 * parse json from server to get attr/value pairs.
                 * Default to return raw data from server.
                 * @type {Function}
                 */
                parse:{
                    value:function (resp) {
                        return resp;
                    }
                }
            }
        });

    function getUrl(o) {
        var u;
        if (o && (u = o.get("url"))) {
            if (typeof u == 'string') {
                return u;
            }
            return u.call(o);
        }
        return u;
    }

    function url() {
        var c,
            cv,
            collections = this.collections;
        for (c in collections) {
            if (collections.hasOwnProperty(c)) {
                cv = collections[c];
                break;
            }
        }
        var base = getUrl(cv) || this.get("urlRoot");

        if (this.isNew()) {
            return base;
        }

        base = base + (base.charAt(base.length - 1) == '/' ? '' : '/');
        return base + encodeURIComponent(this.getId()) + "/";
    }
}, {
    requires:['base']
});