// enable debug for gulp
process.env.DEBUG = process.env.DEBUG || 'fcc:*';
require('dotenv').load();

require('babel-core/register');
const Rx = require('rx'),
  gulp = require('gulp'),
  path = require('path'),
  debug = require('debug')('fcc:gulp'),
  yargs = require('yargs'),
  sortKeys = require('sort-keys'),
  del = require('del'),

  // utils
  plumber = require('gulp-plumber'),
  notify = require('gulp-notify'),
  gutil = require('gulp-util'),
  reduce = require('gulp-reduce-file'),
  concat = require('gulp-concat'),
  uglify = require('gulp-uglify'),
  merge = require('merge-stream'),
  sourcemaps = require('gulp-sourcemaps'),

  // react app
  webpack = require('webpack'),
  webpackStream = require('webpack-stream'),
  webpackDevMiddleware = require('webpack-dev-middleware'),
  webpackHotMiddleware = require('webpack-hot-middleware'),
  webpackConfig = require('./webpack.config.js'),
  webpackFrameConfig = require('./webpack.frame-runner.js'),

  // server process
  nodemon = require('gulp-nodemon'),
  browserSync = require('browser-sync'),

  // css
  less = require('gulp-less'),

  // rev
  rev = require('gulp-rev'),
  revDel = require('rev-del');

Rx.config.longStackSupport = true;
const sync = browserSync.create('fcc-sync-server');

function resolve(filepath, thisString, withThisString) {
  const newPath = require.resolve(filepath);
  if (thisString && withThisString) {
    return newPath.replace(thisString, withThisString);
  }
  return newPath;
}

// user definable
const __DEV__ = !yargs.argv.p;
const host = process.env.HOST || 'localhost';
const port = yargs.argv.port || process.env.PORT || '3001';
const syncPort = yargs.argv['sync-port'] || process.env.SYNC_PORT || '3000';

// make sure sync ui port does not interfere with proxy port
const syncUIPort = yargs.argv['sync-ui-port'] ||
  process.env.SYNC_UI_PORT ||
  parseInt(syncPort, 10) + 2;

const paths = {
  server: './server/server.js',
  serverIgnore: [
    'gulpfile.js',
    'public/',
    'node_modules/',
    'client/',
    'seed',
    'server/manifests/*.json',
    'server/rev-manifest.json'
  ],

  publicJs: './public/js',
  css: 'public/css',

  loopback: {
    client: './client/loopbackClient',
    root: path.join(__dirname, 'client/'),
    clientName: 'lbApp'
  },

  client: {
    src: './client',
    dest: 'public/js'
  },

  vendorChallenges: [
    resolve('jshint', 'src', 'dist'),
    resolve('chai', 'index.js', 'chai.js'),
    resolve('codemirror'),
    resolve('codemirror', 'lib/codemirror.js', 'addon/comment/comment.js'),
    resolve('codemirror', 'lib/codemirror.js', 'addon/edit/closebrackets.js'),
    resolve('codemirror', 'lib/codemirror.js', 'addon/edit/matchbrackets.js'),
    resolve('codemirror', 'lib/codemirror.js', 'addon/lint/lint.js'),
    resolve('codemirror', 'lib/codemirror.js', 'addon/lint/javascript-lint.js'),
    resolve('codemirror', 'lib/codemirror.js', 'mode/javascript/javascript.js'),
    resolve('codemirror', 'lib/codemirror.js', 'mode/jsx/jsx.js'),
    resolve('codemirror', 'lib/codemirror.js', 'mode/xml/xml.js'),
    resolve('codemirror', 'lib/codemirror.js', 'mode/css/css.js'),
    resolve('codemirror', 'lib/codemirror.js', 'mode/htmlmixed/htmlmixed.js'),
    resolve('emmet-codemirror'),
    'public/js/lib/loop-protect/loop-protect.js'
  ],

  vendorMain: [
    resolve('jquery', '.js', '.min.js'),
    resolve('bootstrap', 'npm.js', 'bootstrap.min.js'),
    resolve('d3', '.js', '.min.js'),
    resolve('cal-heatmap'),
    resolve('moment', '.js', '.min.js'),
    resolve(
      'moment-timezone',
      'index.js',
      'builds/moment-timezone-with-data.min.js'
    ),
    resolve('mousetrap', '.js', '.min.js'),
    resolve('lightbox2', '.js', '.min.js'),
    resolve('rx', 'index.js', 'dist/rx.all.min.js')
  ],

  less: './client/less/main.less',
  lessFiles: [
    './client/**/*.less',
    './common/**/*.less'
  ],

  manifest: 'server/manifests/',

  node: {
    src: './client',
    dest: 'common/app'
  },

  syncWatch: [
    'public/**/*.*'
  ],

  challenges: [
    'seed/challenges/*/*.json'
  ]
};

const errorNotifier = notify.onError({
  title: 'Compile Error',
  message: '<%= error %>'
});

function errorHandler(...args) {
  // Send error to notification center with gulp-notify
  errorNotifier.apply(this, args);
  // Keep gulp from hanging on this task
  this.emit('end');
}

function delRev(dest, manifestName) {
  // in production do not delete old revisions
  if (!__DEV__) {
    return gutil.noop();
  }
  return revDel({
    oldManifest: path.join(paths.manifest, manifestName),
    dest: dest
  });
}

gulp.task('serve', function(cb) {
  let called = false;
  const monitor = nodemon({
    script: paths.server,
    ext: '.jsx .js .json',
    ignore: paths.serverIgnore,
    exec: path.normalize('node_modules/.bin/babel-node'),
    env: {
      NODE_ENV: process.env.NODE_ENV || 'development',
      DEBUG: process.env.DEBUG || 'fcc:*',
      PORT: port
    }
  })
    .on('start', function() {
      if (!called) {
        called = true;
        cb();
      }
    })
    .on('restart', function(files) {
      if (files) {
        debug('Nodemon will restart due to changes in: ', files);
      }
    });

    process.once('SIGINT', () => {
      monitor.once('exit', () => {
        /* eslint-disable no-process-exit */
        process.exit(0);
        /* eslint-enable no-process-exit */
      });
    });
});

const syncDepenedents = [
  'serve',
  'js',
  'less'
];

gulp.task('dev-server', syncDepenedents, function() {
  webpackConfig.entry.bundle = [
    'webpack/hot/dev-server',
    'webpack-hot-middleware/client'
  ].concat(webpackConfig.entry.bundle);

  const bundler = webpack(webpackConfig);
  sync.init(null, {
    ui: {
      port: syncUIPort
    },
    proxy: {
      target: `http://${host}:${port}`,
      reqHeaders: ({ url: { hostname } }) => ({
        host: `${hostname}:${syncPort}`
      })
    },
    logLevel: 'info',
    files: paths.syncWatch,
    port: syncPort,
    open: false,
    middleware: [
      webpackDevMiddleware(bundler, {
        publicPath: webpackConfig.output.publicPath,
        stats: 'errors-only'
      }),
      webpackHotMiddleware(bundler)
    ]
  });
});

gulp.task('pack-client', function() {
  if (!__DEV__) { console.log('\n\nbundling production\n\n'); }

  const dest = webpackConfig.output.path;

  return gulp.src(webpackConfig.entry.bundle)
    .pipe(plumber({ errorHandler }))
    .pipe(webpackStream(webpackConfig))
    .pipe(gulp.dest(dest));
});

gulp.task('pack-frame-runner', function() {
  if (!__DEV__) { console.log('\n\nbundling frame production\n\n'); }


  const dest = webpackFrameConfig.output.path;

  return gulp.src(webpackFrameConfig.entry)
    .pipe(plumber({ errorHandler }))
    .pipe(webpackStream(webpackFrameConfig))
    .pipe(gulp.dest(dest));
});

const webpackManifestFiles = [ 'react-manifest.json', 'chunk-manifest.json' ];
gulp.task('move-webpack-manifest', ['pack-client'], function() {
  const files = webpackManifestFiles.map(function(filename) {
    return path.join(webpackConfig.output.path, filename);
  });
  return gulp.src(files).pipe(gulp.dest(paths.manifest));
});

const cleanDeps = ['pack-client', 'move-webpack-manifest'];
gulp.task('clean-webpack-manifest', cleanDeps, function() {
  return del(webpackManifestFiles.map(function(filename) {
    return path.join(webpackConfig.output.path, filename);
  }))
    .then(function(pathsDeleted) {
      gutil.log('[clean-webpack-manifest]', 'paths deleted' + pathsDeleted);
    })
    .catch(function(err) {
      throw new gutil.PluginError('clean-webpack-manifest', err);
    });
});

gulp.task('less', function() {
  const manifestName = 'css-manifest.json';
  const dest = paths.css;
  return gulp.src(paths.less)
    .pipe(plumber({ errorHandler }))
    .pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
    // compile
    .pipe(less({
      paths: [
        path.join(__dirname, 'client', 'less'),
        path.join(__dirname, 'common')
      ]
    }))
    .pipe(__DEV__ ?
      sourcemaps.write({ sourceRoot: '/less' }) :
      gutil.noop()
    )
    .pipe(gulp.dest(dest))
    // add revision
    .pipe(__DEV__ ? gutil.noop() : rev())
    // copy files to public
    .pipe(__DEV__ ? gutil.noop() : gulp.dest(dest))
    // create and merge manifest
    .pipe(__DEV__ ? gutil.noop() : rev.manifest(manifestName))
    .pipe(__DEV__ ? gutil.noop() : delRev(
      dest,
      manifestName
    ))
    .pipe(__DEV__ ? gutil.noop() : gulp.dest(paths.manifest));
});

function getFilesGlob(files) {
  if (!__DEV__) {
    return files;
  }
  return files.map(function(file) {
    return file
      .replace('.min.', '.')
      // moment breaks the pattern
      .replace('/min/', '/');
  });
}

gulp.task('js', function() {
  const manifestName = 'js-manifest.json';
  const dest = paths.publicJs;

  const jsFiles = merge(

    gulp.src(getFilesGlob(paths.vendorMain))
      .pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
      .pipe(concat('vendor-main.js'))
      .pipe(
        __DEV__ ?
          sourcemaps.write({ sourceRoot: '/vendor' }) :
          gutil.noop()
      ),

    gulp.src(paths.vendorChallenges)
      .pipe(__DEV__ ? sourcemaps.init() : gutil.noop())
      .pipe(__DEV__ ? gutil.noop() : uglify())
      .pipe(concat('vendor-challenges.js'))
      .pipe(
        __DEV__ ?
          sourcemaps.write({ sourceRoot: '/vendor' }) :
          gutil.noop()
      )
  );

  return jsFiles
    .pipe(gulp.dest(dest))
    // create registry file
    .pipe(__DEV__ ? gutil.noop() : rev())
    // copy revisioned assets to dest
    .pipe(__DEV__ ? gutil.noop() : gulp.dest(dest))
    // create manifest file
    .pipe(__DEV__ ? gutil.noop() : rev.manifest(manifestName))
    .pipe(__DEV__ ? gutil.noop() : delRev(
      dest,
      manifestName
    ))
    // copy manifest file to dest
    .pipe(__DEV__ ? gutil.noop() : gulp.dest(paths.manifest));
});


const collector = (file, memo) =>
  Object.assign(memo, JSON.parse(file.contents));

function done(manifest) {
  return sortKeys(manifest);
}

const buildDependents = [
  'less',
  'js',
  'pack-client',
  'move-webpack-manifest'
];

gulp.task('build-manifest', buildDependents, function() {
  return gulp.src(paths.manifest + '*.json')
    .pipe(reduce('rev-manifest.json', collector, done, {}))
    .pipe(gulp.dest('server/'));
});

gulp.task('build', [
  'less',
  'js',
  'pack-client',
  'pack-frame-runner',
  'move-webpack-manifest',
  'clean-webpack-manifest',
  'build-manifest'
]);

const watchDependents = [
  'less',
  'js',
  'serve',
  'pack-frame-runner',
  'dev-server'
];

gulp.task('watch', watchDependents, function() {
  gulp.watch(paths.lessFiles, ['less']);
  gulp.watch(paths.vendorChallenges, ['js']);
  gulp.watch(webpackFrameConfig.entry, ['pack-frame-runner']);
});

gulp.task('default', [
  'less',
  'serve',
  'watch',
  'dev-server',
  'pack-frame-runner'
]);
