var path = require('path');

var packagejs = require('./package.json');
var conf = require('./conf.js');

// Sorted alphabetically!
var babelify = require('babelify');
var envify = require('envify/custom');
var browserify = require('browserify');
var gulp = require("gulp");
var eslint = require('gulp-eslint');
var less = require("gulp-less");
var livereload = require("gulp-livereload");
var cleanCSS = require('gulp-clean-css');
var notify = require("gulp-notify");
var peg = require("gulp-peg");
var plumber = require("gulp-plumber");
var rename = require("gulp-rename");
var sourcemaps = require('gulp-sourcemaps');
var gutil = require("gulp-util");
var _ = require('lodash');
var uglifyify = require('uglifyify');
var buffer = require('vinyl-buffer');
var source = require('vinyl-source-stream');
var watchify = require('watchify');

var vendor_packages = _.difference(
    _.union(
        _.keys(packagejs.dependencies),
        conf.js.vendor_includes
    ),
    conf.js.vendor_excludes
);

var handleError = {errorHandler: notify.onError("Error: <%= error.message %>")};

/*
 * Sourcemaps are a wonderful way to develop directly from the chrome devtools.
 * However, generating correct sourcemaps is a huge PITA, especially on Windows.
 * Fixing this upstream is tedious as apparently nobody really cares and
 * a single misbehaving transform breaks everything.
 * Thus, we just manually fix all paths.
 */
function fixSourceMaps(file) {
    file.sourceMap.sources = file.sourceMap.sources.map(function (x) {
        return path.relative(".", x).split(path.sep).join('/');
    });
    return "/";
}
// Browserify fails for paths starting with "..".
function fixBrowserifySourceMaps(file) {
    file.sourceMap.sources = file.sourceMap.sources.map((x) => {
        return x.replace("src/js/node_modules", "node_modules");
    });
    return fixSourceMaps(file);
}
function fixLessSourceMaps(file) {
    file.sourceMap.sources = file.sourceMap.sources.map((x) => {
        if(!x.startsWith("..")){
            return "../src/css/" + x;
        }
        return x.replace("src/js/node_modules", "node_modules");
    });
    return fixSourceMaps(file);
}

function styles(files, dev){
    return gulp.src(files)
        .pipe(dev ? plumber(handleError) : gutil.noop())
        .pipe(sourcemaps.init())
        .pipe(less())
        .pipe(dev ? gutil.noop() : cleanCSS())
        .pipe(sourcemaps.write(".", {sourceRoot: fixLessSourceMaps}))
        .pipe(gulp.dest(conf.static))
        .pipe(livereload({auto: false}));
}
gulp.task("styles-app-dev", function () {
    styles(conf.css.app, true);
});
gulp.task("styles-vendor-dev", function () {
    styles(conf.css.vendor, true);
});
gulp.task("styles-app-prod", function () {
    styles(conf.css.app, false);
});
gulp.task("styles-vendor-prod", function () {
    styles(conf.css.app, false);
});


function buildScript(bundler, filename, dev) {
    if (dev) {
        bundler = watchify(bundler);
    } else {
        bundler = bundler.transform(envify({ _: 'purge', NODE_ENV: 'production' }), { global: true });
        bundler = bundler.transform({global: true}, uglifyify);
    }

    function rebundle() {
        return bundler.bundle()
            .on('error', function(error) {
                gutil.log(error + '\n' + error.codeFrame);
                this.emit('end');
            })
            .pipe(dev ? plumber(handleError) : gutil.noop())
            .pipe(source('bundle.js'))
            .pipe(buffer())
            .pipe(sourcemaps.init({loadMaps: true}))
            .pipe(rename(filename))
            .pipe(sourcemaps.write('.', {sourceRoot: fixBrowserifySourceMaps}))
            .pipe(gulp.dest(conf.static))
            .pipe(livereload({auto: false}));
    }

    // listen for an update and run rebundle
    bundler.on('update', rebundle);
    bundler.on('log', gutil.log);
    bundler.on('error', gutil.log);

    // run it once the first time buildScript is called
    return rebundle();
}

function vendor_stream(dev) {
    var bundler = browserify({
        entries: [],
        debug: true,
        cache: {}, // required for watchify
        packageCache: {} // required for watchify
    });
    for (var vp of vendor_packages) {
        bundler.require(vp);
    }
    return buildScript(bundler, "vendor.js", dev);
}
gulp.task("scripts-vendor-dev", function () {
    return vendor_stream(true);
});
gulp.task("scripts-vendor-prod", function () {
    return vendor_stream(false);
});

function app_stream(dev) {
    var bundler = browserify({
        entries: [conf.js.app],
        debug: true,
        extensions: ['.jsx'],
        cache: {}, // required for watchify
        packageCache: {} // required for watchify
    });
    for (var vp of vendor_packages) {
        bundler.external(vp);
    }
    bundler = bundler.transform(babelify);
    return buildScript(bundler, "app.js", dev);
}
gulp.task('scripts-app-dev', function () {
    return app_stream(true);
});
gulp.task('scripts-app-prod', function () {
    return app_stream(false);
});


gulp.task("eslint", function () {
    return gulp.src(conf.js.eslint)
        .pipe(plumber(handleError))
        .pipe(eslint())
        .pipe(eslint.format())
});

gulp.task("copy", function () {
    return gulp.src(conf.copy, {base: conf.src})
        .pipe(gulp.dest(conf.static));
});


gulp.task('templates', function(){
    return gulp.src(conf.templates, {base: conf.src})
        .pipe(gulp.dest(conf.dist));
});

gulp.task("peg", function () {
    return gulp.src(conf.peg, {base: conf.src})
        .pipe(plumber(handleError))
        .pipe(peg())
        .pipe(gulp.dest("src/"));
});

gulp.task(
    "dev",
    [
        "copy",
        "styles-vendor-dev",
        "styles-app-dev",
        "scripts-vendor-dev",
        "peg",
        "scripts-app-dev",
        "templates"
    ]
);
gulp.task(
    "prod",
    [
        "copy",
        "styles-vendor-prod",
        "styles-app-prod",
        "scripts-vendor-prod",
        "peg",
        "scripts-app-prod",
        "templates"
    ]
);

gulp.task("default", ["dev"], function () {
    livereload.listen({auto: true});
    gulp.watch(["src/css/vendor*"], ["styles-vendor-dev"]);
    gulp.watch(["src/css/**"], ["styles-app-dev"]);

    gulp.watch(conf.templates, ["templates"]);
    gulp.watch(conf.peg, ["peg"]);
    gulp.watch(["src/js/**"], ["eslint"]);
    // other JS is handled by watchify.
    gulp.watch(conf.copy, ["copy"]);
});
