var UglifyJS = require('uglify');

// State info
var currentContext = null;

// Remove error ranges from description text for parser errors
UglifyJS.JS_Parse_Error.prototype.toString = function() {
	return this.message;
};

// Add support for warnings
UglifyJS.AST_Node.warn_function = function(warning) {
	if (!currentContext) {
		return;
	}
	
	var range = null;
	var path = null;
	
	// Parse context info from the warning
	var contextInfoExp = /\[(.+):([0-9]+),([0-9]+)\]$/;
	if (contextInfoExp.test(warning)) {
		var contextInfo = contextInfoExp.exec(warning);
		
		path = contextInfo[1];
		
		// Get the range of the warning 
		var line = parseInt(contextInfo[2], 10) || 0;
		var column = parseInt(contextInfo[3], 10) || 0;

		if (line > 0 || column > 0) {
			var textContext = currentContext.textContextForDocument(path);
			var lineStorage = textContext ? textContext.lineStorage : null;
			
			if (lineStorage !== null) {
				var lineRange = lineStorage.lineRangeForLineNumber(line);
				range = new Range(parseInt(lineRange.location, 10) + parseInt(column, 10), 0);
			}
		}
			
		// Remove the line/column/file info from the warning string
		warning = warning.replace(contextInfoExp, '');
	}
	
	currentContext.addWarningMessage(warning, range, path);
};

// Out minify recipes should start with a given expression
var RECIPE_HEADER = /^\/\* JavaScript Compression Recipe: Automatically generated by Espresso \*\/[\n\s]/;
var RECIPE_HEADER_RANGE = new Range(0, 255);

function parseOptionsInContext(context)
{
	// Use a hard range for the potential header rather than testing the entire document
	var header = context.text.substring(RECIPE_HEADER_RANGE.location, RECIPE_HEADER_RANGE.length);
	if (!RECIPE_HEADER.test(header)) {
		return null;
	}
	
	// Parse the options as JSON after removing our header
	var options = null;
	try {
		var optionsText = context.text.replace(RECIPE_HEADER, '');
		options = JSON.parse(optionsText) || {};
		options.paths = options.paths || {};
	}
	catch (e) {}
	
	return options;
}

function PathNotFoundError(path)
{
	this.message = "Path not found: " + path;
}

compiler.compileWithContext = function(context, outError)
{
	// Verify our file actually should be compressed
	var options = parseOptionsInContext(context);
	if (!options) {
		return true;
	}
	
	currentContext = context;
	
	// Compress using UglifyJS
	try {
		var ast = null;
		
		options.paths.forEach(function(currentPath) {
			var currentCode = context.textForDocument(currentPath);
			
			if (!currentCode) {
				throw new PathNotFoundError(currentPath);
			}
			
			try {
				// Combine each file into a single AST
				ast = UglifyJS.parse(currentCode, {
					filename: currentPath,
					toplevel: ast
				});
			}
			catch (error) {
				error.path = currentPath;
				throw error;
			}
		});
		
		ast.figure_out_scope();
		
		if (options.compress) {
			var compressor = UglifyJS.Compressor();
			ast = ast.transform(compressor);
			
			ast.figure_out_scope();
		}
		
		if (options.mangle) {
			// Compute char frequency to make choose mangled names efficiently (for g-zip)
			ast.compute_char_frequency();
			
			ast.mangle_names({
				toplevel: true
			});
		}
		
		context.didCompileToText(ast.print_to_string());
		
		return true;
	}
	catch (error) {
	
		var errorInfo = {};
		var errorCode = EBCompilerErrorUnknown;
			
		if (error instanceof PathNotFoundError) {
			errorCode = EBCompilerErrorPathNotFound;
		}
		else {
			errorCode = EBCompilerErrorParsing;
			
			// Parser errors have paths associated with them		
			if (error.path !== undefined) {
				errorInfo[EBCompilerErrorPathKey] = error.path;
				
				// We may be able to supply an error range
				if (error.line !== undefined) {
					var textContext = currentContext.textContextForDocument(error.path);
					var lineStorage = textContext ? textContext.lineStorage : null;
					
					if (lineStorage !== null) {
						var lineRange = lineStorage.lineRangeForLineNumber(error.line);
						var errorRange = new Range((parseInt(lineRange.location, 10) || 0) + (parseInt(error.col, 10) || 0), 0);
						
						errorInfo[EBCompilerErrorRangeKey] = errorRange;
					}
				}
			}	
		}
		
		context.didFailWithError(errorCode, error.message, errorInfo);
		
		return false;
	}
	finally {
		currentContext = null;
	}
};
