text: // noinspection Annotator
  var args = require('yargs').argv;
  var browserSync = require('browser-sync');
  var config = require('./gulp.config')();
  var del = require('del');
  var glob = require('glob');
  var gulp = require('gulp');
  var path = require('path');
  var semver = require('semver');
  var _ = require('lodash');
  var $ = require('gulp-load-plugins')({lazy: true});
  // noinspection Annotator
  var protractor = $.protractor.protractor;
  
  // noinspection Annotator
  var colors = $.util.colors;
  // noinspection Annotator
  var envenv = $.util.env;
  var port = process.env.PORT || config.defaultPort;
  
  /**
   * yargs variables can be passed in to alter the behavior, when present.
   * Example: gulp serve-dev
   *
   * --verbose  : Various tasks will produce more output to the console.
   * --nosync   : Don't launch the browser with browser-sync when serving code.
   * --debug    : Launch debugger with node-inspector.
   * --debug-brk: Launch debugger and break on 1st line with node-inspector.
   * --startServers: Will start servers for midway tests on the test task.
   */
  
  // noinspection Annotator
  /**
   * List the available gulp tasks
   */
  gulp.task('help', $.taskListing);
  gulp.task('default', ['help']);
  
  /**
   * vet the code and create coverage report
   * @return {Stream}
   */
  gulp.task('vet', function () {
  	log('Analyzing source with JSHint and JSCS');
  
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	return gulp
  		.src(config.alljs)
  		.pipe($.if(args.verbose, $.print()))
  		.pipe($.jshint())
  		.pipe($.jshint.reporter('jshint-stylish', {verbose: true}))
  		.pipe($.jshint.reporter('fail'))
  		.pipe($.jscs());
  });
  
  /**
   * Create a visualizer report
   */
  gulp.task('plato', function (done) {
  	log('Analyzing source with Plato');
  	log('Browse to /report/plato/index.html to see Plato results');
  
  	startPlatoVisualizer(done);
  });
  
  /**
   * Compile less to css
   * @return {Stream}
   */
  gulp.task('styles', ['clean-styles'], function () {
  	log('Compiling Less --> CSS');
  
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	return (gulp
  		.src(config.less)
  		.pipe($.plumber()) // exit gracefully if something fails after this
  		.pipe($.less())
  		//        .on('error', errorLogger) // more verbose and dupe output. requires emit.
  		.pipe($.autoprefixer({browsers: ['last 2 version', '> 5%']}))
  		.pipe(gulp.dest(config.temp)));
  });
  
  /**
   * Copy fonts
   * @return {Stream}
   */
  gulp.task('fonts', ['clean-fonts'], function () {
  	log('Copying fonts');
  
  	return gulp.src(config.fonts).pipe(gulp.dest(config.build + 'fonts'));
  });
  
  /**
   * Compress images
   * @return {Stream}
   */
  gulp.task('images', ['clean-images'], function () {
  	log('Compressing and copying images');
  
  	// noinspection Annotator
  	return gulp.src(config.images).pipe($.imagemin({optimizationLevel: 4})).pipe(gulp.dest(config.build + 'images'));
  });
  
  gulp.task('less-watcher', function () {
  	gulp.watch([config.less], ['styles']);
  });
  
  /**
   * Create $templateCache from the html templates
   * @return {Stream}
   */
  gulp.task('templatecache', ['clean-code'], function () {
  	log('Creating an AngularJS $templateCache');
  
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	return gulp
  		.src(config.htmltemplates)
  		.pipe($.if(args.verbose, $.bytediff.start()))
  		.pipe($.minifyHtml({empty: true}))
  		.pipe($.if(args.verbose, $.bytediff.stop(bytediffFormatter)))
  		.pipe($.angularTemplatecache(config.templateCache.file, config.templateCache.options))
  		.pipe(gulp.dest(config.temp));
  });
  
  /**
   * Wire-up the bower dependencies
   * @return {Stream}
   */
  gulp.task('wiredep', function () {
  	log('Wiring the bower dependencies into the html');
  
  	var wiredep = require('wiredep').stream;
  	var options = config.getWiredepDefaultOptions();
  
  	// Only include stubs if flag is enabled
  	// noinspection Annotator
  	var js = args.stubs ? [].concat(config.js, config.stubsjs) : config.js;
  
  	return gulp
  		.src(config.index)
  		.pipe(wiredep(options))
  		.pipe(inject(js, '', config.jsOrder))
  		.pipe(gulp.dest(config.client));
  });
  
  gulp.task('inject', ['wiredep', 'styles', 'templatecache'], function () {
  	log('Wire up css into the html, after files are ready');
  
  	return gulp.src(config.index).pipe(inject(config.css)).pipe(gulp.dest(config.client));
  });
  
  /**
   * Run the spec runner
   * @return {Stream}
   */
  gulp.task('serve-specs', ['build-specs'], function (done) {
  	log('run the spec runner');
  	serve(true /* isDev */, true /* specRunner */);
  	done();
  });
  /**
   * Inject all the spec files into the specs.html
   * @return {Stream}
   */
  gulp.task('build-specs', ['templatecache'], function (done) {
  	log('building the spec runner');
  
  	var wiredep = require('wiredep').stream;
  	var templateCache = config.temp + config.templateCache.file;
  	var options = config.getWiredepDefaultOptions();
  	var specs = config.specs;
  
  	// noinspection Annotator
  	if (args.startServers) {
  		specs = [].concat(specs, config.serverIntegrationSpecs);
  	}
  	options.devDependencies = true;
  
  	return gulp
  		.src(config.specRunner)
  		.pipe(wiredep(options))
  		.pipe(inject(config.js, '', config.jsOrder))
  		.pipe(inject(config.testlibraries, 'testlibraries'))
  		.pipe(inject(config.specHelpers, 'spechelpers'))
  		.pipe(inject(specs, 'specs', ['**/*']))
  		.pipe(inject(templateCache, 'templates'))
  		.pipe(gulp.dest(config.client));
  });
  
  /**
   * Build everything
   * This is separate so we can run tests on
   * optimize before handling image or fonts
   */
  gulp.task('build', ['optimize', 'images', 'fonts'], function () {
  	log('Building everything');
  
  	var msg = {
  		title: 'gulp build',
  		subtitle: 'Deployed to the build folder',
  		message: 'Running `gulp serve-build`'
  	};
  	del(config.temp);
  	log(msg);
  	notify(msg);
  });
  
  /**
   * Optimize all files, move to a build folder,
   * and inject them into the new index.html
   * @return {Stream}
   */
  gulp.task('optimize', ['inject', 'test'], function () {
  	log('Optimizing the js, css, and html');
  
  	// noinspection Annotator
  	var assets = $.useref.assets({searchPath: './'});
  	// Filters are named for the gulp-useref path
  	// noinspection Annotator
  	var cssFilter = $.filter('**/*.css');
  	// noinspection Annotator
  	var jsAppFilter = $.filter('**/' + config.optimized.app);
  	// noinspection Annotator
  	var jslibFilter = $.filter('**/' + config.optimized.lib);
  
  	var templateCache = config.temp + config.templateCache.file;
  
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	// noinspection Annotator
  	return (gulp
  		.src(config.index)
  		.pipe($.plumber())
  		.pipe(inject(templateCache, 'templates'))
  		.pipe(assets) // Gather all assets from the html with useref
  		// Get the css
  		.pipe(cssFilter)
  		.pipe($.minifyCss())
  		.pipe(cssFilter.restore())
  		// Get the custom javascript
  		.pipe(jsAppFilter)
  		.pipe($.ngAnnotate({add: true}))
  		.pipe($.uglify())
  		.pipe(getHeader())
  		.pipe(jsAppFilter.restore())
  		// Get the vendor javascript
  		.pipe(jslibFilter)
  		.pipe($.uglify()) // another option is to override wiredep to use min files
  		.pipe(jslibFilter.restore())
  		// Take inventory of the file names for future rev numbers
  		.pipe($.rev())
  		// Apply the concat and file replacement with useref
  		.pipe(assets.restore())
  		.pipe($.useref())
  		// Replace the file names in the html with rev numbers
  		.pipe($.revReplace())
  		.pipe(gulp.dest(config.build)));
  });
  
  /**
   * Remove all files from the build, temp, and reports folders
   * @param  {Function} done - callback when complete
   */
  gulp.task('clean', function (done) {
  	var delconfig = [].concat(config.build, config.temp, config.report);
  	// noinspection Annotator
  	log('Cleaning: ' + $.util.colors.blue(delconfig));
  	del(delconfig, done);
  });
  
  /**
   * Remove all fonts from the build folder
   * @param  {Function} done - callback when complete
   */
  gulp.task('clean-fonts', function (done) {
  	clean(config.build + 'fonts/**/*.*', done);
  });
  
  /**
   * Remove all images from the build folder
   * @param  {Function} done - callback when complete
   */
  gulp.task('clean-images', function (done) {
  	clean(config.build + 'images/**/*.*', done);
  });
  
  /**
   * Remove all styles from the build and temp folders
   * @param  {Function} done - callback when complete
   */
  gulp.task('clean-styles', function (done) {
  	var files = [].concat(config.temp + '**/*.css', config.build + 'styles/**/*.css');
  	clean(files, done);
  });
  
  /**
   * Remove all js and html from the build and temp folders
   * @param  {Function} done - callback when complete
   */
  gulp.task('clean-code', function (done) {
  	var files = [].concat(config.temp + '**/*.js', config.build + 'js/**/*.js', config.build + '**/*.html');
  	clean(files, done);
  });
  
  /**
   * Run specs once and exit
   * To start servers and run midway specs as well:
   *    gulp test --startServers
   * @return {Stream}
   */
  gulp.task('test', ['vet', 'templatecache'], function (done) {
  	startTests(true /*singleRun*/, done);
  });
  
  /**
   * Runs the e2e specs using protractor.
   */
  // gulp.task('e2e', ['vet'], function(done) {
  //   runProtractor(done);
  // });
  
  /**
   * Run specs and wait.
   * Watch for file changes and re-run tests on each change
   * To start servers and run midway specs as well:
   *    gulp autotest --startServers
   */
  gulp.task('autotest', function (done) {
  	startTests(false /*singleRun*/, done);
  });
  
  /**
   * serve the dev environment
   * --debug-brk or --debug
   * --nosync
   */
  gulp.task('serve-dev', ['inject'], function () {
  	serve(true /*isDev*/);
  });
  
  /**
   * serve the build environment
   * --debug-brk or --debug
   * --nosync
   */
  gulp.task('serve-build', ['build'], function () {
  	serve(false /*isDev*/);
  });
  
  /**
   * Bump the version
   * --type=pre will bump the prerelease version *.*.*-x
   * --type=patch or no flag will bump the patch version *.*.x
   * --type=minor will bump the minor version *.x.*
   * --type=major will bump the major version x.*.*
   * --version=1.2.3 will bump to a specific version and ignore other flags
   */
  gulp.task('bump', function () {
  	var msg = 'Bumping versions';
  	var type = args.type;
  	// noinspection Annotator
  	var version = args.ver;
  	var options = {};
  	if (version) {
  		options.version = version;
  		msg += ' to ' + version;
  	} else {
  		options.type = type;
  		msg += ' for a ' + type;
  	}
  	log(msg);
  
  	// noinspection Annotator
  	// noinspection Annotator
  	return gulp.src(config.packages).pipe($.print()).pipe($.bump(options)).pipe(gulp.dest(config.root));
  });
  
  /**
   * Optimize the code and re-load browserSync
   */
  gulp.task('browserSyncReload', ['optimize'], browserSync.reload);
  
  ////////////////
  
  /**
   * When files change, log it
   * @param  {Object} event - event that fired
   */
  function changeEvent(event) {
  	var srcPattern = new RegExp('/.*(?=/' + config.source + ')/');
  	log('File ' + event.path.replace(srcPattern, '') + ' ' + event.type);
  }
  
  /**
   * Delete all files in a given path
   * @param  {Array|String}   path - array of paths to delete
   * @param  {Function} done - callback when complete
   */
  function clean(path, done) {
  	// noinspection Annotator
  	log('Cleaning: ' + $.util.colors.blue(path));
  	del(path, done);
  }
  
  /**
   * Inject files in a sorted sequence at a specified inject label
   * @param   {Array|Stream|String} src   glob pattern for source files
   * @param   {String?} label   The label name
   * @param   {Array?} order   glob pattern for sort order of the files
   * @returns {Stream}   The stream
   */
  function inject(src, label, order) {
  	var options = {read: false};
  	if (label) {
  		options.name = 'inject:' + label;
  	}
  
  	// noinspection Annotator
  	return $.inject(orderSrc(src, order), options);
  }
  
  /**
   * Order a stream
   * @param   {Array|Stream} src   The gulp.src stream
   * @param   {Array} order Glob array pattern
   * @returns {Stream} The ordered stream
   */
  function orderSrc(src, order) {
  	//order = order || ['**/*'];
  	// noinspection Annotator
  	// noinspection Annotator
  	return gulp.src(src).pipe($.if(order, $.order(order)));
  }
  
  /**
   * serve the code
   * --debug-brk or --debug
   * --nosync
   * @param  {Boolean} isDev - dev or build mode
   * @param  {Boolean?} specRunner - server spec runner html
   */
  function serve(isDev, specRunner) {
  	var debugMode = semver.satisfies(process.versions.node, '>=7.0.0') ? '--inspect' : '--debug';
  
  	var nodeOptions = getNodeOptions(isDev);
  
  	nodeOptions.nodeArgs = [debugMode + '=5262'];
  
  	if (args.verbose) {
  		console.log(nodeOptions);
  	}
  
  	// noinspection Annotator
  	return $.nodemon(nodeOptions)
  		.on('restart', ['vet'], function (ev) {
  			log('*** nodemon restarted');
  			log('files changed:\n' + ev);
  			setTimeout(function () {
  				browserSync.notify('reloading now ...');
  				browserSync.reload({stream: false});
  			}, config.browserReloadDelay);
  		})
  		.on('start', function () {
  			log('*** nodemon started');
  			startBrowserSync(isDev, specRunner);
  		})
  		.on('crash', function () {
  			log('*** nodemon crashed: script crashed for some reason');
  		})
  		.on('exit', function () {
  			log('*** nodemon exited cleanly');
  		});
  }
  
  function getNodeOptions(isDev) {
  	return {
  		script: config.nodeServer,
  		delayTime: 1,
  		env: {
  			'PORT': port,
  			'NODE_ENV': isDev ? 'dev' : 'build'
  		},
  		watch: [config.server]
  	};
  }
  
  //function runNodeInspector() {
  //    log('Running node-inspector.');
  //    log('Browse to http://localhost:8080/debug?port=5858');
  //    var exec = require('child_process').exec;
  //    exec('node-inspector');
  //}
  
  // /**
  //  * Start e2e tests using Protractor.
  //  * @param {function} done Callback when protractor has finished its operation.
  //  * @return {Stream}
  //  */
  // function runProtractor(done) {
  //   log('Running e2e Protractor Specs...');
  
  //   return gulp
  //     .src([config.scenarios], {read: false})
  //     .pipe($.plumber())
  //     .pipe(protractor({
  //       configFile: './protractor.config.js'
  //     }))
  //     .on('error', function() {
  //       log('Protractor Error.');
  //       done();
  //     })
  //     .on('end', function() {
  //       log('Protractor End.');
  //       done();
  //     });
  // }
  
  /**
   * Start BrowserSync
   * --nosync will avoid browserSync
   */
  function startBrowserSync(isDev, specRunner) {
  	// noinspection Annotator
  	if (args.nosync || browserSync.active) {
  		return;
  	}
  
  	log('Starting BrowserSync on port ' + port);
  
  	// If build: watches the files, builds, and restarts browser-sync.
  	// If dev: watches less, compiles it to css, browser-sync handles reload
  	if (isDev) {
  		gulp.watch([config.less], ['styles']).on('change', changeEvent);
  	} else {
  		gulp.watch([config.less, config.js, config.html], ['browserSyncReload']).on('change', changeEvent);
  	}
  
  	var options = {
  		proxy: 'localhost:' + port,
  		port: 3000,
  		files: isDev ? [config.client + '**/*.*', '!' + config.less, config.temp + '**/*.css'] : [],
  		watchOptions: {
  			ignored: ['node_modules', 'bower_components']
  		},
  		ghostMode: {
  			// these are the defaults t,f,t,t
  			clicks: true,
  			location: false,
  			forms: true,
  			scroll: true
  		},
  		injectChanges: true,
  		logFileChanges: true,
  		logLevel: 'info',
  		logPrefix: 'hottowel',
  		notify: true,
  		reloadDelay: 0 //1000
  	};
  	if (specRunner) {
  		options.startPath = config.specRunnerFile;
  	}
  
  	browserSync(options);
  }
  
  /**
   * Start Plato inspector and visualizer
   */
  function startPlatoVisualizer(done) {
  	log('Running Plato');
  
  	var files = glob.sync(config.plato.js);
  	var excludeFiles = /.*\.spec\.js/;
  	var plato = require('plato');
  
  	var options = {
  		title: 'Plato Inspections Report',
  		exclude: excludeFiles
  	};
  	var outputDir = config.report + '/plato';
  
  	plato.inspect(files, outputDir, options, platoCompleted);
  
  	function platoCompleted(report) {
  		var overview = plato.getOverviewReport(report);
  		if (args.verbose) {
  			log(overview.summary);
  		}
  		if (done) {
  			done();
  		}
  	}
  }
  
  /**
   * Start the tests using karma.
   * @param  {boolean} singleRun - True means run once and end (CI), or keep running (dev)
   * @param  {Function} done - Callback to fire when karma is done
   * @return {undefined}
   */
  function startTests(singleRun, done) {
  	var child;
  	var excludeFiles = [];
  	var fork = require('child_process').fork;
  	var Karma = require('karma').Server;
  	var serverSpecs = config.serverIntegrationSpecs;
  	var e2eSpecs = [config.scenarios];
  	var excludeSpecs = [].concat(serverSpecs, e2eSpecs);
  
  	// noinspection Annotator
  	if (args.startServers) {
  		log('Starting servers');
  		var savedEnv = process.env;
  		savedEnv.NODE_ENV = 'dev';
  		savedEnv.PORT = 8888;
  		child = fork(config.nodeServer);
  	} else {
  		// make sure server and e2e specs are not run through karma
  		if (excludeSpecs && excludeSpecs.length) {
  			excludeFiles = excludeSpecs;
  		}
  	}
  
  	new Karma(
  		{
  			configFile: __dirname + '/karma.conf.js',
  			exclude: excludeFiles,
  			singleRun: singleRun
  		},
  		karmaCompleted
  	).start();
  
  	////////////////
  
  	function karmaCompleted(karmaResult) {
  		log('Karma completed');
  		if (child) {
  			log('shutting down the child process');
  			child.kill();
  		}
  		if (karmaResult === 1) {
  			done('karma: tests failed with code ' + karmaResult);
  		} else {
  			done();
  		}
  	}
  }
  
  /**
   * Formatter for bytediff to display the size changes after processing
   * @param  {Object} data - byte data
   * @return {String}      Difference in bytes, formatted
   */
  function bytediffFormatter(data) {
  	var difference = data.savings > 0 ? ' smaller.' : ' larger.';
  	return (
  		data.fileName +
  		' went from ' +
  		(data.startSize / 1000).toFixed(2) +
  		' kB to ' +
  		(data.endSize / 1000).toFixed(2) +
  		' kB and is ' +
  		formatPercent(1 - data.percent, 2) +
  		'%' +
  		difference
  	);
  }
  
  /**
   * Log an error message and emit the end of a task
   */
  //function errorLogger(error) {
  //    log('*** Start of Error ***');
  //    log(error);
  //    log('*** End of Error ***');
  //    this.emit('end');
  //}
  
  /**
   * Format a number as a percentage
   * @param  {Number} num       Number to format as a percent
   * @param  {Number} precision Precision of the decimal
   * @return {String}           Formatted perentage
   */
  function formatPercent(num, precision) {
  	return (num * 100).toFixed(precision);
  }
  
  /**
   * Format and return the header for files
   * @return {String}           Formatted file header
   */
  function getHeader() {
  	var pkg = require('./package.json');
  	var template = [
  		'/**',
  		' * <%= pkg.name %> - <%= pkg.description %>',
  		' * @authors <%= pkg.authors %>',
  		' * @version v<%= pkg.version %>',
  		' * @link <%= pkg.homepage %>',
  		' * @license <%= pkg.license %>',
  		' */',
  		''
  	].join('\n');
  	// noinspection Annotator
  	return $.header(template, {
  		pkg: pkg
  	});
  }
  
  /**
   * Log a message or series of messages using chalk's blue color.
   * Can pass in a string, object or array.
   */
  function log(msg) {
  	if (typeof msg === 'object') {
  		for (var item in msg) {
  			if (msg.hasOwnProperty(item)) {
  				// noinspection Annotator
  				// noinspection Annotator
  				$.util.log($.util.colors.blue(msg[item]));
  			}
  		}
  	} else {
  		// noinspection Annotator
  		// noinspection Annotator
  		$.util.log($.util.colors.blue(msg));
  	}
  }
  
  /**
   * Show OS level notification using node-notifier
   */
  function notify(options) {
  	var notifier = require('node-notifier');
  	var notifyOptions = {
  		sound: 'Bottle',
  		contentImage: path.join(__dirname, 'gulp.png'),
  		icon: path.join(__dirname, 'gulp.png')
  	};
  	_.assign(notifyOptions, options);
  	// noinspection Annotator
  	notifier.notify(notifyOptions);
  }
  
  module.exports = gulp;
  