// Generated by CoffeeScript 1.9.0

/*
  backbone-mongo.js 0.6.10
  Copyright (c) 2013 Vidigami - https://github.com/vidigami/backbone-mongo
  License: MIT (http://www.opensource.org/licenses/mit-license.php)
 */

(function() {
  var Backbone, BackboneORM, CAPABILITIES, Connection, DESTROY_BATCH_LIMIT, DatabaseTools, JSONUtils, MONGO_GENERATION, MongoCursor, MongoSync, ObjectID, Queue, Schema, Utils, _, _ref,
    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

  ObjectID = require('mongodb').ObjectID;

  _ref = BackboneORM = require('backbone-orm'), _ = _ref._, Backbone = _ref.Backbone, Queue = _ref.Queue, Schema = _ref.Schema, Utils = _ref.Utils, JSONUtils = _ref.JSONUtils;

  MONGO_GENERATION = +(require('mongodb/package.json')).version.split('.').shift();

  if (MONGO_GENERATION !== 1 && MONGO_GENERATION !== 2) {
    throw new Error("mongodb driver version " + (require('mongodb/package.json')).version + " not yet supported. Open an issue: https://github.com/vidigami/backbone-mongo/issues");
  }

  MongoCursor = require('./cursor');

  Connection = require('./lib/connection');

  DatabaseTools = require('./database_tools');

  DESTROY_BATCH_LIMIT = 1000;

  CAPABILITIES = {
    embed: true,
    json: true,
    unique: true,
    manual_ids: true,
    dynamic: true,
    self_reference: true
  };

  MongoSync = (function() {
    function MongoSync(_at_model_type, _at_sync_options) {
      this.model_type = _at_model_type;
      this.sync_options = _at_sync_options != null ? _at_sync_options : {};
      this.db = __bind(this.db, this);
      this.deleteCB = __bind(this.deleteCB, this);
      this.model_type.model_name = Utils.findOrGenerateModelName(this.model_type);
      this.schema = new Schema(this.model_type, {
        id: {
          type: ObjectID
        }
      });
      this.backbone_adapter = this.model_type.backbone_adapter = this._selectAdapter();
    }

    MongoSync.prototype.initialize = function(model) {
      var url;
      if (this.is_initialized) {
        return;
      }
      this.is_initialized = true;
      this.schema.initialize();
      if (!(url = _.result(new this.model_type, 'url'))) {
        throw new Error("Missing url for model");
      }
      return this.connect(url);
    };

    MongoSync.prototype.read = function(model, options) {
      if (model.models) {
        return this.cursor().toJSON(function(err, json) {
          if (err) {
            return options.error(err);
          }
          return options.success(json);
        });
      } else {
        return this.cursor(model.id).toJSON(function(err, json) {
          if (err) {
            return options.error(err);
          }
          if (!json) {
            return options.error(new Error("Model not found. Id " + model.id));
          }
          return options.success(json);
        });
      }
    };

    MongoSync.prototype.create = function(model, options, force) {
      if (this.manual_id && !force) {
        return options.error(new Error("Create should not be called for manual option. Set an id before calling save. Model: " + (JSONUtils.stringify(model.toJSON()))));
      }
      return this.connection.collection((function(_this) {
        return function(err, collection) {
          var doc;
          if (err) {
            return options.error(err);
          }
          if (model.get('_rev')) {
            return options.error(new Error('New document has a non-empty revision'));
          }
          doc = _this.backbone_adapter.attributesToNative(model.toJSON());
          doc._rev = 1;
          return collection.insert(doc, function(err, results) {
            var docs;
            if (err) {
              return options.error(err);
            }
            docs = MONGO_GENERATION === 1 ? results : results.ops;
            if (!docs || docs.length !== 1) {
              return callback(new Error("Failed to create model. Error: document not found"));
            }
            return options.success(_this.backbone_adapter.nativeToAttributes(docs[0]));
          });
        };
      })(this));
    };

    MongoSync.prototype.update = function(model, options) {
      if (!model.get('_rev')) {
        return this.create(model, options, true);
      }
      return this.connection.collection((function(_this) {
        return function(err, collection) {
          var find_query, json, key, modifications, unsets, _i, _len;
          if (err) {
            return options.error(err);
          }
          json = _this.backbone_adapter.attributesToNative(model.toJSON());
          if (_this.backbone_adapter.id_attribute === '_id') {
            delete json._id;
          }
          find_query = _this.backbone_adapter.modelFindQuery(model);
          find_query._rev = json._rev;
          json._rev++;
          modifications = {
            $set: json
          };
          if (unsets = Utils.get(model, 'unsets')) {
            Utils.unset(model, 'unsets');
            if (unsets.length) {
              modifications.$unset = {};
              for (_i = 0, _len = unsets.length; _i < _len; _i++) {
                key = unsets[_i];
                if (!model.attributes.hasOwnProperty(key)) {
                  modifications.$unset[key] = '';
                }
              }
            }
          }
          return collection.findAndModify(find_query, [[_this.backbone_adapter.id_attribute, 'asc']], modifications, {
            "new": true
          }, function(err, result) {
            var doc;
            if (err) {
              return options.error(new Error("Failed to update model (" + _this.model_type.model_name + "). Error: " + err));
            }
            doc = MONGO_GENERATION === 1 ? result : result.value;
            if (!doc) {
              return options.error(new Error("Failed to update model (" + _this.model_type.model_name + "). Either the document has been deleted or the revision (_rev) was stale."));
            }
            if (doc._rev !== json._rev) {
              return options.error(new Error("Failed to update revision (" + _this.model_type.model_name + "). Is: " + doc._rev + " expecting: " + json._rev));
            }
            return options.success(_this.backbone_adapter.nativeToAttributes(doc));
          });
        };
      })(this));
    };

    MongoSync.prototype["delete"] = function(model, options) {
      return this.deleteCB(model, (function(_this) {
        return function(err) {
          if (err) {
            return options.error(err);
          } else {
            return options.success();
          }
        };
      })(this));
    };

    MongoSync.prototype.deleteCB = function(model, callback) {
      return this.connection.collection((function(_this) {
        return function(err, collection) {
          if (err) {
            return options.error(err);
          }
          return collection.remove(_this.backbone_adapter.attributesToNative({
            id: model.id
          }), function(err) {
            if (err) {
              return callback(err);
            } else {
              return Utils.patchRemove(_this.model_type, model, callback);
            }
          });
        };
      })(this));
    };

    MongoSync.prototype.resetSchema = function(options, callback) {
      return this.db().resetSchema(options, callback);
    };

    MongoSync.prototype.cursor = function(query) {
      if (query == null) {
        query = {};
      }
      return new MongoCursor(query, _.pick(this, ['model_type', 'connection', 'backbone_adapter']));
    };

    MongoSync.prototype.destroy = function(query, callback) {
      var _ref1;
      if (arguments.length === 1) {
        _ref1 = [{}, query], query = _ref1[0], callback = _ref1[1];
      }
      return this.connection.collection((function(_this) {
        return function(err, collection) {
          if (err) {
            return callback(err);
          } else {
            return _this.model_type.each(_.extend({
              $each: {
                limit: DESTROY_BATCH_LIMIT,
                json: true
              }
            }, query), _this.deleteCB, callback);
          }
        };
      })(this));
    };

    MongoSync.prototype.connect = function(url) {
      if (this.connection && this.connection.url === url) {
        return;
      }
      if (this.connection) {
        this.connection.destroy();
      }
      return this.connection = new Connection(url, this.schema, this.sync_options);
    };

    MongoSync.prototype.collection = function(callback) {
      return this.connection.collection(callback);
    };

    MongoSync.prototype.db = function() {
      return this.db_tools || (this.db_tools = new DatabaseTools(this));
    };

    MongoSync.prototype._selectAdapter = function() {
      var field_info, field_name, info, _i, _len, _ref1;
      _ref1 = this.schema.raw;
      for (field_name in _ref1) {
        field_info = _ref1[field_name];
        if ((field_name !== 'id') || !_.isArray(field_info)) {
          continue;
        }
        for (_i = 0, _len = field_info.length; _i < _len; _i++) {
          info = field_info[_i];
          if (info.manual || info.manual_id) {
            this.manual_id = true;
            return require('./lib/document_adapter_no_mongo_id');
          }
        }
      }
      return require('./lib/document_adapter_mongo_id');
    };

    return MongoSync;

  })();

  module.exports = function(type, sync_options) {
    var model_type, sync, sync_fn;
    if (sync_options == null) {
      sync_options = {};
    }
    if (Utils.isCollection(new type())) {
      model_type = Utils.configureCollectionModelType(type, module.exports);
      return type.prototype.sync = model_type.prototype.sync;
    }
    sync = new MongoSync(type, sync_options);
    type.prototype.sync = sync_fn = function(method, model, options) {
      if (options == null) {
        options = {};
      }
      sync.initialize();
      if (method === 'createSync') {
        return module.exports.apply(null, Array.prototype.slice.call(arguments, 1));
      }
      if (method === 'sync') {
        return sync;
      }
      if (method === 'schema') {
        return sync.schema;
      }
      if (method === 'isRemote') {
        return false;
      }
      if (sync[method]) {
        return sync[method].apply(sync, Array.prototype.slice.call(arguments, 1));
      } else {
        return void 0;
      }
    };
    Utils.configureModelType(type);
    return BackboneORM.model_cache.configureSync(type, sync_fn);
  };

  module.exports.capabilities = function(url) {
    return CAPABILITIES;
  };

}).call(this);
