var benchpress = require('angular-benchpress/lib/cli');
var clean = require('gulp-rimraf');
var connect = require('gulp-connect');
var ejs = require('gulp-ejs');
var es = require('event-stream');
var file2moduleName = require('./file2modulename');
var fs = require('fs');
var glob = require('glob');
var gulp = require('gulp');
var merge = require('merge');
var mergeStreams = require('event-stream').merge;
var path = require('path');
var Q = require('q');
var readline = require('readline');
var rename = require('gulp-rename');
var runSequence = require('run-sequence');
var shell = require('gulp-shell');
var spawn = require('child_process').spawn;
var through2 = require('through2');
var watch = require('gulp-watch');
var changed = require('gulp-changed');

var js2es5Options = {
  sourceMaps: true,
  annotations: true, // parse annotations
  types: true, // parse types
  script: false, // parse as a module
  memberVariables: true, // parse class fields
  modules: 'instantiate'
};

var js2es5OptionsProd = merge(true, js2es5Options, {
  typeAssertions: false
});

var js2es5OptionsDev = merge(true, js2es5Options, {
  typeAssertionModule: 'rtts_assert/rtts_assert',
  typeAssertions: true
});

var js2dartOptions = {
  sourceMaps: true,
  annotations: true, // parse annotations
  types: true, // parse types
  script: false, // parse as a module
  memberVariables: true, // parse class fields
  outputLanguage: 'dart'
};

var PUB_CMD = process.platform === 'win32' ? 'pub.bat' : 'pub';

var gulpTraceur = require('./tools/transpiler/gulp-traceur');

// ---------
// traceur runtime

gulp.task('jsRuntime/build', function() {
  var traceurRuntime = gulp.src([
    gulpTraceur.RUNTIME_PATH,
    "node_modules/es6-module-loader/dist/es6-module-loader-sans-promises.src.js",
    "node_modules/systemjs/dist/system.src.js",
    "node_modules/systemjs/lib/extension-register.js"
  ]).pipe(gulp.dest('build/js'));
  return traceurRuntime;
});

// -----------------------
// modules
var sourceTypeConfigs = {
  dart: {
    transpileSrc: ['modules/**/*.js'],
    htmlSrc: ['modules/*/src/**/*.html'],
    copySrc: ['modules/**/*.dart'],
    outputDir: 'build/dart',
    outputExt: 'dart',
    mimeType: 'application/dart'
  },
  js: {
    transpileSrc: ['modules/**/*.js', 'modules/**/*.es6'],
    htmlSrc: ['modules/*/src/**/*.html'],
    copySrc: ['modules/**/*.es5'],
    outputDir: 'build/js',
    outputExt: 'js'
  }
};


gulp.task('modules/clean', function() {
  return gulp.src('build', {read: false})
      .pipe(clean());
});

gulp.task('modules/build.dart/src', function() {
  return createModuleTask(merge(sourceTypeConfigs.dart, {compilerOptions: js2dartOptions}));
});

gulp.task('modules/build.dart/pubspec', function(done) {
  var outputDir = sourceTypeConfigs.dart.outputDir;
  return gulp.src('modules/*/pubspec.yaml')
    .pipe(changed(outputDir)) // Only forward files that changed.
    .pipe(gulpDestWithForward(outputDir))
    .pipe(through2.obj(function(file, enc, done) {
      // After a `pubspec.yml` is copied, we run `pub install`.
      spawn(PUB_CMD, ['get'], {
        stdio: [process.stdin, process.stdout, process.stderr],
        cwd: path.dirname(file.path)
      }).on('close', done);
    }));
});

gulp.task('modules/build.dart', ['modules/build.dart/src', 'modules/build.dart/pubspec']);

gulp.task('modules/build.dev.js', function() {
  return createModuleTask(merge(true, sourceTypeConfigs.js, {compilerOptions: js2es5OptionsDev}));
});

gulp.task('modules/build.prod.js', function() {
  return createModuleTask(merge(true, sourceTypeConfigs.js, {compilerOptions: js2es5OptionsProd}));
});

function renameSrcToLib(file) {
  file.dirname = file.dirname.replace(/\bsrc\b/, 'lib');
}

function renameEs5ToJs(file) {
  if (file.extname == '.es5') {
    file.extname = '.js';
  }
}

function createModuleTask(sourceTypeConfig) {
  var transpile = gulp.src(sourceTypeConfig.transpileSrc)
    .pipe(rename({extname: '.'+sourceTypeConfig.outputExt}))
    .pipe(rename(renameSrcToLib))
    .pipe(gulpTraceur(sourceTypeConfig.compilerOptions, file2moduleName))
    .pipe(gulp.dest(sourceTypeConfig.outputDir));
  var copy = gulp.src(sourceTypeConfig.copySrc)
    .pipe(rename(renameSrcToLib))
    .pipe(rename(renameEs5ToJs))
    .pipe(gulp.dest(sourceTypeConfig.outputDir));
  // TODO: provide the list of files to the template
  // automatically!
  var html = gulp.src(sourceTypeConfig.htmlSrc)
    .pipe(rename(renameSrcToLib))
    .pipe(ejs({
      type: sourceTypeConfig.outputExt
    }))
    .pipe(gulp.dest(sourceTypeConfig.outputDir));

  return mergeStreams(transpile, copy, html);
}


// Act as `gulp.dest()` but does forward the files to the next stream.
// I believe this is how `gulp.dest` should work.
function gulpDestWithForward(destDir) {
  var stream = gulp.dest(destDir);
  var originalTransform = stream._transform;

  stream._transform = function(file, enc, done) {
    return originalTransform.call(this, file, enc, function() {
      stream.push(file);
      done();
    });
  };

  return stream;
}

// ------------------
// ANALYZE

gulp.task('analyze/dartanalyzer', function(done) {
  var pubSpecs = [].slice.call(glob.sync('build/dart/*/pubspec.yaml', {
    cwd: __dirname
  }));
  var tempFile = '_analyzer.dart';
  // analyze in parallel!
  return Q.all(pubSpecs.map(function(pubSpecFile) {
    var dir = path.dirname(pubSpecFile);
    var srcFiles = [].slice.call(glob.sync('lib/**/*.dart', {
      cwd: dir
    }));
    var testFiles = [].slice.call(glob.sync('test/**/*_spec.dart', {
      cwd: dir
    }));
    var analyzeFile = ['library _analyzer;'];
    srcFiles.concat(testFiles).forEach(function(fileName, index) {
      if (fileName !== tempFile) {
        analyzeFile.push('import "./'+fileName+'" as mod'+index+';');
      }
    });
    fs.writeFileSync(path.join(dir, tempFile), analyzeFile.join('\n'));
    var defer = Q.defer();
    analyze(dir, defer.makeNodeResolver());
    return defer.promise;
  }));

  function analyze(dirName, done) {
    var dartanalyzerCmd = (process.platform === "win32" ? "dartanalyzer.bat" : "dartanalyzer");
    var stream = spawn(dartanalyzerCmd, ['--fatal-warnings', tempFile], {
      // inherit stdin and stderr, but filter stdout
      stdio: [process.stdin, 'pipe', process.stderr],
      cwd: dirName
    });
    // Filter out unused imports from our generated file.
    // We don't reexports from the generated file
    // as this could lead to name clashes when two files
    // export the same thing.
    var rl = readline.createInterface({
      input: stream.stdout,
      output: process.stdout,
      terminal: false
    });
    var hintCount = 0;
    rl.on('line', function(line) {
      if (line.match(/Unused import .*_analyzer\.dart/)) {
        return;
      }
      if (line.match(/\[hint\]/)) {
        hintCount++;
      }
      console.log(dirName + ':' + line);
    });
    stream.on('close', function(code) {
      var error;
      if (code !== 0) {
        error = new Error('Dartanalyzer failed with exit code ' + code);
      }
      if (hintCount > 0) {
        error = new Error('Dartanalyzer showed hints');
      }
      done(error);
    });
  }
});



// ------------------
// BENCHMARKS JS

gulp.task('benchmarks/build.benchpress.js', function () {
  benchpress.build({
    benchmarksPath: 'build/js/benchmarks/lib',
    buildPath: 'build/benchpress/js'
  })
});

gulp.task('benchmarks/build.js', function() {
  runSequence(
    ['jsRuntime/build', 'modules/build.prod.js'],
    'benchmarks/build.benchpress.js'
  );
});


// ------------------
// BENCHMARKS DART

gulp.task('benchmarks/build.dart2js.dart', function () {
  return gulp.src([
    "build/dart/benchmarks/lib/**/benchmark.dart"
  ]).pipe(shell(['dart2js --package-root="build/dart/benchmarks/packages" -o "<%= file.path %>.js" <%= file.path %>']));
});

gulp.task('benchmarks/create-bpconf.dart', function () {
  var bpConfContent = "module.exports = function(c) {c.set({scripts: [{src: 'benchmark.dart.js'}]});}";
  var createBpConfJs = es.map(function(file, cb) {
    var dir = path.dirname(file.path);
    fs.writeFileSync(path.join(dir, "bp.conf.js"), bpConfContent);
    cb();
  });

  return gulp.src([
    "build/dart/benchmarks/lib/**/benchmark.dart"
  ]).pipe(createBpConfJs);
});

gulp.task('benchmarks/build.benchpress.dart', function () {
  benchpress.build({
    benchmarksPath: 'build/dart/benchmarks/lib',
    buildPath: 'build/benchpress/dart'
  })
});

gulp.task('benchmarks/build.dart', function() {
  runSequence(
    'modules/build.dart',
    'benchmarks/build.dart2js.dart',
    'benchmarks/create-bpconf.dart',
    'benchmarks/build.benchpress.dart'
  );
});



// ------------------
// WEB SERVER

gulp.task('serve', function() {
  connect.server({
    root: [__dirname+'/build'],
    port: 8000,
    livereload: false,
    open: false,
    middleware: function() {
      return [function(req, resp, next){
        if (req.url.match(/\.dart$/)) {
          resp.setHeader("Content-Type", "application/dart");
        }
        next();
      }];
    }
  })();
});



// --------------
// general targets

gulp.task('clean', ['modules/clean']);

gulp.task('build', function(done) {
  runSequence(
    // parallel
    ['jsRuntime/build', 'modules/build.dart', 'modules/build.dev.js'],
    // sequential
    'analyze/dartanalyzer'
  );
});

gulp.task('analyze', function(done) {
  runSequence('analyze/dartanalyzer');
});