var path = require('path');

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

// Sorted alphabetically!
var browserify = require('browserify');
var gulp = require("gulp");
var concat = require('gulp-concat');
var connect = require('gulp-connect');
var jshint = require("gulp-jshint");
var less = require("gulp-less");
var livereload = require("gulp-livereload");
var minifyCSS = require('gulp-minify-css');
var notify = require("gulp-notify");
var peg = require("gulp-peg");
var plumber = require("gulp-plumber");
var react = require("gulp-react");
var rename = require("gulp-rename");
var replace = require('gulp-replace');
var rev = require("gulp-rev");
var sourcemaps = require('gulp-sourcemaps');
var uglify = require('gulp-uglify');
var _ = require('lodash');
var map = require("map-stream");
var reactify = require('reactify');
var buffer = require('vinyl-buffer');
var source = require('vinyl-source-stream');
var transform = require('vinyl-transform');

// FIXME: react-with-addons.min.js for prod use issue
// FIXME: Sourcemap URLs don't work correctly.
// FIXME: Why don't we use gulp-rev's manifest feature?

var manifest = {
    "vendor.css": "vendor.css",
    "app.css": "app.css",
    "vendor.js": "vendor.js",
    "app.js": "app.js",
};

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


// Custom linting reporter used for error notify
var jsHintErrorReporter = function(){
    return map(function (file, cb) {
        if (file.jshint && !file.jshint.success) {
            file.jshint.results.forEach(function (err) {
                if (err) {
                    var msg = [
                        path.basename(file.path),
                        'Line: ' + err.error.line,
                        'Reason: ' + err.error.reason
                    ];
                    notify.onError(
                        "Error: <%= error.message %>"
                    )(new Error(msg.join("\n")));
                }
            });
        }
        cb(null, file);
    })
};

function save_rev(){
    return map(function(file, callback){
        if (file.revOrigBase){
            manifest[path.basename(file.revOrigPath)] = path.basename(file.path);
        }
        callback(null, file);
    })
}

var dont_break_on_errors = function(){
    return plumber(
        function(error){
            notify.onError("Error: <%= error.message %>").apply(this, arguments);
            this.emit('end');
        }
    );
};

/*
 * 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.
 */
//Normalize \ to / on Windows.
function unixStylePath(filePath) {
  return filePath.split(path.sep).join('/');
}
// Hijack the sourceRoot attr to do our transforms
function fixSourceMapPaths(file){
    file.sourceMap.sources = file.sourceMap.sources.map(function (x) {
        return unixStylePath(path.relative(".", x));
    });
    return "/";
}
// Browserify fails for paths starting with "..".
function fixBrowserifySourceMapPaths(file){
    file.sourceMap.sources = file.sourceMap.sources.map(function (x) {
        return x.replace("src/js/node_modules","node_modules");
    });
    return fixSourceMapPaths(file);
}

gulp.task("fonts", function () {
    return gulp.src(conf.fonts)
        .pipe(gulp.dest(conf.static + "/fonts"))
});

function styles_dev(files) {
    return (gulp.src(files)
        .pipe(dont_break_on_errors())
        .pipe(sourcemaps.init())
        .pipe(less())
        .pipe(sourcemaps.write(".", {sourceRoot: fixSourceMapPaths}))
        .pipe(gulp.dest(conf.static))
        .pipe(livereload({ auto: false })));
}
gulp.task("styles-app-dev", function(){
    styles_dev(conf.css.app);
});
gulp.task("styles-vendor-dev", function(){
    styles_dev(conf.css.vendor);
});


function styles_prod(files) {
    return (gulp.src(files)
        .pipe(less())
        .pipe(minifyCSS())
        .pipe(rev())
        .pipe(save_rev())
        .pipe(gulp.dest(conf.static))
        .pipe(livereload({ auto: false })));
}
gulp.task("styles-app-prod", function(){
    styles_prod(conf.css.app);
});
gulp.task("styles-vendor-prod", function(){
    styles_prod(conf.css.vendor);
});


function vendor_stream(debug){
    var vendor = browserify(vendor_packages, {debug: debug});
    _.each(vendor_packages, function(v){
        vendor.require(v);
    });
    return vendor.bundle()
        .pipe(source("dummy.js"))
        .pipe(rename("vendor.js"));
}
gulp.task("scripts-vendor-dev", function (){
    return vendor_stream(false)
        .pipe(gulp.dest(conf.static));
});
gulp.task("scripts-vendor-prod", function(){
    return vendor_stream(false)
        .pipe(buffer())
        .pipe(uglify())
        .pipe(rev())
        .pipe(save_rev())
        .pipe(gulp.dest(conf.static));
});


function app_stream(debug) {
    var browserified = transform(function(filename) {
        var b = browserify(filename, {debug: debug});
        _.each(vendor_packages, function(v){
            b.external(v);
        });
        b.transform(reactify);
        return b.bundle();
    });

    return gulp.src([conf.js.app], {base: "."})
        .pipe(dont_break_on_errors())
        .pipe(browserified)
        .pipe(sourcemaps.init({ loadMaps: true }))
        .pipe(rename("app.js"));
}

gulp.task('scripts-app-dev', function () {
    return app_stream(true)
        .pipe(sourcemaps.write('./', {sourceRoot: fixBrowserifySourceMapPaths}))
        .pipe(gulp.dest(conf.static))
        .pipe(livereload({ auto: false }));
});

gulp.task('scripts-app-prod', function () {
    return app_stream(true)
        .pipe(buffer())
        .pipe(uglify())
        .pipe(rev())
        .pipe(sourcemaps.write('./', {sourceRoot: fixBrowserifySourceMapPaths}))
        .pipe(save_rev())
        .pipe(gulp.dest(conf.static));
});


gulp.task("jshint", function () {
    return gulp.src(conf.js.jshint)
        .pipe(dont_break_on_errors())
        .pipe(react())
        .pipe(jshint())
        .pipe(jshint.reporter("jshint-stylish"))
        .pipe(jsHintErrorReporter());
});

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

function templates(){
    return gulp.src(conf.templates, {base: conf.src})
        .pipe(replace(/\{\{\{(\S*)\}\}\}/g, function(match, p1) {
            return manifest[p1];
        }))
        .pipe(gulp.dest(conf.dist));
}
gulp.task('templates', templates);

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

gulp.task('connect', function() {
    if(conf.connect){
        connect.server({
            port: conf.connect.port
        });
    }
});

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

gulp.task("default", ["dev", "connect"], function () {
    livereload.listen({auto: true});
    gulp.watch(["src/css/vendor*"], ["styles-vendor-dev"]);
    gulp.watch(conf.peg, ["peg", "scripts-app-dev"]);
    gulp.watch(["src/js/**"], ["scripts-app-dev", "jshint"]);
    gulp.watch(["src/css/**"], ["styles-app-dev"]);
    gulp.watch(conf.templates, ["templates"]);
    gulp.watch(conf.copy, ["copy"]);
});