/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
 /**
 * Js Tasks
 * @module tasks/compile-js
 */

const gulp        = require('gulp');
const eslint      = require('gulp-eslint');
const cache       = require('gulp-cached');
const browserify  = require('browserify');
const babelify    = require('babelify');
const gutil       = require('gulp-util');
const gulpif      = require('gulp-if');
const uglify      = require('gulp-uglify');
const sourcemaps  = require('gulp-sourcemaps');
const source      = require('vinyl-source-stream');
const buffer      = require('vinyl-buffer');
const KarmaServer = require('karma').Server;
const filesize    = require('gulp-size');
const addsrc      = require('gulp-add-src');
const concat      = require('gulp-concat');
const runSequence = require('run-sequence');

const paths       = require('../paths.json');

const {
  getCommandArguments,
  notificationOnError,
} = require('./helpers');

const argv = getCommandArguments();

const libBundle = browserify({
  entries   : [paths.js.entryFile],
  debug     : !argv.production || argv.debug,
  paths     : [paths.js.src],
  cache     : true,
  transform : [
    babelify.configure({
      presets    : ['es2015'],
      sourceMaps : true,
    }),
  ],
});

/**
 * Checks and flags code that doesn't correspond to the
 * style guidelines configured under .eslintrc file
 *
 * @task js-lint
 */
gulp.task('js-lint', (done) => {

  if (argv['skip-linting']) {

    console.log('[!] js-lint skipped [!]');

    done();

    return;

  }

  // ESLint ignores files with "node_modules" paths.
  // So, it's best to have gulp ignore the directory as well.
  // Also, Be sure to return the stream from the task;
  // Otherwise, the task may end before the stream has finished.
  return gulp.src([
    `${paths.js.src}/**/*.js`,
  ])
    .pipe(cache('js-lint', { optimizeMemory: true }))
  // eslint() attaches the lint output to the "eslint" property
  // of the file object so it can be used by other modules.
    .pipe(eslint())
  // eslint.format() outputs the lint results to the console.
  // Alternatively use eslint.formatEach() (see Docs).
    .pipe(eslint.format())
  // To have the process exit with an error code (1) on
  // lint error, return the stream and pipe to failOnError last.
    .pipe(gulpif(!argv.force, eslint.failAfterError()))
    .on('error', notificationOnError('\uD83D\uDE25 Linting errors found!', '<%= error.message %>'));

});

/**
 * Runs the unit tests test/unit folder
 *
 * @task unit-tests
 */
gulp.task('unit-tests', (done) => {

  if (argv['skip-unit-tests']) {

    console.log('[!] unit-tests skipped [!]');

    done();

    return;

  }

  runSequence(
    'unit-tests:all',
   // 'unit-tests:precompute',
   // 'unit-tests:proofs',
    done
  );

});

gulp.task('unit-tests:all', [], (done) => {

  new KarmaServer({
    configFile : __dirname + '/../karma.conf.js',
    singleRun  : true,
  }, done).start();

});

gulp.task('unit-tests:precompute', (done) => {

  new KarmaServer({
    configFile : __dirname + '/../karma-precompute.conf.js',
    singleRun  : true,
  }, done).start();

});


gulp.task('unit-tests:proofs', (done) => {

  new KarmaServer({
    configFile : __dirname + '/../karma-proofs.conf.js',
    singleRun  : true,
  }, done).start();

});


/**
 * Bundles, minifies (if production flag is true), generates the
 * source maps and suffixes project's version to the resulted bundle file
 * for the provided browserify stream.
 *
 * @param {BrowserifyObject} browserifyStream
 * @param {String} bundleFilename The desired filename for the bundle
 */
const compileBundle = (browserifyStream, bundleFilename, uglifyCode = true) => {

  return browserifyStream.bundle()
    .on('error', gutil.log)
    .pipe(source(bundleFilename))
    .pipe(buffer())
    .pipe(addsrc([`${paths.js.crypto}/forge.min.js`]))
    .pipe(addsrc([`${paths.js.crypto}/sjcl.js`]))
    .pipe(concat(uglifyCode ? paths.js.bundleMinFile : paths.js.bundleFile))
    .pipe(
      gulpif(
        (!argv.production || argv.debug),
        sourcemaps.init({ loadMaps: true })
      )
    )
    .pipe(gulpif(uglifyCode, uglify()))
    .pipe(
      gulpif(
        (!argv.production || argv.debug),
        sourcemaps.write('./', { includeContent: true, sourceRoot: '/ov-api' })
      )
    )
    .pipe(filesize({ showFiles: true }));

}

/**
 * Bundles, minifies (if production arg is true), generates the source maps
 * (if no production arg was passed or debug args is present along with production arg)
 * and suffixes project's version to the resulted bundle file for the provided browserify stream.
 *
 * @task build-js
 */
gulp.task('build-js', ['build-js:include-sjcl', 'build-js:include-node-forge'], () => {

  return compileBundle(libBundle, paths.js.bundleFile)
    .pipe(gulp.dest(`${paths.js.dist}`));

});

gulp.task('build-js:include-node-forge', () => {

  return gulp.src(['./node_modules/node-forge/dist/forge.min.js'])
    .pipe(gulp.dest(paths.js.crypto));

});

gulp.task('build-js:include-sjcl', () => {

  return gulp.src(['./node_modules/sjcl/sjcl.js'])
    .pipe(gulp.dest(paths.js.crypto));

});

gulp.task('build-js:for-doc', () => {

  return compileBundle(libBundle, paths.js.bundleFile, false)
    .pipe(gulp.dest('./dist/doc/src'));

});


gulp.task('watch:js', () => {

  gulp.watch(
    [
      `${paths.js.src}/**/*.js`,
      `${paths.js.src}/**/*.coffee`,
    ],
    [
      'build-js',
    ]
  );

});
