var fs = require('fs'),
    UglifyJS = require('uglify-js'),
    zlib = require('zlib'),
    SourceNode = require('source-map').SourceNode,
    UglifyCss = require('uglifycss');

deps = require('./deps.js').deps;

function getFiles(compsBase32) {
    var memo = {},
        comps;

    if (compsBase32) {
        comps = parseInt(compsBase32, 32).toString(2).split('');
        console.log('Managing dependencies...');
    }

    function addFiles(srcs) {
        for (var j = 0, len = srcs.length; j < len; j++) {
            memo[srcs[j]] = true;
        }
    }

    for (var i in deps) {
        if (comps) {
            if (parseInt(comps.pop(), 2) === 1) {
                console.log(' * ' + i);
                addFiles(deps[i].src);
            } else {
                console.log('   ' + i);
            }
        } else {
            addFiles(deps[i].src);
        }
    }

    console.log('');

    var files = [];

    for (var src in memo) {
        files.push('src/' + src);
    }

    return files;
}

exports.getFiles = getFiles;

function getSizeDelta(newContent, oldContent, fixCRLF) {
    if (!oldContent) {
        return ' (new)';
    }
    if (newContent === oldContent) {
        return ' (unchanged)';
    }
    if (fixCRLF) {
        newContent = newContent.replace(/\r\n?/g, '\n');
        oldContent = oldContent.replace(/\r\n?/g, '\n');
    }
    var delta = newContent.length - oldContent.length;

    return delta === 0 ? '' : ' (' + (delta > 0 ? '+' : '') + delta + ' bytes)';
}

function loadSilently(path) {
    try {
        return fs.readFileSync(path, 'utf8');
    } catch (e) {
        return null;
    }
}

// Concatenate the files while building up a sourcemap for the concatenation,
// and replace the line defining L.version with the string prepared in the jakefile
function bundleFiles(files, copy, version) {
    var node = new SourceNode(null, null, null, '');

    node.add(new SourceNode(null, null, null, copy + '(function (window, document, undefined) {'));

    for (var i = 0, len = files.length; i < len; i++) {
        var contents = fs.readFileSync(files[i], 'utf8');

        if (files[i] === 'src/Leaflet.draw.js') {
            contents = contents.replace(
                new RegExp('drawVersion = \'.*\''),
                'drawVersion = ' + JSON.stringify(version)
            );
        }

        var lines = contents.split('\n');
        var lineCount = lines.length;
        var fileNode = new SourceNode(null, null, null, '');

        fileNode.setSourceContent(files[i], contents);

        for (var j = 0; j < lineCount; j++) {
            fileNode.add(new SourceNode(j + 1, 0, files[i], lines[j] + '\n'));
        }
        node.add(fileNode);

        node.add(new SourceNode(null, null, null, '\n\n'));
    }

    node.add(new SourceNode(null, null, null, '}(window, document));'));

    var bundle = node.toStringWithSourceMap();
    return {
        src: bundle.code,
        srcmap: bundle.map.toString()
    };
}

function bytesToKB(bytes) {
    return (bytes / 1024).toFixed(2) + ' KB';
}


exports.build = function (callback, version, compsBase32, buildName) {

    var files = getFiles(compsBase32);

    console.log('Bundling and compressing ' + files.length + ' files...');

    var copy = fs.readFileSync('src/copyright.js', 'utf8').replace('{VERSION}', version),

        filenamePart = 'leaflet.draw' + (buildName ? '-' + buildName : ''),
        pathPart = 'dist/' + filenamePart,
        srcPath = pathPart + '-src.js',
        mapPath = pathPart + '-src.map',
        srcFilename = filenamePart + '-src.js',
        mapFilename = filenamePart + '-src.map',

        bundle = bundleFiles(files, copy, version),
        newSrc = bundle.src + '\n//# sourceMappingURL=' + mapFilename,

        oldSrc = loadSilently(srcPath),
        srcDelta = getSizeDelta(newSrc, oldSrc, true),

        leafletDrawCssPath = './src/leaflet.draw.css',
        compressedCssPath = './dist/leaflet.draw.css',
        cssSource = loadSilently(leafletDrawCssPath),
        oldCompressedCss = loadSilently(compressedCssPath),
        cssSourcePath = './dist/leaflet.draw-src.css',
        newCompressedCss;

    try {
        newCompressedCss = UglifyCss.processFiles(
            [leafletDrawCssPath],
            {maxLineLen: 500, expandVars: true}
        )
    } catch (e) {
        console.error('UglifyCss failed to minify the files');
        console.error(err);
        callback(err);
    }

    var cssSrcDelta = getSizeDelta(newCompressedCss, oldCompressedCss, true);

    console.log('\tCompressed Css: ' + bytesToKB(newCompressedCss.length) + cssSrcDelta);
    try {
        if (newCompressedCss !== oldCompressedCss) {
            fs.writeFileSync(cssSourcePath, cssSource);
            fs.writeFileSync(compressedCssPath, newCompressedCss);
            console.log('\tSaved to ' + srcPath);
        }
    } catch (err) {
        console.error('UglifyCSS failed to minify the files');
        console.error(err);
        callback(err);
    }

    console.log('\tUncompressed Js: ' + bytesToKB(newSrc.length) + srcDelta);
    if (newSrc !== oldSrc) {
        fs.writeFileSync(srcPath, newSrc);
        fs.writeFileSync(mapPath, bundle.srcmap);
        console.log('\tSaved to ' + srcPath);
    }

    var path = pathPart + '.js',
        oldCompressed = loadSilently(path),
        newCompressed;

    try {
        newCompressed = copy + UglifyJS.minify(newSrc, {
                warnings: true,
                fromString: true
            }).code;
    } catch (err) {
        console.error('UglifyJS failed to minify the files');
        console.error(err);
        callback(err);
    }

    var delta = getSizeDelta(newCompressed, oldCompressed);

    console.log('\tCompressed: ' + bytesToKB(newCompressed.length) + delta);

    var newGzipped,
        gzippedDelta = '';

    function done() {
        if (newCompressed !== oldCompressed) {
            fs.writeFileSync(path, newCompressed);
            console.log('\tSaved to ' + path);
        }
        console.log('\tGzipped: ' + bytesToKB(newGzipped.length) + gzippedDelta);
        callback();
    }

    zlib.gzip(newCompressed, function (err, gzipped) {
        if (err) {
            return;
        }
        newGzipped = gzipped;
        if (oldCompressed && (oldCompressed !== newCompressed)) {
            zlib.gzip(oldCompressed, function (err, oldGzipped) {
                if (err) {
                    return;
                }
                gzippedDelta = getSizeDelta(gzipped, oldGzipped);
                done();
            });
        } else {
            done();
        }
    });
};

exports.test = function (complete, fail) {
    var karma = require('karma'),
        testConfig = {configFile: __dirname + '/../spec/karma.conf.js'};

    testConfig.browsers = ['PhantomJSCustom'];

    function isArgv(optName) {
        return process.argv.indexOf(optName) !== -1;
    }

    if (isArgv('--chrome')) {
        testConfig.browsers.push('Chrome');
    }
    if (isArgv('--safari')) {
        testConfig.browsers.push('Safari');
    }
    if (isArgv('--ff')) {
        testConfig.browsers.push('Firefox');
    }
    if (isArgv('--ie')) {
        testConfig.browsers.push('IE');
    }

    if (isArgv('--cov')) {
        testConfig.preprocessors = {
            'src/**/*.js': 'coverage'
        };
        testConfig.coverageReporter = {
            type: 'html',
            dir: 'coverage/'
        };
        testConfig.reporters = ['coverage'];
    }

    console.log('Running tests...');

    var server = new karma.Server(testConfig, function (exitCode) {
        if (!exitCode) {
            console.log('\tTests ran successfully.\n');
            complete();
        } else {
            process.exit(exitCode);
        }
    });
    server.start();
};
