/*
 * grunt-contrib-watch
 * http://gruntjs.com/
 *
 * Copyright (c) 2018 "Cowboy" Ben Alman, contributors
 * Licensed under the MIT license.
 */

'use strict';

var path = require('path');
var Gaze = require('gaze').Gaze;
var _ = require('lodash');
var waiting = 'Waiting...';
var changedFiles = Object.create(null);
var watchers = [];

module.exports = function(grunt) {

  var taskrun = require('./lib/taskrunner')(grunt);

  // Default date format logged
  var dateFormat = function(time) {
    grunt.log.writeln(String(
      'Completed in ' +
      time.toFixed(3) +
      's at ' +
      (new Date()).toString()
    ).cyan + ' - ' + waiting);
  };

  // When task runner has started
  taskrun.on('start', function() {
    Object.keys(changedFiles).forEach(function(filepath) {
      // Log which file has changed, and how.
      grunt.log.ok('File "' + filepath + '" ' + changedFiles[filepath] + '.');
    });
    // Reset changedFiles
    changedFiles = Object.create(null);
  });

  // When task runner has ended
  taskrun.on('end', function(time) {
    if (time > 0) {
      dateFormat(time);
    }
  });

  // When a task run has been interrupted
  taskrun.on('interrupt', function() {
    grunt.log.writeln('').write('Scheduled tasks have been interrupted...'.yellow);
  });

  // When taskrun is reloaded
  taskrun.on('reload', function() {
    taskrun.clearRequireCache(Object.keys(changedFiles));
    grunt.log.writeln('').writeln('Reloading watch config...'.cyan);
  });

  grunt.registerTask('watch', 'Run predefined tasks whenever watched files change.', function(target) {
    var self = this;
    var name = self.name || 'watch';

    // Close any previously opened watchers
    watchers.forEach(function(watcher) {
      watcher.close();
    });
    watchers = [];

    // Never gonna give you up, never gonna let you down
    if (grunt.config([name, 'options', 'forever']) !== false) {
      taskrun.forever();
    }

    // If a custom dateFormat function
    var df = grunt.config([name, 'options', 'dateFormat']);
    if (typeof df === 'function') {
      dateFormat = df;
    }

    if (taskrun.running === false) {
      grunt.log.writeln(waiting);
    }

    // Initialize taskrun
    var targets = taskrun.init(name, {target: target});

    targets.forEach(function(target) {
      if (typeof target.files === 'string') {
        target.files = [target.files];
      }

      // Process into raw patterns
      var patterns = _.chain(target.files).flatten().map(function(pattern) {
        return grunt.config.process(pattern);
      }).value();

      // Validate the event option
      if (typeof target.options.event === 'string') {
        target.options.event = [target.options.event];
      }

      var eventCwd = process.cwd();
      if (target.options.cwd && target.options.cwd.event) {
        eventCwd = target.options.cwd.event;
      }

      // Set cwd if options.cwd.file is set
      if (typeof target.options.cwd !== 'string' && target.options.cwd.files) {
        target.options.cwd = target.options.cwd.files;
      }

      // Create watcher per target
      watchers.push(new Gaze(patterns, target.options, function(err) {
        if (err) {
          if (typeof err === 'string') {
            err = new Error(err);
          }
          grunt.log.writeln('ERROR'.red);
          grunt.fatal(err);
          return taskrun.done();
        }

        // Log all watched files with --verbose set
        if (grunt.option('verbose')) {
          var watched = this.watched();
          Object.keys(watched).forEach(function(watchedDir) {
            watched[watchedDir].forEach(function(watchedFile) {
              grunt.log.writeln('Watching ' + path.relative(process.cwd(), watchedFile) + ' for changes.');
            });
          });
        }

        // On changed/added/deleted
        this.on('all', function(status, filepath) {

          // Skip events not specified
          if (!_.includes(target.options.event, 'all') &&
              !_.includes(target.options.event, status)) {
            return;
          }

          filepath = path.relative(eventCwd, filepath);

          // Skip empty filepaths
          if (filepath === '') {
            return;
          }

          // If Gruntfile.js changed, reload self task
          if (target.options.reload || /gruntfile\.(js|coffee)/i.test(filepath)) {
            taskrun.reload = true;
          }

          // Emit watch events if anyone is listening
          if (grunt.event.listeners('watch').length > 0) {
            grunt.event.emit('watch', status, filepath, target.name);
          }

          // Group changed files only for display
          changedFiles[filepath] = status;

          // Add changed files to the target
          if (taskrun.targets[target.name]) {
            if (!taskrun.targets[target.name].changedFiles) {
              taskrun.targets[target.name].changedFiles = Object.create(null);
            }
            taskrun.targets[target.name].changedFiles[filepath] = status;
          }

          // Queue the target
          if (taskrun.queue.indexOf(target.name) === -1) {
            taskrun.queue.push(target.name);
          }

          // Run the tasks
          taskrun.run();
        });

        // On watcher error
        this.on('error', function(err) {
          if (typeof err === 'string') {
            err = new Error(err);
          }
          grunt.log.error(err.message);
        });
      }));
    });

  });
};
