const gulp = require('gulp');
const pkg = require('./package.json');
const corePkg = require('../package.json');
const merge = require('event-stream').merge;
const browserSync = require('browser-sync').create();
const $ = require('gulp-load-plugins')();
const eco = require('eco');
const fs = require('fs');
const ancss = require('@onsenui/ancss');
const cssnextPlugin = require('postcss-cssnext');
const reporter = require('postcss-reporter');
const historyApiFallback = require('connect-history-api-fallback');
const {rollup} = require('rollup');
const babel = require('rollup-plugin-babel');
const commonjs = require('rollup-plugin-commonjs');
const glob = require('glob');
const rimraf = require('rimraf');
const path = require('path');
const yaml = require('js-yaml');

// Include these plugins outside $ to fix gulp-hub
const plumber = require('gulp-plumber');
const postcss = require('gulp-postcss');
const stylelintPlugin = require('gulp-stylelint');

const prefix = __dirname + '/../build/css/';
const babelrc = Object.assign({}, corePkg.babel);
babelrc.babelrc = babelrc.presets[0][1].modules = false;
babelrc.exclude = 'node_modules/**';

const cwdOption = {
  cwd: __dirname // makes sure the correct relative path is used when tasks are run from main OnsenUI gulpfile
};

////////////////////////////////////////
// build-css
////////////////////////////////////////
gulp.task('build-css', gulp.series(cssClean, stylelint, cssnext, cssmin));

////////////////////////////////////////
// build
////////////////////////////////////////
gulp.task('build', gulp.series('build-css', generatePreview));

////////////////////////////////////////
// stylelint
////////////////////////////////////////
function stylelint() {
  return gulp.src([
      './src/**/*.css',
      '!./src/components/combination.css', // not following BEM
      '!./src/iphonex-support/**/*.css' // not following BEM
    ], cwdOption)
    .pipe(stylelintPlugin({
      failAfterError: false,
      reporters: [{formatter: 'string', console: true}],
      configFile: path.join(__dirname, 'stylelint.config.js') // uses css-components/stylelint.config.js even when run from main gulpfile
    }));
}

////////////////////////////////////////
// cssmin
////////////////////////////////////////
function cssmin() {
  return gulp.src(prefix + '{*-,}onsen-css-components.css')
    .pipe($.cssmin())
    .pipe($.rename({suffix: '.min'}))
    .pipe(gulp.dest('./build/'))
    .pipe(gulp.dest(prefix));
}

////////////////////////////////////////
// cssnext
////////////////////////////////////////
function cssnext() {
  const plugins = [
    require('postcss-import'),
    require('postcss-base64')({
      extensions: ['.svg'],
      root: __dirname + '/src/components/'
    }),
    cssnextPlugin({
      browsers: babelrc.presets[0][1].targets.browsers,
    }),
    reporter({
      clearAllMessages: true,
      clearReportedMessages: true,
      throwError: false
    })
  ];

  return gulp.src('src/{*-,}onsen-css-components.css', cwdOption)
    //.pipe(plumber()) // this was causing the task to never complete with gulp 4, but why...?
    .pipe(postcss(plugins))
    .pipe(gulp.dest('./build/'))
    .pipe(gulp.dest(prefix))
    .pipe(browserSync.stream());
}
gulp.task('cssnext', gulp.series(stylelint, cssnext));

function cssClean(done) {
  rimraf.sync(__dirname + '/build/{*-,}onsen-css-components.css');
  rimraf.sync(__dirname + '/build/{*-,}onsen-css-components.min.css');
  rimraf.sync(prefix + '/{*-,}onsen-css-components.css');
  rimraf.sync(prefix + '/{*-,}onsen-css-components.min.css');
  done();
}
gulp.task('css-clean', cssClean);

////////////////////////////////////////
// generate-preview
////////////////////////////////////////
let lastMarkupToken = '';

function generatePreview(done) {
  const components = parseComponents();
  const markupToken = identifyComponentsMarkup(components);

  if (markupToken !== lastMarkupToken) {
    gulp.series(previewAssets, previewJs, (done) => {
      generate(components);
      browserSync.reload();

      lastMarkupToken = markupToken;
      done();
    })();
    done();
  } else {
    lastMarkupToken = markupToken;
    done();
  }
}

function generatePreviewForce(done) {
  generate(parseComponents());
  browserSync.reload();
  done();
}
exports['generate-preview-force'] = gulp.series(previewAssets, previewJs, generatePreviewForce);

function generate(components) {
  const template = fs.readFileSync(__dirname + '/previewer-src/index.html.eco', 'utf-8');
  const patterns = yaml.safeLoadAll(fs.readFileSync(__dirname + '/patterns.yaml', 'utf-8'));
  const themes = glob.sync(__dirname + '/build/{*-,}onsen-css-components.css').map(filePath => path.basename(filePath, '.css'));
  const toJSON = JSON.stringify.bind(JSON);

  fs.writeFileSync(__dirname + '/build/index.html', eco.render(template, {toJSON, components, themes, patterns}), 'utf-8');
}

function identifyComponentsMarkup(componentsJSON) {
  const token = componentsJSON.map(component => {
    return component.annotation.markup;
  }).join('');

  return token;
}

function parseComponents() {
  const css = fs.readFileSync(__dirname + '/build/onsen-css-components.css', 'utf-8');
  const components = ancss.parse(css, {detect: line => line.match(/^~/)});
  return components || [];
}

////////////////////////////////////////
// preview-assets
////////////////////////////////////////
function previewAssets() {
  return gulp.src('previewer-src/*.{svg,css}')
    .pipe(gulp.dest('./build/'));
}

////////////////////////////////////////
// preview-js
////////////////////////////////////////
function previewJs() {
  return rollup({
    input: 'previewer-src/app.js',
    plugins: [
      commonjs,
      babel(babelrc)
    ]
  })
  .then(bundle => {
    return bundle.write({
      file: 'build/app.gen.js',
      format: 'umd',
      sourcemap: 'inline'
    });
  });
}

////////////////////////////////////////
// reset-console
////////////////////////////////////////
function reset(done) {
  process.stdout.write('\x1Bc');
  done();
}

const outputDevServerInfo = (() => {
  let defer = true;

  return function () {
    if (defer) {
      setTimeout(() => {
        output();
        defer = true;
      }, 60);
      defer = false;
    }
  }

  function output() {
    const localUrl = browserSync.getOption('urls').get('local');
    const externalUrl = browserSync.getOption('urls').get('external');

    console.log('\nAccess URLs:');
    console.log('     Local:', $.util.colors.magenta(localUrl));
    console.log('  External:', $.util.colors.magenta(externalUrl));
    console.log();

    displayBuildCSSInfo();
  }
})();

function displayBuildCSSInfo() {

  console.log('Built CSS Files:')
  getCSSPaths().forEach(cssPath => {
    console.log('  ' + $.util.colors.magenta(cssPath));
  });

  function getCSSPaths() {
    return glob.sync(__dirname + '/build/{*-,}onsen-css-components.css').map(cssPath => {
      return '.' + path.sep + path.relative(__dirname, cssPath);
    });
  }
}

function getCSSPaths() {
  return glob.sync(__dirname + '/build/{*-,}onsen-css-components.css').map(cssPath => {
    return '.' + path.sep + path.relative(__dirname, cssPath);
  });
}

////////////////////////////////////////
// serve
////////////////////////////////////////
function serve(done) {
  gulp.watch(['src/**/*.css'], () => {
    reset();
    gulp.series('build-css', 'generate-preview', outputDevServerInfo);
  });

  gulp.watch(['previewer-src/**', 'patterns.yaml'], () => {
    reset();
    gulp.series('generate-preview-force', outputDevServerInfo)
  });

  browserSync.emitter.on('init', outputDevServerInfo);

  browserSync.init({
    logLevel: 'silent',
    ui: false,
    port: 4321,
    notify: false,
    server: {
      baseDir: __dirname + '/build',
      middleware: [historyApiFallback()],
    },
    startPath: '/',
    open: false
  });
}

exports.serve = gulp.series('build', serve);
