'use strict';

var sinon = require('sinon');
var main = require('../package.json').main;
var schedule = require('../' + main);

var es6;
try {
  eval('(function* () {})()');
  es6 = require('./es6/job-test')(schedule);
} catch (e) {}

var clock;

module.exports = {
  setUp: function(cb) {
    clock = sinon.useFakeTimers();
    cb();
  },
  "Job constructor": {
    "Accepts Job name and function to run": function(test) {
      var job = new schedule.Job('the job', function() {});

      test.equal(job.name, 'the job');
      test.done();
    },
    "Job name is optional and will be auto-generated": function(test) {
      var job = new schedule.Job();

      test.ok(job.name);
      test.done();
    },
    "Uses unique names across auto-generated Job names": function(test) {
      var job1 = new schedule.Job();
      var job2 = new schedule.Job();

      test.notEqual(job1.name, job2.name);
      test.done();
    }
  },
  "#schedule(Date)": {
    "Runs job once at some date": function(test) {
      test.expect(1);

      var job = new schedule.Job(function() {
        test.ok(true);
      });

      job.schedule(new Date(Date.now() + 3000));

      setTimeout(function() {
        test.done();
      }, 3250);

      clock.tick(3250);
    },
    "Cancel next job before it runs": function(test) {
      test.expect(1);

      var job = new schedule.Job(function() {
        test.ok(true);
      });

      job.schedule(new Date(Date.now() + 1500));
      job.schedule(new Date(Date.now() + 3000));
      job.cancelNext();
      setTimeout(function() {
        test.done();
      }, 3250);

      clock.tick(3250);
    },
    "Run job on specified date": function(test) {
      test.expect(1);

      var job = new schedule.Job(function() {
        test.ok(true);
      });

      job.runOnDate(new Date(Date.now() + 3000));

      setTimeout(function() {
        test.done();
      }, 3250);

      clock.tick(3250);
    },
    "Run job in generator": function(test) {
      if (!es6) {
        test.expect(0);
        test.done();
        return;
      }

      es6.jobInGenerator(test);

      clock.tick(3250);
    },
    "Context is passed into generator correctly": function(test) {
      if (!es6) {
        test.expect(0);
        test.done();
        return;
      }

      es6.jobContextInGenerator(test);

      clock.tick(3250);
    },
    "Won't run job if scheduled in the past": function(test) {
      test.expect(0);

      var job = new schedule.Job(function() {
        test.ok(false);
      });

      job.schedule(new Date(Date.now() - 3000));

      setTimeout(function() {
        test.done();
      }, 1000);

      clock.tick(1000);
    },
    "Jobs still run after scheduling a Job in the past": function(test) {
      test.expect(1);

      var pastJob = new schedule.Job(function() {
      // Should not run, blow up if it does
        test.ok(false);
      });

      pastJob.schedule(new Date(Date.now() - 3000));

      var job = new schedule.Job(function() {
        test.ok(true);
      });

      job.schedule(new Date(Date.now() + 3000));

      setTimeout(function() {
        test.done();
      }, 3250);

      clock.tick(3250);
    },
    "Job emits 'scheduled' event with 'run at' Date": function(test) {
      test.expect(1);

      var date = new Date(Date.now() + 3000);
      var job = new schedule.Job(function() {
        test.done();
      });

      job.on('scheduled', function(runAtDate) {
        test.equal(runAtDate.getTime(), date.getTime());
      });

      job.schedule(date);
      clock.tick(3250);
    }
  },
  "#schedule(Date, fn)": {
    "Runs job once at some date, calls callback when done": function(test) {
      test.expect(1);

      var job = new schedule.Job(function() {}, function() {
        test.ok(true);
      });

      job.schedule(new Date(Date.now() + 3000));

      setTimeout(function() {
        test.done();
      }, 3250);

      clock.tick(3250);
    }
  },
  "#schedule(RecurrenceRule)": {
    "Runs job at interval based on recur rule, repeating indefinitely": function(test) {
      test.expect(3);

      var job = new schedule.Job(function() {
        test.ok(true);
      });

      var rule = new schedule.RecurrenceRule();
      rule.second = null; // fire every second

      job.schedule(rule);

      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);

      clock.tick(3250);
    },
    "Job emits 'scheduled' event for every next invocation": function(test) {
      // Job will run 3 times but be scheduled 4 times, 4th run never happens
      // due to cancel.
      test.expect(4);

      var job = new schedule.Job(function() {});

      job.on('scheduled', function(runOnDate) {
        test.ok(true);
      });

      var rule = new schedule.RecurrenceRule();
      rule.second = null; // fire every second

      job.schedule(rule);

      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);

      clock.tick(3250);
    },
    "Doesn't invoke job if recur rule schedules it in the past": function(test) {
      test.expect(0);

      var job = new schedule.Job(function() {
        test.ok(false);
      });

      var rule = new schedule.RecurrenceRule();
      rule.year = 2000;

      job.schedule(rule);

      setTimeout(function() {
        job.cancel();
        test.done();
      }, 1000);

      clock.tick(1000);
    }
  },
  "#schedule({...})": {
    "Runs job at interval based on object, repeating indefinitely": function(test) {
      test.expect(3);

      var job = new schedule.Job(function() {
        test.ok(true);
      });

      job.schedule({
        second: null // fire every second
      });

      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);

      clock.tick(3250);
    },
    "Job emits 'scheduled' event for every next invocation": function(test) {
      // Job will run 3 times but be scheduled 4 times, 4th run never happens
      // due to cancel.
      test.expect(4);

      var job = new schedule.Job(function() {});

      job.on('scheduled', function(runOnDate) {
        test.ok(true);
      });

      job.schedule({
        second: null // Fire every second
      });

      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);

      clock.tick(3250);
    },
    "Doesn't invoke job if object schedules it in the past": function(test) {
      test.expect(0);

      var job = new schedule.Job(function() {
        test.ok(false);
      });

      job.schedule({
        year: 2000
      });

      setTimeout(function() {
        job.cancel();
        test.done();
      }, 1000);

      clock.tick(1000);
    }
  },
  "#schedule('jobName', {...})": {
    "Runs job with a custom name input": function(test) {
      test.expect(3);

      var job = new schedule.Job('jobName', function() {
        test.equal(job.name, 'jobName');
      });

      job.schedule({
        second: null // fire every second
      });

      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);

      clock.tick(3250);
    }
  },
  "#schedule({...}, {...})": {
    "Runs job and run callback when job is done if callback is provided": function(test) {
      test.expect(3);

      var job = new schedule.Job(function() {}, function() {
        test.ok(true);
      });

      job.schedule({
        second: null // fire every second
      });

      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);

      clock.tick(3250);
    },
    "Runs job with a custom name input and run callback when job is done": function(test) {
      test.expect(3);

      var job = new schedule.Job('MyJob', function() {}, function() {
        test.equal(job.name, 'MyJob');
      });

      job.schedule({
        second: null // fire every second
      });

      setTimeout(function() {
        job.cancel();
        test.done();
      }, 3250);

      clock.tick(3250);
    }
  },
  "#cancel": {
    "Prevents all future invocations": function(test) {
      test.expect(1);

      var job = new schedule.Job(function() {
        test.ok(true);
      });

      job.schedule({
        second: null // fire every second
      });

      setTimeout(function() {
        job.cancel();
      }, 1250);

      setTimeout(function() {
        test.done();
      }, 2250);

      clock.tick(2250);
    },
    "Cancelled job reschedules": function(test) {
      test.expect(1);
      var ok = false;

      var job = schedule.scheduleJob('*/1 * * * * *', function () {});

      setTimeout(function() {
        job.cancel(true);
        if (job.nextInvocation() !== null) ok = true;
      }, 1250);

      setTimeout(function() {
        job.cancel();
        test.ok(ok);
        test.done();
      }, 2250);

      clock.tick(2250);
    },
    "CancelNext job reschedules": function(test) {
      test.expect(1);
      var ok = false;

      var job = schedule.scheduleJob('*/1 * * * * *', function () {});

      setTimeout(function() {
        job.cancelNext();
        if (job.nextInvocation() !== null) ok = true;
      }, 1250);

      setTimeout(function() {
        job.cancel();
        test.ok(ok);
        test.done();
      }, 2250);

      clock.tick(2250);
    },
    "Job emits 'canceled' event": function(test) {
      test.expect(1);

      var job = new schedule.Job(function() {});

      job.on('canceled', function() {
        test.ok(true);
      });

      job.schedule({
        second: null // fire every second
      });

      setTimeout(function() {
        job.cancel();
      }, 1250);

      setTimeout(function() {
        test.done();
      }, 2250);

      clock.tick(2250);
    },
    "Job is added to scheduledJobs when created and removed when cancelled": function(test) {
      test.expect(4);

      var job1 = new schedule.Job('cancelJob', function() {});
      job1.schedule({
        second: null // fire every second
      });

      var job2 = schedule.scheduleJob('second',
                                      { second: null },
                                      function() {},
                                      function() {});

      test.strictEqual(schedule.scheduledJobs.cancelJob, job1);
      test.strictEqual(schedule.scheduledJobs.second, job2);
      setTimeout(function() {
        job1.cancel();
        job2.cancel();
        test.strictEqual(schedule.scheduledJobs.cancelJob, undefined);
        test.strictEqual(schedule.scheduledJobs.second, undefined);
        test.done();
      }, 1250);

      clock.tick(1250);
    }
  },
  "#reschedule": {
    "When rescheduled counter will be reset to zero": function(test) {

      var job = new schedule.scheduleJob({
        second: null
      }, function() {});

      setTimeout(function() {
        test.equal(job.triggeredJobs(), 3);
        schedule.rescheduleJob(job, {
          minute: null
        });
      }, 3250);

      setTimeout(function() {
        job.cancel();
        test.equal(job.triggeredJobs(), 0);
        test.done();
      }, 5000);

      clock.tick(5000);
    }
  },
  "When invoked": {
    "Job emits 'run' event": function(test) {
      test.expect(1);

      var job = new schedule.Job(function() {});

      job.on('run', function() {
        test.ok(true);
      });

      job.schedule(new Date(Date.now() + 3000));

      setTimeout(function() {
        test.done();
      }, 3250);

      clock.tick(3250);
    },
    "Job counter increase properly": function(test) {
      var job = new schedule.Job(function() {});

      job.schedule({
        second: null // fire every second
      });

      setTimeout(function() {
        job.cancel();
        test.equal(job.triggeredJobs(), 2);
        test.done();
      }, 2250);

      clock.tick(2250);
    },
    "Job gets invoked with the fire date": function (test) {
      test.expect(2);
      var prevFireDate;
      var job = new schedule.Job(function (fireDate) {
        if (!prevFireDate) {
          test.ok(fireDate instanceof Date);
        } else {
          test.equal(fireDate.getTime() - prevFireDate.getTime(), 1000);
        }
        prevFireDate = fireDate;
      });

      job.schedule({
        second: null // fire every second
      });

      setTimeout(function () {
        job.cancel();
        test.done();
      }, 2250);

      clock.tick(2250);
    }
  },
  tearDown: function(cb) {
    clock.restore();
    cb();
  }
};