text: 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 sass = require('gulp-sass');
  var path = require('path');
  var _ = require('lodash');
  var $ = require('gulp-load-plugins')({
      lazy: true
  });
  var replace = require('gulp-replace');
  
  var colors = $.util.colors;
  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.
   */
  
  /**
   * 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');
  
      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 sass to css
   * @return {Stream}
   */
  gulp.task('styles', ['clean-styles'], function() {
      log('Compiling sass --> CSS');
  
      return gulp
          .src(config.sass)
          .pipe($.plumber()) // exit gracefully if something fails after this
          .pipe($.sass().on('error', sass.logError))
  
      //.pipe($.autoprefixer({
      //browsers: ['last 2 version', '> 5%']
      //}))
      .pipe(gulp.dest(config.temp));
  });
  
  gulp.task('sass-watcher', function() {
      gulp.watch([config.sass], ['styles']);
  });
  
  /**
   * 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');
  
      return gulp
          .src(config.images)
          .pipe($.imagemin({
              optimizationLevel: 4
          }))
          .pipe(gulp.dest(config.build + 'images'));
  });
  
  
  /**
   * Copy i18n partial translations
   * @return {Stream}
   */
  gulp.task('i18n', ['clean-i18n'], function() {
      log('Copying partial i18n translations');
  
      return gulp
          .src(config.i18n)
          .pipe(gulp.dest(config.build + 'i18n'));
  });
  
  /**
   * Remove all i18n partial translations
   * @param  {Function} done - callback when complete
   */
  gulp.task('clean-i18n', function(done) {
      clean(config.build + 'i18n/**/*.*', done);
  });
  
  /**
   * Create $templateCache from the html templates
   * @return {Stream}
   */
  gulp.task('templatecache', ['clean-code'], function() {
      log('Creating an AngularJS $templateCache');
  
      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
      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))
          /*Only for django*/
          //.pipe(replace('src="/src', 'src="{% static \'/src'))
          //.pipe(replace('.js"', '.js\' %}"'))
          //.pipe(replace('href="/.tmp', 'href="{% static \'/.tmp'))
          //.pipe(replace('.css"', '.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;
  
      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', 'i18n', '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'], function() {
      log('Optimizing the js, css, and html');
  
      var assets = $.useref.assets({
          searchPath: './'
      });
      // Filters are named for the gulp-useref path
      var cssFilter = $.filter('**/*.css');
      var jsAppFilter = $.filter('**/' + config.optimized.app);
      var jslibFilter = $.filter('**/' + config.optimized.lib);
  
      var templateCache = config.temp + config.templateCache.file;
  
      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())
          //Insert django tags into de build index
          .pipe(replace('src="js', 'src="{% static \'js'))
          .pipe(replace('.js"', '.js\' %}"'))
          .pipe(replace('href="styles', 'href="{% static \'styles'))
          .pipe(replace('.css"', '.css\' %}"'))
          .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);
      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);
  });
  
  /**
   * 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;
      var version = args.ver;
      var options = {};
      if (version) {
          options.version = version;
          msg += ' to ' + version;
      } else {
          options.type = type;
          msg += ' for a ' + type;
      }
      log(msg);
  
      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);
  
  
  gulp.task('watch', function() {
      gulp.watch([config.sass, config.js, config.html])
          .on('change', changeEvent);
  });
  
  
  gulp.task('django', function() {
      gulp.watch([config.sass, config.html, config.js], ['build']);
      //Hay que introducir el siguiente comando en linux para que observe muchos ficheros
      // echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
  });
  ////////////////
  
  /**
   * 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}   path - array of paths to delete
   * @param  {Function} done - callback when complete
   */
  function clean(path, done) {
      log('Cleaning: ' + $.util.colors.blue(path));
      del(path, done);
  }
  
  /**
   * Inject files in a sorted sequence at a specified inject label
   * @param   {Array} 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;
      }
  
      return $.inject(orderSrc(src, order), options);
  }
  
  /**
   * Order a stream
   * @param   {Stream} src   The gulp.src stream
   * @param   {Array} order Glob array pattern
   * @returns {Stream} The ordered stream
   */
  function orderSrc(src, order) {
      //order = order || ['**/*'];
      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 = '--debug';
      var nodeOptions = getNodeOptions(isDev);
  
      nodeOptions.nodeArgs = [debugMode + '=5858'];
  
      if (args.verbose) {
          console.log(nodeOptions);
      }
  
      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 BrowserSync
   * --nosync will avoid browserSync
   */
  function startBrowserSync(isDev, specRunner) {
      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 sass, compiles it to css, browser-sync handles reload
      if (isDev) {
          gulp.watch([config.sass], ['styles'])
              .on('change', changeEvent);
      } else {
          gulp.watch([config.sass, config.js, config.html], ['browserSyncReload'])
              .on('change', changeEvent);
      }
  
      var options = {
          proxy: 'localhost:' + port,
          port: 3000,
          files: isDev ? [
              config.client + '**/*.*',
              '!' + config.sass,
              config.temp + '**/*.css'
          ] : [],
          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;
  
      if (args.startServers) {
          log('Starting servers');
          var savedEnv = process.env;
          savedEnv.NODE_ENV = 'dev';
          savedEnv.PORT = 8888;
          child = fork(config.nodeServer);
      } else {
          if (serverSpecs && serverSpecs.length) {
              excludeFiles = serverSpecs;
          }
      }
  
      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');
      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)) {
                  $.util.log($.util.colors.blue(msg[item]));
              }
          }
      } else {
          $.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);
      notifier.notify(notifyOptions);
  }
  
  module.exports = gulp;