/**
 * Various build tools for Jake.
 */

/*jshint smarttabs:true, undef:true, node:true, latedef:true, curly:true, bitwise:true */
"use strict";

var fs = require("fs");
var path = require("path");
var child_process = require("child_process");

function extend(a, b) {
	if (b) {
		for (var name in b) {
			a[name] = b[name];
		}
	}

	return a;
}

function getFileModTime(filePath) {
	return fs.existsSync(filePath) ? fs.statSync(filePath).mtime.getTime() : 0;
}

function setFileModTime(filePath, time) {
	return fs.utimesSync(filePath, new Date(time), new Date(time));
}

exports.uglify = function(options) {
	var UglifyJS = require("uglify-js");
	var filePaths = [];

	options = extend({
		mangle : true,
		toplevel : false,
		no_functions : false,
		ascii_only: true
	}, options);

	var toFileModTime = getFileModTime(options.to);
	var fromFileModTime = 0;

	// Combine JS files
	if (options.from instanceof Array) {
		options.from.forEach(function(filePath) {
			if (options.sourceBase) {
				filePath = path.join(options.sourceBase, filePath);
			}

			filePaths.push(filePath);

			fromFileModTime = Math.max(fromFileModTime, getFileModTime(filePath));
		});
	} else {
		filePaths.push(options.from);
		fromFileModTime = getFileModTime(options.from);
	}

	if (options.force === true || fromFileModTime !== toFileModTime) {
		var result = UglifyJS.minify(filePaths, {
		});

		fs.writeFileSync(options.to, result.code);
		setFileModTime(options.to, fromFileModTime);
	}
};

exports.less = function (options) {
	var source = "", lastMod = 0, less = require('less');

	var sourceFile = options.from;
	var outputFile = options.toCss;

	options = extend({
		compress: true,
		yuicompress: true,
		optimization: 1,
		silent: false,
		paths: [],
		color: true,
		strictImports: false
	}, options);

	var parser = new less.Parser({
		paths: [options.baseDir || path.dirname(sourceFile)],
		filename: path.basename(sourceFile),
		optimization: options.optimization,
		strictImports: options.strictImports
	});

	// Patch over BOM bug
	// Todo: Remove this when they fix the bug
	less.Parser.importer = function (file, paths, callback, env) {
		var pathname;

		paths.unshift('.');

		for (var i = 0; i < paths.length; i++) {
			try {
				pathname = path.join(paths[i], file);
				fs.statSync(pathname);
				break;
			} catch (e) {
				pathname = null;
			}
		}

		if (pathname) {
			fs.readFile(pathname, 'utf-8', function(e, data) {
				if (e) {
					return callback(e);
				}

				data = data.replace(/^\uFEFF/, '');

				new(less.Parser)({
					paths: [path.dirname(pathname)].concat(paths),
					filename: pathname
				}).parse(data, function (e, root) {
					callback(e, root, data);
				});
			});
		} else {
			if (typeof(env.errback) === "function") {
				env.errback(file, paths, callback);
			} else {
				callback({ type: 'File', message: "'" + file + "' wasn't found.\n" });
			}
		}
	};

	// Parse one or multiple files
	if (sourceFile instanceof Array) {
		sourceFile.forEach(function(sourceFile) {
			lastMod = Math.max(lastMod, getFileModTime(path.join(options.baseDir, sourceFile)));
		});

		if (options.force !== true && lastMod === getFileModTime(outputFile)) {
			return;
		}

		sourceFile.forEach(function(sourceFile) {
			source += fs.readFileSync(path.join(options.baseDir, sourceFile), 'utf-8').toString().replace(/^\uFEFF/g, '');
		});

		if (options.toLess) {
			var lessImportCode = "";

			sourceFile.forEach(function(sourceFile) {
				lessImportCode += '@import "' + sourceFile + '";\n';
			});

			fs.writeFileSync(options.toLess, lessImportCode);
		}
	} else {
		lastMod = getFileModTime(sourceFile);
		if (options.force !== true && lastMod === getFileModTime(outputFile)) {
			return;
		}

		source = fs.readFileSync(sourceFile).toString();
	}

	parser.parse(source, function (err, tree) {
		if (err) {
			less.writeError(err, options);
			return;
		}

		fs.writeFileSync(outputFile, tree.toCSS({
			compress: options.compress,
			yuicompress: options.yuicompress
		}));

		setFileModTime(outputFile, lastMod);
	});
};

exports.yuidoc = function (sourceDir, outputDir, options) {
	var Y = require('yuidocjs');

	if (!(sourceDir instanceof Array)) {
		sourceDir = [sourceDir];
	}

	options = extend({
		paths: sourceDir,
		outdir: outputDir,
		time: false
	}, options);

	var starttime = new Date().getTime();
	var json = (new Y.YUIDoc(options)).run();

	var builder = new Y.DocBuilder(options, json);
	builder.compile(function() {
		var endtime = new Date().getTime();

		if (options.time) {
			Y.log('Completed in ' + ((endtime - starttime) / 1000) + ' seconds' , 'info', 'yuidoc');
		}
	});
};

exports.jshint = function (options) {
	var jshint = require('jshint').JSHINT, exclude;

	function removeComments(str) {
		str = str || "";
		str = str.replace(/\/\*(?:(?!\*\/)[\s\S])*\*\//g, "");
		str = str.replace(/\/\/[^\n\r]*/g, ""); // Everything after '//'

		return str;
	}

	options = options || {};

	var color = function(s, c){
		return (color[c].toLowerCase()||'') + s + color.reset;
	};

	color.reset = '\u001b[39m';
	color.red = '\u001b[31m';
	color.yellow = '\u001b[33m';
	color.green = '\u001b[32m';

	if (fs.existsSync(".jshintrc")) {
		options = extend(JSON.parse(removeComments("" + fs.readFileSync(".jshintrc"))), options);
	}

	if (options.exclude) {
		exclude = options.exclude;
		delete options.exclude;
	}

	function process(filePath) {
		var stat = fs.statSync(filePath);

		// Don't hint on minified files
		if (/\.min\.js$/.test(filePath)) {
			return;
		}

		if (exclude && exclude.indexOf(filePath) != -1) {
			return;
		}

		if (/\.js$/.test(filePath)) {
			if (!jshint(fs.readFileSync(filePath).toString(), options)) {
				// Print the errors
				console.log(color('Errors in file ' + filePath, 'red'));
				var out = jshint.data(),
				errors = out.errors;
				Object.keys(errors).forEach(function(error){
					error = errors[error];

					if (error) {
						console.log('line: ' + error.line + ':' + error.character+ ' -> ' + error.reason );
						console.log(color(error.evidence,'yellow'));
					}
				});
			}
		}
	}

	var patterns = options.patterns;
	delete options.patterns;
	patterns.forEach(function(filePath) {
		require("glob").sync(filePath).forEach(process);
	});
};

exports.zip = function (options) {
	var ZipWriter = require('moxie-zip').ZipWriter;
	var archive = new ZipWriter();

	function process(filePath, zipFilePath) {
		var args, stat = fs.statSync(filePath);

		zipFilePath = zipFilePath || filePath;
		filePath = filePath.replace(/\\/g, '/');
		zipFilePath = zipFilePath.replace(/\\/g, '/');

		if (options.pathFilter) {
			args = {filePath: filePath, zipFilePath: zipFilePath};
			options.pathFilter(args);
			zipFilePath = args.zipFilePath;
		}

		if (options.exclude) {
			for (var i = 0; i < options.exclude.length; i++) {
				var pattern = options.exclude[i];

				if (pattern instanceof RegExp) {
					if (pattern.test(filePath)) {
						return;
					}
				} else {
					if (filePath === pattern) {
						return;
					}
				}
			}
		}

		if (stat.isFile()) {
			var data = fs.readFileSync(filePath);

			if (options.dataFilter) {
				args = {filePath: filePath, zipFilePath: zipFilePath, data: data};
				options.dataFilter(args);
				data = args.data;
			}

			archive.addData(path.join(options.baseDir, zipFilePath), data);
		} else if (stat.isDirectory()) {
			fs.readdirSync(filePath).forEach(function(fileName) {
				process(path.join(filePath, fileName), path.join(zipFilePath, fileName));
			});
		}
	}

	options.baseDir = (options.baseDir || '').replace(/\\/g, '/');

	options.from.forEach(function(filePath) {
		if (filePath instanceof Array) {
			process(filePath[0], filePath[1]);
		} else {
			process(filePath);
		}
	});

	archive.saveAs(options.to);
};

exports.compileAmd = function (options) {
	//options.verbose = true;
	require("amdlc").compile(options);
};

exports.parseLessDocs = function (filePath) {
	var matches, docCommentRegExp = /\/\*\*([\s\S]+?)\*\//g, lessFiles = [];
	var source = fs.readFileSync(filePath).toString();

	for (matches = docCommentRegExp.exec(source); matches; matches = docCommentRegExp.exec(source)) {
		var docComment = matches[1];

		var lessMatch = /\@\-x\-less\s+(.+)/g.exec(docComment);
		if (lessMatch) {
			lessFiles.push(lessMatch[1]);
		}
	}

	return lessFiles;
};

exports.getReleaseDetails = function (filePath) {
	var firstLine = ("" + fs.readFileSync(filePath)).split('\n')[0];

	return {
		version: /^Version ([0-9xabrc.]+)/.exec(firstLine)[1],
		releaseDate: /^Version [^\(]+\(([^\)]+)\)/.exec(firstLine)[1]
	};
};

exports.instrumentFile = function(options) {
	var Instrument = require('coverjs').Instrument;
	var source = "" + fs.readFileSync(options.from);

	fs.writeFileSync(options.to, new Instrument(source, {
		name: options.from
	}).instrument());
};
