"use strict";

/// !doc
/// 
/// # Compiler and file loader
///  
/// `var compiler = require('streamline/lib/compiler/compile')`
/// 
var fs = require("fs");
var fspath = require("path");

function _getTransform(options) {
	// req variable prevents client side require from getting it as a dependency
	var req = require;
	if (options.generators) {
		if (options.fast) return req("../generators-fast/transform");
		else return req("../generators/transform");
	} else if (options.fibers) {
		if (options.fast) return req("../fibers-fast/transform");
		else return req("../fibers/transform");
	} else {
		return require("../callbacks/transform");
	}
}

function _banner(version, options) {
	// important: no newline, to support lines-preserve option!
	var optStr = options.oldStyleFutures ? " --old-style-futures" : "";
	if (options.promise) optStr += " --promise-" + options.promise;
	if (options.standalone) optStr += " --standalone";
	if (options.aggressive) optStr += " --aggressive";
	return "/*** Generated by streamline " + version + optStr + " - DO NOT EDIT ***/";
}

function _extend(obj, other) {
	for (var i in other) {
		obj[i] = other[i];
	}
	return obj;
}

function _transform(transform, source, options) {
	options.source = source;
	options.callback = options.callback || "_";
	options.lines = options.lines || "preserve";
	return transform.transform(source, options);
}

function parseShebang(content) {
	if (content[0] === '#' && content[1] === '!') {
		var n = content.indexOf("\n");
		var le = "\n";
		if (n != -1) {
			var shebang = content.substr(0, n);
			if (shebang[shebang.length - 1] == "\r") {
				le = "\r\n";
				shebang = shebang.substr(0, shebang.length - 1);
			}
			content = content.substr(n + 1);
			return [shebang, content, le];
		}
	}
	return ['', content, ''];
}

function mtimeSync(fname) {
	try {
		return fs.statSync(fname).mtime;
	} catch (ex) {
		return 0;
	}
}

exports.transformModule = function(content, path, options) {
	options = _extend({}, options || {});

	var ext = fspath.extname(path);
	var basename = fspath.basename(path, ext);
	var dirname = fspath.dirname(path);

	var js = dirname + '/' + basename + ext;
	options.lines = options.lines || "preserve";

	var transform = _getTransform(options);
	var banner = _banner(transform.version, options);

	options.sourceName = path;
	var matches;
	if (ext !== '.js' && ext !== '.coffee') {
		// we don't care about shebang here, but keep line ending if it had a shebang for line counts
		var shebangparse = parseShebang(content);
		var shebang = shebangparse[0];
		content = shebangparse[2] + shebangparse[1];
		return transform.transform(content, options);
	} else {
		return content;
	}
}

function cacheRoot(options) {
	if (options.cacheDir) return options.cacheDir;
	if (process.env.HOME === undefined && process.env.HOMEDRIVE === undefined) throw new Error("HOME not found, unable to store Streamline callback cache");
	return (process.env.HOME || (process.env.HOMEDRIVE + process.env.HOMEPATH).replace(/\\/g, '/')) + "/.streamline";
}

var dirMode = parseInt('777', 8);

function subdir(options) {
	var d = options.generators ? 'generators' : options.fibers ? 'fibers' : 'callbacks';
	if (options.fast) d += '-fast';
	if (options.aggressive) d += '-aggressive';
	return d;
}

function mkdirsSync(path) {
	var p = "",
		i = 0;
	path.split('/').slice(0, -1).forEach(function(seg) {
		p += (i++ ? '/' : '') + seg;
		if (!(fs.existsSync || fspath.existsSync)(p)) {
			try {
				fs.mkdirSync(p, dirMode);
			} catch(err) {
				if (i > 1 && err.code !== 'EEXIST') {
					throw err;
				}
			}
		}
	});
}

function cachedTransformSync(content, path, transform, banner, options, testOnly) {
	path = path.replace(/\\/g, '/');
	if (options.cache) {
		var i = path.indexOf('node_modules/');
		if (i < 0) i = path.lastIndexOf('/');
		else i += 'node_modules'.length;

		var dir = cacheRoot(options) + '/' + subdir(options);
		dir += '/' + path.substring(0, i).replace(/[\/:]/g, '__');
		var f = dir + path.substring(i);
		mkdirsSync(f);
		var transformed;
		if (mtimeSync(f) > mtimeSync(path)) {
			transformed = fs.readFileSync(f, "utf8");
			if (transformed.substring(0, banner.length) === banner) return transformed;
		}
	}
	if (testOnly) return null;

	// no luck in cache
	if (options.verbose) console.log("streamline: transforming: " + path);
	var opts = Object.keys(options).reduce(function(r, k) {
		r[k] = options[k];
		return r;
	}, {});
	opts.lines = opts.lines || "sourcemap";
	transformed = banner + _transform(transform, content, opts);
	if (options.cache && path.indexOf('/tmp--') < 0) fs.writeFileSync(f, transformed, "utf8");
	return transformed;
}

exports.cachedTransformSync = function(content, path, transform, options, testOnly) {
	var banner = _banner(transform.version, options);
	return cachedTransformSync(content, path, {
		transform: transform
	}, banner, options, testOnly);
}
