var mongo = require('mongodb');

/**
 * Default connection details.
 */

var defaults = {
    host: '127.0.0.1'
  , port: 27017
  , database: 'default'
};

/**
 * Create a MongoDB database connection.
 *
 * @param {Object|String} config (optional)
 * @param {Object} options (optional)
 * @api public
 */

var Database = exports.Database = function (config, options) {
    var database, host, port, self = this;

    config = config || {};
    options = options || {
        auto_reconnect: true
      , poolSize: 8
    };

    if (config.mongodb) {
        config = config.mongodb;
    }
    if (typeof config === 'object') {
        host = config.host || defaults.host;
        port = config.port || defaults.port;
        database = config.database || defaults.database;
    } else {
        //Parse (host)(:port)(/database)
        var match = config.match(/([^:\/]+)?(\:[0-9]+)?(\/.+)?/);
        host = match[1] || defaults.host;
        port = match[2] ? parseInt(match[2].substr(1), 10) : defaults.port;
        database = match[3] ? match[3].substr(1) : defaults.database;
    }

    this.db = new mongo.Db(database, new mongo.Server(host, port, options || {}));
};

/**
 * Get a MongoDB collection.
 *
 * @return {Collection} collection
 * @api public
 */

Database.prototype.collection = function (name) {
    return new Collection(this.db, name);
};

/**
 * A MongoDB collection.
 *
 * @param {Mongodb.Database} db
 * @param {String} name
 * @api public
 */

var Collection = exports.Collection = function (db, name) {
    this.db = db;
    this.name = name;
};

/**
 * Proxy calls to the underlying collection so that they're buffered
 * until a connection is made.
 */

Object.keys(mongo.Collection.prototype).forEach(function (method) {
    Collection.prototype[method] = function () {
        var args = Array.prototype.slice.call(arguments)
          , callback = args[args.length - 1]
          , db = this.db, name = this.name;
        db.open(function (err) {
            if (err) return callback(err);
            db.collection(name, function (err, collection) {
                if (err) return callback(err);
                collection[method].apply(collection, args);
            });
        });
    };
});

/**
 * Provide a helper for finding documents but managing concurrency while
 * iterating over the cursor.
 *
 * @param {Object} query (optional)
 * @param {Number} concurrent - how many calls to callback before requiring next()
 * @param {Function} callback - receives (err, doc, next)
 * @api public
 */

Collection.prototype.concurrent = function (query, concurrent, callback) {
    if (arguments.length === 2) {
        callback = concurrent;
        concurrent = query;
        query = {};
    }
    this.find(query, function (err, cursor) {
        if (err) return callback(err);
        var complete;
        function next() {
            cursor.nextObject(function (err, obj) {
                if (complete) {
                    return;
                } else if (err) {
                    complete = true;
                    return callback(err);
                }
                callback(null, obj, function () {
                    process.nextTick(next);
                });
            });
        }
        while (concurrent--) next();
    });
};

/**
 * Convenience method for creating a connection.
 *
 * @param {Object|String} config
 * @return {Database} connection
 * @api public
 */

exports.connect = function (config, options) {
    return new Database(config, options);
};

