var UglifyJS = require("uglify-js");
var transform = require("./transform").transform;
var fs = require("fs");

function compress(sources, opts) {
    if (!opts)
        opts = {};
    if (typeof sources == "string")
        sources = [{source: sources, file: ""}];
    
    var toplevel = null;
    var literals = [];
    
    sources.forEach(function(pkg) {
        if (pkg.source == undefined && pkg.file)
            pkg.source = fs.readFileSync(pkg.file, "utf8");
        
        if (/"disable compress"/.test(pkg.source))
            return literals.push(pkg.source);
        
        var source = pkg.source;
        try {
            source = transform(source, { mode: "es5" });
        } catch(e) {
            logParseError(e, pkg);
            throw e;
        }
        // if (pkg.file) console.log("Adding '" + pkg.file + "'.");
        try {
            toplevel = UglifyJS.parse(source, {
                filename: (pkg.file || pkg.id || "").replace(new RegExp("^" + opts.basepath + "/"), ""), //@todo remove prefix
                toplevel: toplevel
            });
        } catch (e) {
            logParseError(e, pkg);
            throw e;
        }
    });
    
    if (!toplevel) {
        return { code: literals.join("\n"), map: null };
    }
    
    if (opts.compress == "dryrun") {
        return { code: "dryrun\n" };
    }
    
    /**
     * UglifyJS contains a scope analyzer that you need to call manually before 
     * compressing or mangling. Basically it augments various nodes in the AST 
     * with information about where is a name defined, how many times is a name 
     * referenced, if it is a global or not, if a function is using eval or the 
     * with statement etc. I will discuss this some place else, for now what's 
     * important to know is that you need to call the following before doing 
     * anything with the tree:
     */
    toplevel.figure_out_scope();
    
    var compressor = UglifyJS.Compressor({
        warnings: !!opts.verbose,
        unsafe: false
    });
    var compressed_ast = toplevel.transform(compressor);
    
    /**
     * After compression it is a good idea to call again figure_out_scope 
     * (since the compressor might drop unused variables / unreachable code and 
     * this might change the number of identifiers or their position). 
     * Optionally, you can call a trick that helps after Gzip (counting 
     * character frequency in non-mangleable words). 
     */
    compressed_ast.figure_out_scope();
    compressed_ast.compute_char_frequency();

    // Right now this breaks the LogicBlox workspace; needs further investigation
    if (opts.obfuscate) {
        compressed_ast.mangle_names({except: ["$", "require", "exports", "initBaseUrls", "initSender"]});
    }
    
    var outputOptions = opts.oneLine ? {} : {
        quote_keys    : false, // quote all keys in object literals?
        space_colon   : false, // add a space after colon signs?
        ascii_only    : false, // output ASCII-safe? (encodes Unicode characters as ASCII)
        inline_script : false, // escape "</script"?
        max_line_len  : 100,   // maximum line length (for non-beautified output)
        beautify      : false, // beautify output?
        bracketize    : false, // use brackets every time?
        comments      : false, // output comments?
        semicolons    : false  // use semicolons to separate statements? (otherwise, newlines)
    };

    if (opts.mapFile) {
        // Generate a source map
        var source_map = UglifyJS.SourceMap({
            file : opts.mapFile || "build.js.map",
            root : opts.mapRoot
        });
        outputOptions.source_map = source_map;
    }
    var stream = UglifyJS.OutputStream(outputOptions);
    UglifyJS.AST_Node.warn_function = function(txt) {
        if (txt && txt.startsWith("Output exceeds")) return;
        console.error("WARN: %s", txt);
    };
    compressed_ast.print(stream);
    
    function asciify(text) {
        // remove top level use strict since that can break some librarires
        text = text.replace(/^(["'])use strict\1/, "$1no strict!$1");
        text = text.replace(/[\x80-\uffff]/g, function(c) {
            c = c.charCodeAt(0).toString(16);
            if (c.length == 2)
                return "\\x" + c;
            if (c.length == 3)
                c = "0" + c;
            return "\\u" + c;
        });
        return text;
    }
    
    return {
        code : asciify(stream.toString()) + "\n" + literals.join("\n"),
        map  : source_map ? source_map.toString() : null // json output for your source map
    };
}

function logParseError(e, pkg) {
    console.error("Error while parsing", (pkg.file || pkg.id) + ":" + e.line + ":" + e.col);
    if (e.line) {
        var MAX_LEN = 45;
        var line = pkg.source.split("\n")[e.line - 1];
        var col = e.col;
        if (col > MAX_LEN) {
            line = line.slice(col - MAX_LEN);
            col = MAX_LEN;
        }
        if (line.length > col + MAX_LEN)
            line = line.slice(0, col + MAX_LEN);
        line = line + "\n" + Array(col + 1).join(" ") + "^";
        console.error(line);
    }
}

compress.withCache = function(sources, opts) {
    var cache = opts.cache || {};
    if (cache && !cache.compress)
        cache.compress = Object.create(null);
    if (typeof sources == "string")
        sources = [{source: sources, file: ""}];
    var code = sources.map(function(pkg) {
        if (pkg.id && cache.compress[pkg.id]) {
            // console.log("Compress Cache Hit " + pkg.id);
            return cache.compress[pkg.id];
        }
        if (opts.exclude && opts.exclude.test(pkg.id))
            return pkg.source;
        var code = compress([pkg], opts).code;
        if (/[([]/.test(code[0]))
            code = ";" + code;
        return (cache.compress[pkg.id] = code);
    }).join("\n");
    return {code: code, map: null};
};

module.exports = compress;


// console.log(compress([{source: "/*disable compress*/var a = 0; var  b = 1+1;"}, {source: "var a = 0; var  b = 1+1;"} ]))