"use strict";

var Q = require('q'),
    _ = require('underscore'),
    log = require('plog')('mambo.inserter');

function Inserter(model, alias){
    this.model = model;
    this.schema = this.model.schema(alias);
    this.alias = alias;

    this.expectations = {};
    this.ops = {};
}

Inserter.prototype.shouldExist = function(name){
    return this.expect(name, true);
};

Inserter.prototype.shouldNotExist = function(name){
    return this.expect(name, false);
};

Inserter.prototype.shouldEqual = function(name, value){
    return this.expect(name, true, value);
};

Inserter.prototype.expect = function(name, exists, value){
    var field = this.schema.field(name);

    this.expectations[name] = {};
    if(value !== undefined && !exists){
        exists = true;
    }
    this.expectations[name].Exists = exists;
    if(value !== undefined && exists === true){
        this.expectations[name].Value = field.export(value);
    }
    return this;
};

Inserter.prototype.from = function(obj){
    for(var key in obj){
        if(this.schema.hasField(key)){
            this.ops[key] = obj[key];
        }
    }
    return this;
};

Inserter.prototype.set = function(opts){
    for(var key in opts){
        this.ops[key] = opts[key];
    }
    return this;
};

Inserter.prototype.commit = function(){
    var d = Q.defer();

    this.model.put(this.alias, this.ops,
        this.expectations, false, function(err, data){
            if(err){
                return d.reject(err);
            }
            d.resolve(data);
        }
    );
    return d.promise;
};

Inserter.prototype.insert = function(alias){
    // Upgrade to batch inserter
    // @todo (lucas) This should use batch.Batch.
    if(Object.keys(this.expectations).length > 0){
        throw new Error("Can't upgrade to a batch with expectations.");
    }

    var i = new BatchInserter(this.model);
    i.fromInserter(this);
    i.insert(alias);
    return i;
};

function BatchInserter(model){
    this.model = model;
    this.lastAlias = null;
    this.puts = {};
    this.numOps = 0;
    this.chunkSize = 25;
}

BatchInserter.prototype.chunk = function(s){
    this.chunkSize = s;
    return this;
};

BatchInserter.prototype.insert = function(alias){
    this.lastAlias = alias;
    if(!this.puts.hasOwnProperty(alias)){
        this.puts[alias] = [];
    }
    return this;
};

BatchInserter.prototype.fromInserter = function(inserter){
    this.insert(inserter.alias);
    this.set(inserter.ops);
    return this;
};

BatchInserter.prototype.set = function(ops){
    this.puts[this.lastAlias].push(ops);
    this.numOps++;
    return this;
};

BatchInserter.prototype.commit = function(){
    var d = Q.defer(),
        self = this;

    if(this.numOps <= this.chunkSize){
        log.debug('Committing single batch');
        return this.model.batchWrite(this.puts, {});
    }
    else {
        // Split the work into chunks of 25.
        var chunks = [],
            index = 0,
            item,
            chunkSizes = [0];

        Object.keys(this.puts).forEach(function(alias){
            while(item = self.puts[alias].shift()){
                if(chunkSizes[index] === self.chunkSize){
                    index++;
                    chunkSizes[index] = 0;
                }
                var chunk = chunks[index];

                if(!chunk){
                    chunks[index] = {};
                }
                if(!chunks[index][alias]){
                    chunks[index][alias] = [];
                }
                chunks[index][alias].push(item);
                chunkSizes[index]++;
            }
        });

        log.debug('Committing batch in `'+chunks.length+'` chunks');

        return Q.all(chunks.map(function(chunk){
            return self.model.batchWrite(chunk, {});
        })).then(function(results){
            log.debug('All chunks committed');
            var result = {
                'success': {},
                'unprocessed': {}
            };
            results.forEach(function(r){
                for(var alias in r.success){
                    if(!result.success.hasOwnProperty(alias)){
                        result.success[alias] = 0;
                    }
                    result.success[alias] += r.success[alias];
                }

                for(var tableName in r.unprocessed){
                    if(!result.unprocessed.hasOwnProperty(tableName)){
                        result.unprocessed[tableName] = [];
                    }
                    r.unprocessed[tableName].forEach(function(i){
                        result.unprocessed[tableName].push(i);
                    });
                }
            });
            return result;
        });
    }
};

module.exports = Inserter;
@@ -12,17 +12,21 @@
 %22;%0A%0Avar 
-Q
+async
  = requi
@@ -33,40 +33,13 @@
 re('
-q'),%0A    _ = require('underscore
+async
 '),%0A
@@ -1280,34 +1280,14 @@
 ion(
-)%7B%0A    var d = Q.defer();%0A
+done)%7B
 %0A   
@@ -1362,168 +1362,13 @@
 se, 
-function(err, data)%7B%0A            if(err)%7B%0A                return d.reject(err);%0A            %7D%0A            d.resolve(data);%0A        %7D%0A    );%0A    return d.promise
+done)
 ;%0A%7D;
@@ -2463,16 +2463,20 @@
 unction(
+done
 )%7B%0A    v
@@ -2482,31 +2482,8 @@
 var 
-d = Q.defer(),%0A        
 self
@@ -2623,24 +2623,30 @@
 his.puts, %7B%7D
+, done
 );%0A    %7D%0A   
@@ -3484,19 +3484,21 @@
     
-return Q.al
+async.paralle
 l(ch
@@ -3542,16 +3542,52 @@
  return 
+function(callback)%7B%0A                
 self.mod
@@ -3609,16 +3609,26 @@
 hunk, %7B%7D
+, callback
 );%0A     
@@ -3634,17 +3634,27 @@
     
-%7D)).then(
+    %7D;%0A        %7D), 
 func
@@ -3658,16 +3658,21 @@
 unction(
+err, 
 results)
@@ -3716,24 +3716,93 @@
 ommitted');%0A
+            if(err)%7B%0A                return done(err);%0A            %7D%0A
             
@@ -4609,21 +4609,26 @@
     
-return
+done(null,
  result
+)
 ;%0A  

Switch to async