define(
   ['underscore', 'underscore.string', 'sources/pgadmin', 'jquery', 'backbone'],
function(_, S, pgAdmin, $, Backbone) {
  var pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};

  pgBrowser.DataModel = Backbone.Model.extend({
      /*
       * Parsing the existing data
       */
      on_server: false,
      parse: function(res) {
        var self = this;
        if (res && _.isObject(res) && 'node' in res && res['node']) {
          self.tnode = _.extend({}, res.node);
          delete res.node;
        }
        var objectOp = function(schema) {
          if (schema && _.isArray(schema)) {
            _.each(schema, function(s) {
              var obj, val;
              switch(s.type) {
                case 'collection':
                  obj = self.get(s.id);
                  val = res[s.id];
                  if (_.isArray(val) || _.isObject(val)) {
                    if (!obj || !(obj instanceof Backbone.Collection)) {
                      obj = new (pgBrowser.Node.Collection)(val, {
                        model: ((_.isString(s.model) &&
                                 s.model in pgBrowser.Nodes) ?
                                pgBrowser.Nodes[s.model].model : s.model),
                        top: self.top || self,
                        handler: self,
                        parse: true,
                        silent: true,
                        attrName: s.id
                        });

                      /*
                       * Nested collection models may or may not have idAttribute.
                       * So to decide whether model is new or not set 'on_server'
                       * flag on such models.
                       */

                      self.set(s.id, obj, {silent: true, parse: true, on_server : true});
                    } else {
                      /*
                       * Nested collection models may or may not have idAttribute.
                       * So to decide whether model is new or not set 'on_server'
                       * flag on such models.
                       */
                      obj.reset(val, {silent: true, parse: true, on_server : true});
                    }
                  }
                  else {
                    obj = null;
                  }
                  self.set(s.id, obj, {silent: true});
                  res[s.id] = obj;
                  break;
                case 'model':
                  obj = self.get(s.id);
                  val = res[s.id];
                  if (!_.isUndefined(val) && !_.isNull(val)) {
                    if (!obj || !(obj instanceof Backbone.Model)) {
                      if (_.isString(s.model) &&
                          s.model in pgBrowser.Nodes[s.model]) {
                        obj = new (pgBrowser.Nodes[s.model].Model)(
                            obj, {
                              silent: true, top: self.top || self, handler: self,
                              attrName: s.id
                            }
                            );
                      } else {
                        obj = new (s.model)(obj, {
                          silent: true, top: self.top || self, handler: self,
                          attrName: s.id
                        });
                      }
                    }
                    obj.set(val, {parse: true, silent: true});
                  } else {
                    obj = null;
                  }
                  res[s.id] = obj;
                  break;
                case 'nested':
                  objectOp(s.schema);

                  break;
                default:
                  break;
              }
            });
          }
        };

        objectOp(self.schema);

        return res;
      },
      isNew: function() {
        if (this.has(this.idAttribute)) {
          return !this.has(this.idAttribute);
        }
        return !this.on_server;
      },
      primary_key: function() {
        if (this.keys && _.isArray(this.keys)) {
          var res = {}, self = this;

          _.each(self.keys, function(k) {
            res[k] = self.attributes[k];
          });

          return JSON.stringify(res);
        }
        return this.cid;
      },
      initialize: function(attributes, options) {
        var self = this;
        self._previous_key_values = {};

        if (!_.isUndefined(options) && 'on_server' in options && options.on_server) {
          self.on_server = true;
        }

        Backbone.Model.prototype.initialize.apply(self, arguments);

        if (_.isUndefined(options) || _.isNull(options)) {
          options = attributes || {};
          attributes = null;
        }

        self.sessAttrs = {};
        self.fieldData = {};
        self.origSessAttrs = {};
        self.objects = [];
        self.arrays = [];
        self.attrName = options.attrName,
        self.top = (options.top || self.collection && self.collection.top || self.collection || self);
        self.handler = options.handler ||
          (self.collection && self.collection.handler);
        self.trackChanges = false;
        self.errorModel = new Backbone.Model();
        self.node_info = options.node_info;

        var obj;
        var objectOp = function(schema) {
          if (schema && _.isArray(schema)) {
            _.each(schema, function(s) {

              switch(s.type) {
                case 'int':
                case 'numeric':
                  self.fieldData[s.id] = {
                    id: s.id,
                    label: s.label,
                    type: s.type,
                    min: s.min || undefined,
                    max: s.max || undefined
                  }
                  break;
                default:
                  self.fieldData[s.id] = {
                      id: s.id,
                      label: s.label,
                      type: s.type
                    }
              }

              switch(s.type) {
                case 'array':
                  self.arrays.push(s.id);

                  break;
                case 'collection':
                  obj = self.get(s.id)
                  if (!obj || !(obj instanceof pgBrowser.Node.Collection)) {
                    if (_.isString(s.model) &&
                      s.model in pgBrowser.Nodes) {
                      var node = pgBrowser.Nodes[s.model];
                      obj = new (node.Collection)(obj, {
                        model: node.model,
                        top: self.top || self,
                        handler: self,
                        attrName: s.id
                      });
                    } else {
                      obj = new (pgBrowser.Node.Collection)(obj, {
                        model: s.model,
                        top: self.top || self,
                        handler: self,
                        attrName: s.id
                      });
                    }
                  }

                  obj.name = s.id;
                  self.objects.push(s.id);
                  self.set(s.id, obj, {silent: true});

                  break;
                case 'model':
                  obj = self.get(s.id)
                  if (!obj || !(obj instanceof Backbone.Model)) {
                    if (_.isString(s.model) &&
                        s.model in pgBrowser.Nodes[s.model]) {
                      obj = new (pgBrowser.Nodes[s.model].Model)(
                          obj, {
                            top: self.top || self, handler: self, attrName: s.id
                          }
                          );
                    } else {
                      obj = new (s.model)(
                          obj, {
                            top: self.top || self, handler: self, attrName: s.id
                          });
                    }
                  }

                  obj.name = s.id;
                  self.objects.push(s.id);
                  self.set(s.id, obj, {silent: true});

                  break;
                case 'nested':
                  objectOp(s.schema);
                  break;
                default:
                  return;
              }
            });
          }
        };
        objectOp(self.schema);

        if (self.handler && self.handler.trackChanges) {
          self.startNewSession();
        }

        if ('keys' in self && _.isArray(self.keys)) {
          _.each(self.keys, function(key) {
              self.on("change:" + key, function(m) {
                self._previous_key_values[key] =  m.previous(key);
              })
          })
        }
        return self;
      },
      // Create a reset function, which allow us to remove the nested object.
      reset: function(opts) {
        var obj;

        if (opts && opts.stop)
            this.stopSession();

        for(var id in this.objects) {
          obj = this.get(id);

          if (obj) {
            if (obj instanceof pgBrowser.DataModel) {
              obj.reset(opts);
            } else if (obj instanceof Backbone.Model) {
              obj.clear(opts);
            } else if (obj instanceof pgBrowser.DataCollection) {
              obj.reset(opts);
            } else if (obj instanceof Backbone.Collection) {
              obj.each(function(m) {
                if (m instanceof Backbone.DataModel) {
                  obj.reset();
                  obj.clear(opts);
                }
              });
              if (!(opts instanceof Array)){ opts = [opts] }
              Backbone.Collection.prototype.reset.apply(obj, opts);
            }
          }
        }
        this.clear(opts);
      },
      sessChanged: function() {
        var self = this;

        return (_.size(self.sessAttrs) > 0 ||
            _.some(self.objects, function(k) {
              var obj = self.get(k);
              if (!(_.isNull(obj) || _.isUndefined(obj))) {
                return obj.sessChanged();
              }
              return false;
            }));
      },
      sessValid: function() {
        var self = this;
        // Perform default validations.
        if ('default_validate' in self && typeof(self.default_validate) == 'function' &&
            _.isString(self.default_validate())) {
          return false;
        }

        if ('validate' in self && _.isFunction(self.validate) &&
            _.isString(self.validate.apply(self))) {
          return false;
        }
        return true;
      },
      set: function(key, val, options) {
        var opts = _.isObject(key) ? val : options;

        this._changing = true;
        this._previousAttributes = _.clone(this.attributes);
        this.changed = {};

        var res = Backbone.Model.prototype.set.call(this, key, val, options);
        this._changing = false;

        if ((opts&& opts.internal) || !this.trackChanges) {
          return true;
        }

        if (key != null && res) {
          var attrs = {},
              self = this,
              msg;

          var attrChanged = function(v, k) {
            if (k in self.objects) {
              return;
            }
            attrs[k] = v;
            if (_.isEqual(self.origSessAttrs[k], v)) {
              delete self.sessAttrs[k];
            } else {
              self.sessAttrs[k] = v;
            }
          };

          // Handle both `"key", value` and `{key: value}` -style arguments.
          if (typeof key === 'object') {
            _.each(key, attrChanged);
          } else {
            attrChanged(val, key);
          }

          self.trigger('pgadmin-session:set', self, attrs);
          if (!options || !options.silent) {
            self.trigger('change', self, options);
          }

          // Perform default validations.

          if ('default_validate' in self && typeof(self.default_validate) == 'function') {
            msg = self.default_validate();
          }

          if ('validate' in self && typeof(self['validate']) === 'function') {

            if (!msg) {
              msg = self.validate(_.keys(attrs));
            }
          }

          /*
           * If any parent present, we will need to inform the parent - that
           * I have some issues/fixed the issue.
           *
           * If not parent found, we will raise the issue
           */
          if (_.size(self.errorModel.attributes) == 0) {
              if (self.collection || self.handler) {
                (self.collection || self.handler).trigger(
                    'pgadmin-session:model:valid', self, (self.collection || self.handler)
                    );
              } else {
                self.trigger('pgadmin-session:valid', self.sessChanged(), self);
              }
            } else {
              msg = msg || _.values(self.errorModel.attributes)[0];
              if (self.collection || self.handler) {
                (self.collection || self.handler).trigger(
                    'pgadmin-session:model:invalid', msg, self, (self.collection || self.handler)
                    );
              } else {
                self.trigger('pgadmin-session:invalid', msg, self);
              }
            }

          return res;
        }
        return res;
      },
      /*
       * We do support modified data only through session by tracking changes.
       *
       * In normal mode, we will use the toJSON function of Backbone.Model.
       * In session mode, we will return all the modified data only. And, the
       * objects (collection, and model) will be return as stringified JSON,
       * only from the parent object.
       */
      toJSON: function(session, method) {
        var self = this, res, isNew = self.isNew();

        session = (typeof(session) != "undefined" && session == true);

        if (!session || isNew) {
          res = Backbone.Model.prototype.toJSON.call(this, arguments);
        } else {
          res = {};
          res[self.idAttribute || '_id'] = self.get(self.idAttribute || '_id');
          res = _.extend(res, self.sessAttrs);
        }

        /*
         * We do have number objects (models, collections), which needs to be
         * converted to JSON data manually.
         */
        _.each(
            self.objects,
            function(k) {
              var obj = self.get(k);
              /*
               * For session changes, we only need the modified data to be
               * transformed to JSON data.
               */
              if (session) {
                if (res[k] instanceof Array) {
                  res[k] = JSON.stringify(res[k]);
                } else if ((obj && obj.sessChanged && obj.sessChanged()) || isNew) {
                  res[k] = obj && obj.toJSON(!isNew);
                  /*
                   * We will run JSON.stringify(..) only from the main object,
                   * not for the JSON object within the objects, that only when
                   * HTTP method is 'GET'.
                   *
                   * We do stringify the object, so that - it will not be
                   * translated to wierd format using jQuery.
                   */
                  if (obj && method && method == 'GET') {
                    res[k] = JSON.stringify(res[k]);
                  }
                } else {
                  delete res[k];
                }
              } else if (!(res[k] instanceof Array)) {
                res[k] = (obj && obj.toJSON());
              }
            });
        if (session) {
          _.each(
              self.arrays,
              function(a) {
                /*
                 * For session changes, we only need the modified data to be
                 * transformed to JSON data.
                 */
                if (res[a] && res[a] instanceof Array) {
                  res[a] = JSON.stringify(res[a]);
                }
              });
        }
        return res;
      },
      startNewSession: function() {
        var self = this;

        if (self.trackChanges) {
          self.trigger('pgadmin-session:stop', self);
          self.off('pgadmin-session:model:invalid', self.onChildInvalid);
          self.off('pgadmin-session:model:valid', self.onChildValid);
          self.off('pgadmin-session:changed', self.onChildChanged);
          self.off('pgadmin-session:added', self.onChildChanged);
          self.off('pgadmin-session:removed', self.onChildChanged);
        }

        self.trackChanges = true;
        self.sessAttrs = {};
        self.origSessAttrs = _.clone(self.attributes);

        var res = false;

        _.each(self.objects, function(o) {
          var obj = self.get(o);

          if (_.isUndefined(obj) || _.isNull(obj)) {
            return;
          }

          delete self.origSessAttrs[o];

          if (obj && 'startNewSession' in obj && _.isFunction(obj.startNewSession)) {
            obj.startNewSession();
          }
        });

        // Let people know, I have started session hanlding
        self.trigger('pgadmin-session:start', self);

        // Let me listen to the my child invalid/valid messages
        self.on('pgadmin-session:model:invalid', self.onChildInvalid);
        self.on('pgadmin-session:model:valid', self.onChildValid);
        self.on('pgadmin-session:changed', self.onChildChanged);
        self.on('pgadmin-session:added', self.onChildChanged);
        self.on('pgadmin-session:removed', self.onChildChanged);
      },

      onChildInvalid: function(msg, obj) {
        var self = this;

        if (self.trackChanges && obj) {
          var objName = obj.attrName;

          if (!objName) {
            var hasPrimaryKey = obj.primary_key &&
                  typeof(obj.primary_key) === 'function';
              var key = hasPrimaryKey ? obj.primary_key() : obj.cid,
                comparator = hasPrimaryKey ?
                  function(k) {
                    var o = self.get('k');

                    if (o && o.primary_key() === key) {
                      objName = k;
                      return true;
                    }

                    return false;
                  } :
                  function(k) {
                    var o = self.get(k);

                    if (o.cid === key) {
                      objName = k;
                      return true;
                    }

                    return false;
                  };
            _.findIndex(self.objects, comparator);
          }

          if (objName) {
            /*
             * Update the error message for this object.
             */
            self.errorModel.set(objName, msg);

            if (self.handler) {
              (self.handler).trigger('pgadmin-session:model:invalid', msg, self, self.handler);
            } else  {
              self.trigger('pgadmin-session:invalid', msg, self);
            }
          }
        }

        return this;
      },
      onChildValid: function(obj) {
        var self = this;

        if (self.trackChanges && obj) {
          var objName = obj.attrName;

          if (!objName) {
              var hasPrimaryKey = (obj.primary_key &&
                (typeof(obj.primary_key) === 'function'));
              var key = hasPrimaryKey ? obj.primary_key() : obj.cid,
              comparator = hasPrimaryKey ?
                function(k) {
                  var o = self.get('k');

                  if (o && o.primary_key() === key) {
                    objName = k;
                    return true;
                  }

                  return false;
                } :
                function(k) {
                  var o = self.get('k');

                  if (o && o.cid === key) {
                    objName = k;
                    return true;
                  }

                  return false;
                };

              _.findIndex(self.objects, comparator);
          }

          var msg = null,
              validate = function(m, attrs) {
                if ('default_validate' in m && typeof(m.default_validate) == 'function') {
                  msg = m.default_validate();
                  if (_.isString(msg)) {
                    return msg;
                  }
                }

                if ('validate' in m && typeof(m.validate) == 'function') {
                  msg = m.validate(attrs);

                  return msg;
                }
                return null;
              };

          if (obj instanceof Backbone.Collection) {
            for (var idx in obj.models) {
              if (validate(obj.models[idx]))
                break;
            }
          } else if (obj instanceof Backbone.Model) {
            validate(obj);
          }

          if (objName && self.errorModel.has(objName)) {
            if (!msg) {
              self.errorModel.unset(objName);
            } else {
              self.errorModel.set(objName, msg);
            }
          }

          /*
           * The object is valid, but does this has any effect on parent?
           * Let's validate this object itself, before making it clear.
           *
           * We will trigger validation information.
           */
          if (_.size(self.errorModel.attributes) == 0 &&
              !validate(self, (objName && [objName]))) {
            if (self.handler) {
              (self.handler).trigger('pgadmin-session:model:valid', self, self.handler);
            } else  {
              self.trigger(
                  'pgadmin-session:valid', self.sessChanged(), self
                  );
            }
          } else {
            msg = msg || _.values(self.errorModel.attributes)[0];

            if (self.handler) {
              (self.handler).trigger(
                  'pgadmin-session:model:invalid', msg, self, self.handler
                  );
            } else  {
              self.trigger('pgadmin-session:invalid', msg, self);
            }
          }
        }
      },

      onChildChanged: function(obj) {
        var self = this;

        if (self.trackChanges && self.collection) {
          (self.collection).trigger('change', self);
        }
      },

      stopSession: function() {
        var self = this;

        if (self.trackChanges) {
          self.off('pgadmin-session:model:invalid', self.onChildInvalid);
          self.off('pgadmin-session:model:valid', self.onChildValid);
          self.off('pgadmin-session:changed', self.onChildChanged);
          self.off('pgadmin-session:added', self.onChildChanged);
          self.off('pgadmin-session:removed', self.onChildChanged);
        }

        self.trackChanges = false;
        self.sessAttrs = {};
        self.origSessAttrs = {};

        _.each(self.objects, function(o) {
          var obj = self.get(o);

          if (_.isUndefined(obj) || _.isNull(obj)) {
            return;
          }

          self.origSessAttrs[o] = null;
          delete self.origSessAttrs[o];

          if (obj && 'stopSession' in obj && _.isFunction(obj.stopSession)) {
            obj.stopSession();
          }
        });

        self.trigger('pgadmin-session:stop');
      },
      default_validate: function() {
        var msg, field, value, type;

        for (var i = 0, keys = _.keys(this.attributes), l = keys.length;
                 i<l;
                 i++) {

          value = this.attributes[keys[i]];
          field  = this.fieldData[keys[i]]
          msg = null;

          if (!(_.isUndefined(value) || _.isNull(value) ||
                String(value).replace(/^\s+|\s+$/g, '') == '')) {

            if (!field) {
              continue;
            }

            type = field.type || undefined;
            if (!type) {
              continue;
            }

            switch(type) {
              case 'int':
                msg = this.integer_validate(value, field);
                break;
              case 'numeric':
                msg = this.number_validate(value, field);
                break;
            }

            if (msg) {
              this.errorModel.set(field.id, msg);
              return msg;
            } else {
              this.errorModel.unset(field.id);
            }
          } else {
            if (field) {
              this.errorModel.unset(field.id);
            }
          }
        }
        return null;
      },

      check_min_max: function (value, field) {
        var label = field.label,
            min_value = field.min,
            max_value =  field.max;

        if (min_value && value < min_value) {
          return S(pgAdmin.Browser.messages.MUST_GR_EQ).sprintf(label, min_value).value();
        } else if (max_value && value > max_value) {
          return S(pgAdmin.Browser.messages.MUST_LESS_EQ).sprintf(label, max_value).value();
        }
        return null;
      },
      number_validate: function (value, field) {
        var pattern = new RegExp("^-?[0-9]+(\.?[0-9]*)?$");
        if (!pattern.test(value)) {
          return S(pgAdmin.Browser.messages.MUST_BE_NUM).sprintf(field.label).value()
        }
        return this.check_min_max(value, field)
      },
      integer_validate: function(value, field) {
        var pattern = new RegExp("^-?[0-9]*$");
        if (!pattern.test(value)) {
          return S(pgAdmin.Browser.messages.MUST_BE_INT).sprintf(field.label).value()
        }
        return this.check_min_max(value, field)
      }
    });

    pgBrowser.DataCollection = Backbone.Collection.extend({
      // Model collection
      initialize: function(attributes, options) {
        var self = this;

        options = options || {};
        /*
         * Session changes will be kept in this object.
         */
        self.sessAttrs = {
          'changed': [],
          'added': [],
          'deleted': [],
          'invalid': []
        };
        self.top = options.top || self;
        self.attrName = options.attrName;
        self.handler = options.handler;
        self.trackChanges = false;

        /*
         * Listen to the model changes for the session changes.
         */
        self.on('add', self.onModelAdd);
        self.on('remove', self.onModelRemove);
        self.on('change', self.onModelChange);

        /*
         * We need to start the session, if the handler is already in session
         * tracking mode.
         */
        if (self.handler && self.handler.trackChanges) {
          self.startNewSession();
        }

        return self;
      },
      startNewSession: function() {
        var self = this,
            msg;

        if (self.trackChanges) {
          // We're stopping the existing session.
          self.trigger('pgadmin-session:stop', self);

          self.off('pgadmin-session:model:invalid', self.onModelInvalid);
          self.off('pgadmin-session:model:valid', self.onModelValid);
        }

        self.trackChanges = true;
        self.sessAttrs = {
          'changed': [],
          'added': [],
          'deleted': [],
          'invalid': []
        };

        _.each(self.models, function(m) {
          if ('startNewSession' in m && _.isFunction(m.startNewSession)) {
            m.startNewSession();
          }

          if ('default_validate' in m && typeof(m.default_validate) == 'function') {
            msg = m.default_validate();
          }

          if (_.isString(msg)) {
            self.sessAttrs['invalid'][m.cid] = msg;
          } else if ('validate' in m && typeof(m.validate) === 'function') {
            msg = m.validate();

            if (msg) {
              self.sessAttrs['invalid'][m.cid] = msg;
            }
          }
        });

        // Let people know, I have started session hanlding
        self.trigger('pgadmin-session:start', self);

        self.on('pgadmin-session:model:invalid', self.onModelInvalid);
        self.on('pgadmin-session:model:valid', self.onModelValid);
      },
      onModelInvalid: function(msg, m) {
        var self = this,
            invalidModels = self.sessAttrs['invalid'];

        if (self.trackChanges) {
          // Do not add the existing invalid object
          invalidModels[m.cid] = msg;

          // Inform the parent that - I am an invalid model.
          if (self.handler) {
            (self.handler).trigger('pgadmin-session:model:invalid', msg, self, self.handler);
          } else {
            self.trigger('pgadmin-session:invalid', msg, self);
          }
        }

        return true;
      },
      onModelValid: function(m) {
        var self = this,
            invalidModels = self.sessAttrs['invalid'];

        if (self.trackChanges) {
          // Now check uniqueness of current model with other models.
          var isUnique = self.checkDuplicateWithModel(m);

          // If unique then find the object the invalid list, if found remove it from the list
          // and inform the parent that - I am a valid object now.

          if (isUnique && m.cid in invalidModels) {
            delete invalidModels[m.cid];
          }

          if (isUnique) {
            this.triggerValidationEvent.apply(this);
          }
        }

        return true;
      },
      stopSession: function() {
        var self = this;

        self.trackChanges = false;
        self.sessAttrs = {
          'changed': [],
          'added': [],
          'deleted': [],
          'invalid': []
        };

        _.each(self.models, function(m) {
          if ('stopSession' in m && _.isFunction(m.stopSession)) {
            m.stopSession();
          }
        });
      },
      sessChanged: function() {
        return (
            this.sessAttrs['changed'].length > 0 ||
            this.sessAttrs['added'].length > 0 ||
            this.sessAttrs['deleted'].length > 0
            );
      },
      /*
       * We do support the changes through session tracking in general.
       *
       * In normal mode, we will use the general toJSON(..) function of
       * Backbone.Colletion.
       *
       * In session mode, we will return session changes as:
       * We will be returning the session changes as:
       * {
       *  'added': [JSON of each new model],
       *  'delete': [JSON of each deleted model],
       *  'changed': [JSON of each modified model with session changes]
       * }
       */
      toJSON: function(session) {
        var self = this,
            session = (typeof(session) != "undefined" && session == true);

        if (!session) {
          return Backbone.Collection.prototype.toJSON.call(self);
        } else {
          var res = {};

          res['added'] = [];
          _.each(this.sessAttrs['added'], function(o) {
            res['added'].push(o.toJSON());
          });
          if (res['added'].length == 0) {
            delete res['added'];
          }
          res['changed'] = [];
          _.each(self.sessAttrs['changed'], function(o) {
            res['changed'].push(o.toJSON(true));
          });
          if (res['changed'].length == 0) {
            delete res['changed'];
          }
          res['deleted'] = [];
          _.each(self.sessAttrs['deleted'], function(o) {
            res['deleted'].push(o.toJSON());
          });
          if (res['deleted'].length == 0) {
            delete res['deleted'];
          }

          return (_.size(res) == 0 ? null : res);
        }
      },
      // Override the reset function, so that - we can reset the model
      // properly.
      reset: function(opts) {
        if (opts && opts.stop)
            this.stopSession();
        this.each(function(m) {
          if (!m)
            return;
          if (m instanceof pgBrowser.DataModel) {
            m.reset(opts);
          } else {
            m.clear(opts);
          }
        });
        Backbone.Collection.prototype.reset.apply(this, arguments);
      },
      objFindInSession: function(m, type) {
        var hasPrimaryKey = m.primary_key &&
              typeof(m.primary_key) == 'function',
            key = hasPrimaryKey ? m.primary_key() : m.cid,
            comparator = hasPrimaryKey ? function(o) {
              return (o.primary_key() === key);
            } : function(o) {
              return (o.cid === key);
            };

        return (_.findIndex(this.sessAttrs[type], comparator));
      },
      onModelAdd: function(obj) {
        if (!this.trackChanges)
          return true;

        var self = this,
            msg,
            isAlreadyInvalid = (_.size(self.sessAttrs['invalid']) != 0),
            idx = self.objFindInSession(obj, 'deleted');

        // Hmm.. - it was originally deleted from this collection, we should
        // remove it from the 'deleted' list.
        if (idx >= 0) {
          var origObj = self.sessAttrs['deleted'][idx];

          obj.origSessAttrs = _.clone(origObj.origSessAttrs);
          obj.attributes = _.extend(obj.attributes, origObj.attributes);
          obj.sessAttrs = _.clone(origObj.sessAttrs);

          self.sessAttrs['deleted'].splice(idx, 1);

          // It has been changed originally!
          if ((!'sessChanged' in obj) || obj.sessChanged()) {
            self.sessAttrs['changed'].push(obj);
          }

          (self.handler || self).trigger('pgadmin-session:added', self, obj);


          if ('default_validate' in obj && typeof(obj.default_validate) == 'function') {
            msg = obj.default_validate();
          }

          if (_.isString(msg)) {
            (self.sessAttrs['invalid'])[obj.cid] = msg;
          } else if ('validate' in obj && typeof(obj.validate) === 'function') {
            msg = obj.validate();

            if (msg) {
              (self.sessAttrs['invalid'])[obj.cid] = msg;
            }
          }
        } else {

          if ('default_validate' in obj && typeof(obj.default_validate) == 'function') {
            msg = obj.default_validate();
          }

          if (_.isString(msg)) {
            (self.sessAttrs['invalid'])[obj.cid] = msg;
          } else if ('validate' in obj && typeof(obj.validate) === 'function') {
            msg = obj.validate();

            if (msg) {
              (self.sessAttrs['invalid'])[obj.cid] = msg;
            }
          }
          self.sessAttrs['added'].push(obj);

          /*
           * Session has been changed
           */
          (self.handler || self).trigger('pgadmin-session:added', self, obj);
        }

        // Let the parent/listener know about my status (valid/invalid).
        this.triggerValidationEvent.apply(this);

        return true;
      },
      onModelRemove: function(obj) {
        if (!this.trackChanges)
          return true;

        /* Once model is removed from collection clear its errorModel as it's no longer relevant
         * for us. Otherwise it creates problem in 'clearInvalidSessionIfModelValid' function.
         */
        obj.errorModel.clear();

        var self = this,
            invalidModels = self.sessAttrs['invalid'],
            copy = _.clone(obj),
            idx = self.objFindInSession(obj, 'added');

        // We need to remove it from the invalid object list first.
        if (obj.cid in invalidModels) {
          delete invalidModels[obj.cid];
        }

        // Hmm - it was newly added, we can safely remove it.
        if (idx >= 0) {
          self.sessAttrs['added'].splice(idx, 1);

          (self.handler || self).trigger('pgadmin-session:removed', self, copy);

          self.checkDuplicateWithModel(copy);

          // Let the parent/listener know about my status (valid/invalid).
          this.triggerValidationEvent.apply(this);

          return true;
        }

        // Hmm - it was changed in this session, we should remove it from the
        // changed models.
        idx = self.objFindInSession(obj, 'changed');

        if (idx >= 0) {
          self.sessAttrs['changed'].splice(idx, 1);
          (self.handler || self).trigger('pgadmin-session:removed', self, copy);
        } else {
          (self.handler || self).trigger('pgadmin-session:removed', self, copy);
        }

        self.sessAttrs['deleted'].push(obj);

        self.checkDuplicateWithModel(obj);

        // Let the parent/listener know about my status (valid/invalid).
        this.triggerValidationEvent.apply(this);

        /*
         * This object has been remove, we need to check (if we still have any
         * other invalid message pending).
         */

        return true;
      },
      triggerValidationEvent: function() {
        var self = this,
            msg = null,
            invalidModels = self.sessAttrs['invalid'],
            validModels = [];

        for (var key in invalidModels) {
          msg = invalidModels[key];
          if (msg) {
            break;
          }
          else {
            // Hmm..
            // How come - you have been assigned in invalid list.
            // I will make a list of it, and remove it later.
            validModels.push(key);
          }
        }

        // Let's remove the un
        for (key in validModels) {
          delete invalidModels[validModels[key]];
        }

        if (!msg) {
          if (self.handler) {
            self.handler.trigger('pgadmin-session:model:valid', self, self.handler);
          } else {
            self.trigger('pgadmin-session:valid', self.sessChanged(), self);
          }
        } else {
          if (self.handler) {
            self.handler.trigger('pgadmin-session:model:invalid', msg, self, self.handler);
          } else {
            self.trigger('pgadmin-session:invalid', msg, self);
          }
        }
      },
      onModelChange: function(obj) {
        var  self = this;

        if (!this.trackChanges || !(obj instanceof pgBrowser.Node.Model))
          return true;

        var idx = self.objFindInSession(obj, 'added');

        // It was newly added model, we don't need to add into the changed
        // list.
        if (idx >= 0) {
          (self.handler || self).trigger('pgadmin-session:changed', self, obj);

          return true;
        }

        idx = self.objFindInSession(obj, 'changed');

        if (!'sessChanged' in obj) {
          (self.handler || self).trigger('pgadmin-session:changed', self, obj);

          if (idx >= 0) {
            return true;
          }

          self.sessAttrs['changed'].push(obj);

          return true;
        }

        if (idx >= 0) {

          if (!obj.sessChanged()) {
            // This object is no more updated, removing it from the changed
            // models list.
            self.sessAttrs['changed'].splice(idx, 1);

            (self.handler || self).trigger('pgadmin-session:changed',self, obj);
            return true;
          }

          (self.handler || self).trigger('pgadmin-session:changed',self, obj);

          return true;
        }

        if (obj.sessChanged()) {
          self.sessAttrs['changed'].push(obj);
          (self.handler || self).trigger('pgadmin-session:changed', self, obj);
        }

        return true;
      },

      /*
       * This function will check if given model is unique or duplicate in
       * collection and set/clear duplicate errors on models.
       */
      checkDuplicateWithModel: function(model) {
        if (!('keys' in model) || _.isEmpty(model.keys)) {
          return true;
        }

        var self = this,
            condition = {},
            previous_condition = {};

        _.each(model.keys, function(key) {
          condition[key] = model.get(key);
          if(key in model._previous_key_values) {
              previous_condition[key] = model._previous_key_values[key];
            } else {
              previous_condition[key] = model.previous(key);
            }
        });

        // Reset previously changed values.
        model._previous_key_values = {};

        var old_conflicting_models =  self.where(previous_condition);

        if (old_conflicting_models.length == 1) {
          var m = old_conflicting_models[0];
          self.clearInvalidSessionIfModelValid(m);
        }

        var new_conflicting_models = self.where(condition);
        if (new_conflicting_models.length == 0) {
          self.clearInvalidSessionIfModelValid(model);
        } else if (new_conflicting_models.length == 1) {
          self.clearInvalidSessionIfModelValid(model);
          self.clearInvalidSessionIfModelValid(new_conflicting_models[0]);
        } else {
          var msg = "Duplicate rows.";
          setTimeout(function() {
            _.each(new_conflicting_models, function(m) {
              self.trigger(
                    'pgadmin-session:model:invalid', msg, m, self.handler
                    );
              m.trigger(
                    'pgadmin-session:model:duplicate', m, msg
                    );
            });
          }, 10);

          return false;
        }
        return true;
      },
      clearInvalidSessionIfModelValid: function(m) {
        var errors = m.errorModel.attributes,
            invalidModels = this.sessAttrs['invalid'];

        m.trigger('pgadmin-session:model:unique', m
                    );
        if (_.size(errors) == 0) {
          delete invalidModels[m.cid];
        } else {
          invalidModels[m.cid] = errors[Object.keys(errors)[0]];
        }
      }
    });

    pgBrowser.Events = _.extend({}, Backbone.Events);

    return pgBrowser;
});
