var fs = require("fs");
var crypto = require("crypto");
var exec = require("child_process").exec;

// Third-party deps
var colors = require("colors");
var ini = require("ini");
var knox = require("knox");
var wrench = require("wrench");
var Bagpipe = require("bagpipe");

// We use bagpipe in order to throttle the async file system calls
// If you open to many files at once, node with throw EMFILE errors
var bag = new Bagpipe(20);

// Local map of content keys for looking up content details
var bucket = {};

var logError = function (message) {
	console.error("ERROR:".red, message);
};

var logInfo = function (message) {
	console.info(message.grey);
};

var die = function (message) {
	logError(message);
	console.error("ABORT:".red, "Poole aborted due to the errors above");
	process.exit();
};

var configPath = ".poole.ini";
if (!fs.existsSync(configPath)) {
	die("No .poole.ini found!");
}

var config = ini.parse(fs.readFileSync(configPath, "utf-8"));

// We use the know utility for interacting with S3
// TODO: Set config from external source
var client = knox.createClient({
	key: config.key,
	secret: config.secret,
	region: config.region,
	bucket: config.bucket
});

var build = function (outputFolder, callback) {
	var command = "jekyll --no-auto --no-server " + outputFolder;

	console.log("INFO:".blue, "Building with Jekyll...");
	var child = exec(command, function (err, stdout, stderr) {
		if (err) { die(err.message); }
		if (stderr) { die(stderr); }

		if (stdout) { logInfo(stdout); }
		console.info("INFO:".blue, "Jekyll build succeeded");
		callback && callback();
	});
};

var checkBucket = function (outputFolder, callback) {
	var onEnd = function () {
		callback && callback();
	};

	client.list(function (err, data) {
		// TODO: Check for errors
		var contents = data.Contents;
		var count = 0;

		if (contents.length < 1) {
			onEnd();
			return;
		}

		for (var i = 0, j = contents.length; i < j; ++i) {
			var item = contents[i];
			bucket[item.Key] = item;
			++count;
			bag.push(processBucketItem, item, outputFolder, function () {
				--count;
				count <= 0 && onEnd();
			});
		}
	});
};

var processBucketItem = function (item, outputFolder, callback) {
	var file = outputFolder + "/" + item.Key;

	fs.exists(file, function (exists) {
		if (exists) {
			// Hash file contents and compare against stored value
			var hash = crypto.createHash("md5");
			var stream = fs.ReadStream(file);

			stream.on("data", function (data) {
				hash.update(data);
			});

			stream.on("end", function () {
				var local = hash.digest("hex");
				var remote = item.ETag.replace(/"/g, "");
				item.dirty = (local !== remote);
				callback && callback();
			});
		} else {
			// Delete from the bucket
			var fileName = "/" + encodeURIComponent(item.Key);
			client.deleteFile(fileName, function (err, res) {
				// TODO: Handle errors

				console.info("DELETE:".red, item.Key, String(res.statusCode).grey);
				callback && callback();
			});
		}
	});
};

var checkLocal = function (outputFolder, callback) {
	wrench.readdirRecursive(outputFolder, function (err, files) {
		if (!files) { return; }

		for (var i = 0, j = files.length; i < j; ++i) {
			bag.push(processLocalItem, files[i], outputFolder, function () {
				// Thing was processed
			});
		}
	});
};

var processLocalItem = function (path, outputFolder, callback) {
	var file = outputFolder + "/" + path;

	fs.stat(file, function (err, stats) {
		if (!stats || !stats.isFile()) {
			callback && callback();
			return;
		}

		var bucketItem = bucket[path];
		if (!bucketItem || bucketItem.dirty) {
			client.putFile(file, "/" + path, {
				"x-amz-acl": "public-read"
			}, function (err, res) {
				console.info("PUT:".green, path, String(res.statusCode).grey);
				callback && callback();
			});
		} else {
			callback && callback();
		}
	});
};

var output = "/tmp/poole-" + Date.now();

build(output, function (success) {
	checkBucket(output, function () {
		checkLocal(output, function () {

		});
	});
});

// TODO: Cleanup tmp directory!

@@ -94,29 +94,8 @@
 xec;
-%0A%0A// Third-party deps
 %0Avar
@@ -356,18 +356,18 @@
  node wi
-th
+ll
  throw E
@@ -3006,16 +3006,62 @@
 back) %7B%0A
+%09var totalFiles = 0;%0A%09var totalProcessed = 0;%0A
 %09wrench.
@@ -3142,16 +3142,46 @@
 urn; %7D%0A%0A
+%09%09totalFiles += files.length;%0A
 %09%09for (v
@@ -3298,30 +3298,100 @@
 %09%09%09%09
-// Thing was processed
+++totalProcessed;%0A%09%09%09%09if (totalProcessed %3E= totalFiles) %7B%0A%09%09%09%09%09callback && callback();%0A%09%09%09%09%7D
 %0A%09%09%09
@@ -4036,121 +4036,240 @@
 %7B%0A%09c
-heckBucket(output, function () %7B%0A%09%09checkLocal(output, function () %7B%0A%0A%09%09%7D
+onsole.info(%22INFO:%22.blue, %22Pushing to S3 bucket: %22 + config.bucket);%0A%09checkBucket(output, function () %7B%0A%09%09checkLocal(output, function () %7B%0A%09%09%09wrench.rmdirSyncRecursive(output);%0A%09%09%09console.info(%22INFO:%22.blue, %22Finished!%22
 );%0A
+%09
 %09%7D);%0A
+%09
 %7D);%0A
-%0A// TODO: Cleanup tmp directory!
+%7D);
 %0A

Remove tmp folder on complete