#!/usr/bin/env node
'use strict';

var async = require('async'),
    path = require('path');

/**
 * Expose the functions that make the DSL to the given context.
 */
function seedContext(ctx) {
    var buckets = {},
        lastObj;

    /**
     * Define a new bucket, the returned value is a function that creates
     * members of the bucket.
     */
    ctx.bucket = function (name) {
        var bucket = buckets[name];

        if (!bucket) {
            buckets[name] = bucket = function (key, data, fun) {
                if (typeof data === 'function') {
                    fun = data;
                    data = undefined;
                }

                lastObj = bucket[key];

                if (!lastObj) {
                    bucket[key] = lastObj = {
                        key: key,
                        bucket: name,
                        data: data || {},
                        links: []
                    };
                } else if (data) {
                    throw new Error("Trying to override data of " + bucket + "/" + key);
                }

                // Run callback in context of data object.
                if (fun) {
                    fun.call(lastObj.data);
                }

                return lastObj;
            };
        }

        return bucket;
    };

    /**
     * Configure a link from the object currently being defined.
     *
     * The link can either be in the form of a item as returned by calling a
     * bucket, or a list with [bucket, key], or any object defining key, bucket
     * and optionally tag.
     *
     * The optional 2nd argument will be used as the tag of the link when used.
     */
    ctx.link = function (link, tag) {
        if (!lastObj) {
            throw new Error("link called with no active object");
        }

        if (!link) {
            throw new Error("link called without arguments");
        }

        if (link.bucket && link.key) {
            link = {
                bucket: link.bucket,
                key: link.key,
            };
        } else {
            link = {
                bucket: link[0],
                key: link[1]
            };
        }

        if (tag) {
            link.tag = tag;
        }

        lastObj.links.push(link);
    };

    return buckets;
}

/**
 * Iterate over each item in each bucket.
 */
function iterateContextAsync(context, iterator, done) {
    var workers = 4;

    async.eachSeries(Object.keys(context), function (name, callback) {
        var bucket = context[name];

        async.eachLimit(Object.keys(bucket), workers, function (key, callback) {
            var obj = bucket[key];
            iterator(name, obj, callback);
        }, callback);
    }, done);
}

/**
 * Execute a seed file and save the defined objects to RIAK.
 */
function processFile(file, riak, options, callback) {
    var abspath = path.resolve(file),
        buckets;

    function saveOne(bucket, obj, callback) {
        if (options.verbose) {
            console.error('Saving ' + bucket + '/' + obj.key);
        }

        var meta = {links: obj.links};
        riak.save(bucket, obj.key, obj.data, meta, function (err) {
            if (err) {
                console.error(err);
            }
            callback();
        });
    }

    buckets = seedContext(global);
    require(abspath);
    iterateContextAsync(buckets, saveOne, callback);
}

/**
 * Script behaviour
 */
function main() {
    var riak = require('riak-js'),
        argv,
        url,
        client;

    argv = require('optimist')
        .usage('Seeds a RIAK database from a description file\nUsage: $0 host:port genesis-file')
        .demand(2)
        .alias('v', 'verbose')
        .argv;

    url = argv._[0].split(':');
    client = riak.getClient({host: url[0], port: url[1]});

    // Load coffee-script to get the require hook installed.
    try {
        require('coffee-script');
    } catch (e) {
        if (argv.verbose) {
            console.error('Could not load coffee-script');
            console.error('genesis files in coffee-script not supported');
        }
    }

    processFile(argv._[1], client, {verbose: argv.verbose});
}

module.exports = {
    processFile: processFile,
    iterateContextAsync: seedContext,
    seedContext: seedContext
};

// Do stuff if this is the main module.
if (require.main === module) {
    main();
}

@@ -378,16 +378,28 @@
 on (name
+, properties
 ) %7B%0A    
@@ -424,24 +424,50 @@
 uckets%5Bname%5D
+,%0A            objects = %7B%7D
 ;%0A%0A        i
@@ -706,30 +706,31 @@
   lastObj = 
-bucket
+objects
 %5Bkey%5D;%0A%0A    
@@ -777,22 +777,23 @@
         
-bucket
+objects
 %5Bkey%5D = 
@@ -1305,32 +1305,121 @@
 ;%0A            %7D;
+%0A%0A            bucket.objects = objects;%0A            bucket.properties = properties %7C%7C %7B%7D;
 %0A        %7D%0A%0A    
@@ -2517,24 +2517,37 @@
 ontext, 
-iterator
+eachBucket, eachValue
 , done) 
@@ -2675,19 +2675,108 @@
 xt%5Bname%5D
-;%0A%0A
+,%0A            objects = bucket.objects;%0A%0A        eachBucket(name, bucket, function () %7B%0A    
         
@@ -2803,22 +2803,23 @@
 ct.keys(
-bucket
+objects
 ), worke
@@ -2857,24 +2857,28 @@
             
+    
 var obj = bu
@@ -2875,22 +2875,23 @@
 r obj = 
-bucket
+objects
 %5Bkey%5D;%0A 
@@ -2905,16 +2905,21 @@
     
-iterator
+    eachValue
 (nam
@@ -2933,24 +2933,28 @@
  callback);%0A
+    
         %7D, c
@@ -2959,24 +2959,36 @@
  callback);%0A
+        %7D);%0A
     %7D, done)
@@ -3188,18 +3188,420 @@
 ion save
-On
+Bucket(name, bucket, callback) %7B%0A        if (!bucket.properties) %7B%0A            return callback();%0A        %7D%0A%0A        if (options.verbose) %7B%0A            console.error('Configuring ' + name);%0A        %7D%0A%0A        riak.saveBucket(name, bucket.properties, function (err) %7B%0A            if (err) %7B%0A                console.error(err);%0A            %7D%0A            callback();%0A        %7D);%0A    %7D%0A%0A    function saveValu
 e(bucket
@@ -4045,10 +4045,24 @@
 save
-On
+Bucket, saveValu
 e, c

make bucket properties configurable