/**
 * Database module object.
 * @abstract
 * @constructor lib.db.Model
 * @param {object} data - Property data.
 * @author Taka Okunishi
 *
 */

var object = require('../../lib/object'),
    define = object.define,
    copy = object.copy,
    deepCopy = object.deepCopy,
    Diff = object.Diff;

exports = module.exports = define({
    init: function (data) {
        var s = this;
        deepCopy(s.defaultValue, s);
        if (data) {
            deepCopy(data, s);
            var _id = data._id;
            if (_id) s._id = _id.toString();
        }
    },
    /** @lends lib.db.Model.prototype **/
    properties: {
        /**
         * Database connection.
         * @type {object}
         * @protected
         * @ignore
         **/
        _connection: null,
        /**
         * Insert this model.
         * @param {function} callback - Callback when done.
         * @returns this
         */
        insert: function (callback) {
            var s = this,
                proto = s.__proto__;
            s.__proto__ = null;
            s._vr = 0;
            if (s._id) {
                s._id = exports._newObjectId(s._id);
            } else {
                delete s._id;
            }
            return proto._connection.save(s, function (err, data) {
                if (err) {
                    callback && callback.call(s, err);
                    return;
                }
                s.__proto__ = proto;
                s._id = data._id;
                callback && callback.call(s, null, s);
            });
        },
        /**
         * Update this model.
         * @param {function} callback - Callback when done.
         * @returns this
         */
        update: function (callback) {
            var s = this,
                proto = s.__proto__;
            var _id = exports._newObjectId(s._id.toString());
            s.__proto__ = null;
            delete s._id;
            s._vr = parseInt(s._vr) + 1;
            return proto._connection.update({
                _id: _id
            }, {
                $set: s
            }, {
                multi: false
            }, function (err) {
                if (err) {
                    callback && callback.call(s, err);
                    return;
                }
                s._id = _id;
                s.__proto__ = proto;
                callback && callback.call(s, null, s);
            });
        },
        /**
         * Remove this model.
         * @param {function} callback - Callback when done.
         * @returns this
         */
        destroy: function (callback) {
            var s = this,
                proto = s.__proto__;
            var id = exports._newObjectId(s._id.toString());
            s.__proto__ = null;
            return proto._connection.remove({
                _id: id
            }, s, function (err) {
                if (err) {
                    callback && callback(err);
                    return;
                }
                s.__proto__ = proto;
                callback && callback.call(s, null);
            });
        },
        diff: function (data) {
            var s = this;
            return new Diff(s, data);
        }
    }
});


/**
 * Find a module by id.
 * @param {string|object} id - Model Id.
 * @param {function(Error, lib.db.Model)} callback - Callback with found model.
 */
exports.findById = function (id, callback) {
    var s = this;
    if (!id) {
        return callback && callback(null, null);
    }
    return s.findOneByCondition({
        _id: exports._newObjectId(id.toString())
    }, function (err, model) {
        callback && callback(err, model);
    });
};
/**
 * Find all models
 * @param {function(Error, lib.db.Model[])} callback - Callback with found models.
 */
exports.findAll = function (callback) {
    var s = this;
    return s.findByCondition({}, function (err, models) {
        callback && callback(err, models)
    });
};
/**
 * Find models by a condition.
 * @param {object} condition - A search condition.
 * @param {function(Error, lib.db.Model[])} callback - Callback with found models.
 * @returns {*|}
 */
exports.findByCondition = function (condition, callback) {
    var s = this;
    return s.prototype._connection.find(condition, function (err, data) {
        if (err) {
            callback && callback.call(s, err);
            return;
        }
        var models = data && data.map(function (data) {
            if (!data) return undefined;
            var model = new s(data);
            model._id = data._id && exports._newObjectId(data._id.toString());
            return model;
        });
        callback && callback.call(s, null, models);
    });
};
/**
 * Find a model by a condition.
 * @param {object} condition - A search condition.
 * @param {function(Error, lib.db.Model)} callback - Callback with a found model.
 */
exports.findOneByCondition = function (condition, callback) {
    var s = this,
        connection = s.prototype._connection;
    return connection.findOne(condition, function (err, data) {
        if (err) {
            callback && callback(err, null);
            return;
        }
        if (!data) {
            callback && callback.call(s, null);
            return;
        }
        var model = new s(data);
        model._id = exports._newObjectId(data._id.toString());
        callback && callback.call(s, null, model);
    });
};
/**
 * Count all model.
 * @param {function(Error, number)} callback - Callback when done.
 */
exports.countAll = function (callback) {
    var s = this;
    return s.countByCondition({}, function (err, count) {
        callback && callback(err, count);
    });
};
/**
 * Count models by a condition
 * @param {object} condition - A count condition.
 * @param {function} callback - Callback with the count.
 * @returns {*|null}
 */
exports.countByCondition = function (condition, callback) {
    var s = this,
        connection = s.prototype._connection;
    return connection.count(condition, function (err, count) {
        if (err) {
            callback && callback.call(s, err);
            return;
        }
        callback && callback.call(s, null, count);
    });
};
/**
 * Save all models.
 * @param {object[]} models - Models to save.
 * @param {function} callback - Callback when done.
 */
exports.insertAll = function (models, callback) {
    require('async').series(
        models.map(function (model) {
            return function (callback) {
                model.insert(function (err) {
                    callback(err);
                });
            }
        }),
        function (err) {
            callback(err);
        }
    );
};

/**
 * Update all models.
 * @param {array} models - Models to update.
 * @param {function} callback - Callback when done.
 */
exports.updateAll = function (models, callback) {
    require('async').series(
        models.map(function (model) {
            return function (callback) {
                model.update(callback);
            }
        }),
        function (err) {
            callback && callback(err);
        }
    );
};
/**
 * Destroy all models.
 * @param {lib.db.Model[]} models - Models to destroy.
 * @param {function} callback - Callback when done.
 */
exports.destroyAll = function (models, callback) {
    var $1 = arguments[0];
    if (typeof($1) === 'function') {
        callback = $1;
        exports.findAll(function (models) {
            exports.destroyAll(models, callback);
        });
        return;
    }
    require('async').series(
        models.map(function (model) {
            return function (callback) {
                model.remove(callback);
            }
        }),
        function (err) {

            callback && callback(err);
        });
};


exports._newObjectId = function (data) {
    var ObjectId = require('mongojs').ObjectId;
    return new ObjectId(data);
};

exports.ensureIndex = function (config, callback) {
    var s = this,
        connection = s.prototype._connection;
    connection.ensureIndex.apply(connection, arguments);
};

exports.getIndexes = function (callback) {
    var s = this,
        connection = s.prototype._connection;
    connection.getIndexes.apply(connection, arguments);
};