/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';

const EventEmitter = require('events');
const async = require('async');
const _ = require('lodash');
const arrivals = require('arrivals');
const debug = require('debug')('phases');

module.exports = phaser;

function phaser(phaseSpecs) {
  let ee = new EventEmitter();

  let tasks = _.map(phaseSpecs, function(spec, i) {
    if (!spec.index) {
      spec.index = i;
    }

    if (spec.arrivalRate && !spec.rampTo) {
      spec.mode = spec.mode || 'uniform';
    }

    if (spec.pause) {
      return createPause(spec, ee);
    } else if (spec.rampTo && spec.arrivalRate) {
      return createRamp(spec, ee);
    } else if (spec.arrivalCount) {
      return createArrivalCount(spec, ee);
    } else if (spec.arrivalRate) {
      return createArrivalRate(spec, ee);
    } else {
      console.log('Unknown phase spec\n%j\nThis should not happen', spec);
    }
  });

  ee.run = function() {
    async.series(tasks, function(err) {
      if (err) {
        debug(err);
      }

      ee.emit('done');
    });
  };

  return ee;
}

function createPause(spec, ee) {
  const duration = spec.pause * 1000;
  const task = function(callback) {
    ee.emit('phaseStarted', spec);
    setTimeout(function() {
      ee.emit('phaseCompleted', spec);
      return callback(null);
    }, duration);
  };
  return task;
}

function createRamp(spec, ee) {
  const incBy = (spec.rampTo - spec.arrivalRate) / (spec.duration - 1);
  const stepCount = spec.duration;
  const arrivalRate = spec.arrivalRate;

  debug('rampTo: incBy = %s', incBy);
  let steps = _.map(_.range(0, stepCount), function(i) {
    return function(callback) {
      let tick = 1000 / (arrivalRate + i * incBy);
      debug('rampTo: tick = %s', tick);
      let p = arrivals.uniform.process(tick, 1000);
      p.on('arrival', function() {
        ee.emit('arrival');
      });
      p.on('finished', function() {
        return callback(null);
      });
      p.start();
    };
  });

  const task = function task(callback) {
    ee.emit('phaseStarted', spec);
    async.series(steps, function(err) {
      if (err) {
        debug(err);
      }
      ee.emit('phaseCompleted', spec);
      return callback(null);
    });
  };

  return task;
}

function createArrivalCount(spec, ee) {
  const task = function(callback) {
    ee.emit('phaseStarted', spec);
    const duration = spec.duration * 1000;
    const interval = duration / spec.arrivalCount;
    const p = arrivals.uniform.process(interval, duration);
    p.on('arrival', function() {
      ee.emit('arrival');
    });
    p.on('finished', function() {
      ee.emit('phaseCompleted', spec);
      return callback(null);
    });
    p.start();
  };

  return task;
}

function createArrivalRate(spec, ee) {
  const task = function(callback) {
    ee.emit('phaseStarted', spec);
    const ar = 1000 / spec.arrivalRate;
    const duration = spec.duration * 1000;
    debug('creating a %s process for arrivalRate', spec.mode);
    const p = arrivals[spec.mode].process(ar, duration);
    p.on('arrival', function() {
      ee.emit('arrival');
    });
    p.on('finished', function() {
      ee.emit('phaseCompleted', spec);
      return callback(null);
    });
    p.start();
  };

  return task;
}

@@ -311,24 +311,59 @@
 ('lodash');%0A
+const isUndefined = _.isUndefined;%0A
 const arriva
@@ -579,17 +579,28 @@
     if (
-!
+isUndefined(
 spec.ind
@@ -602,16 +602,17 @@
 c.index)
+)
  %7B%0A     
@@ -638,24 +638,37 @@
   %7D%0A%0A    if 
+(!isUndefined
 (spec.arriva
@@ -676,13 +676,25 @@
 Rate
+)
  && 
-!
+isUndefined(
 spec
@@ -701,16 +701,17 @@
 .rampTo)
+)
  %7B%0A     
@@ -757,24 +757,37 @@
   %7D%0A%0A    if 
+(!isUndefined
 (spec.pause)
@@ -786,16 +786,17 @@
 c.pause)
+)
  %7B%0A     
@@ -835,33 +835,215 @@
    %7D
- else if (spec.rampTo && 
+%0A%0A    if (!isUndefined(spec.arrivalCount)) %7B%0A      return createArrivalCount(spec, ee);%0A    %7D%0A%0A    if (!isUndefined(spec.arrivalRate)) %7B%0A%0A      // If arrivalRate is zero, it's the same as a pause:%0A      if (
 spec
@@ -1046,36 +1046,44 @@
 spec.arrivalRate
+ === 0
 ) %7B%0A
+  
       return cre
@@ -1089,60 +1089,188 @@
 eate
-Ramp(spec, ee);%0A    %7D else if (spec.arrivalCount
+Pause(%0A          Object.assign(%0A            spec, %7B pause: spec.duration %7D),%0A          ee);%0A      %7D%0A%0A      // If it's a ramp, create that:%0A      if (!isUndefined(spec.rampTo)
 ) %7B%0A
+  
     
@@ -1280,36 +1280,28 @@
 eturn create
-ArrivalCount
+Ramp
 (spec, ee);%0A
@@ -1308,38 +1308,59 @@
     
+ 
-%7D else if (spec.arrivalRate) %7B
+ %7D%0A%0A      // Otherwise it's a plain arrival phase:
 %0A   
@@ -1407,18 +1407,10 @@
    %7D
- else %7B%0A  
+%0A%0A
     
@@ -1478,22 +1478,16 @@
  spec);%0A
-    %7D%0A
   %7D);%0A%0A 

Make phase definitions with arrivalRate = 0 work