// Timer event driver
var Patch = require("./patch.js");
var DemandMatcher = require('./demand-matcher.js').DemandMatcher;
var Struct = require('./struct.js');

var Dataspace_ = require("./dataspace.js");
var Dataspace = Dataspace_.Dataspace;
var __ = Dataspace_.__;
var _$ = Dataspace_._$;

var periodicTick = Struct.makeConstructor('periodicTick', ['intervalMS']); // message
var timeLaterThan = Struct.makeConstructor('timeLaterThan', ['deadlineMS']); // assertion

function spawnTimerDriver() {
  Dataspace.spawn(
    new DemandMatcher([Patch.observe(periodicTick(_$('intervalMS')))],
		      [Patch.advertise(periodicTick(_$('intervalMS')))],
                      function (c) {
			Dataspace.spawn(new Tick(c.intervalMS));
		      }));
  Dataspace.spawn(
    new DemandMatcher([Patch.observe(timeLaterThan(_$('deadlineMS')))],
		      [Patch.advertise(timeLaterThan(_$('deadlineMS')))],
                      function (c) {
			Dataspace.spawn(new Alarm(c.deadlineMS));
		      }));
}

function Tick(intervalMS) {
  this.intervalMS = intervalMS;
  this.handle = null;
}

Tick.prototype.boot = function () {
  var self = this;
  this.handle = setInterval(Dataspace.wrap(function () {
    Dataspace.send(periodicTick(self.intervalMS));
  }), this.intervalMS);

  return Patch.sub(Patch.observe(periodicTick(this.intervalMS))) // monitor interest
    .andThen(Patch.pub(periodicTick(this.intervalMS))) // signal we exist to DemandMatcher
  ;
};

Tick.prototype.trapexit = function () {
  this.cancelTimer();
};

Tick.prototype.cancelTimer = function () {
  if (this.handle !== null) {
    clearInterval(this.handle);
    this.handle = null;
  }
};

Tick.prototype.handleEvent = function (e) {
  if (e.type === 'stateChange' && e.patch.hasRemoved()) {
    Dataspace.exit();
  }
};

function Alarm(deadlineMS) {
  this.deadlineMS = deadlineMS;
  this.handle = null;
}

Alarm.prototype.boot = function () {
  this.checkTimeout();

  return Patch.sub(Patch.observe(timeLaterThan(this.deadlineMS))) // monitor interest
    .andThen(Patch.pub(timeLaterThan(this.deadlineMS))) // signal we exist to DemandMatcher
  ;
};

Alarm.prototype.checkTimeout = function () {
  var now = +(new Date());
  var delta = this.deadlineMS - now;
  if (delta <= 0) {
    Dataspace.stateChange(Patch.assert(timeLaterThan(this.deadlineMS)));
    this.cancelTimer();
  } else if (this.handle === null) {
    var self = this;
    this.handle = setTimeout(Dataspace.wrap(function () { self.checkTimeout(); }), delta);
  }
};

Alarm.prototype.cancelTimer = function () {
  if (this.handle !== null) {
    clearTimeout(this.handle);
    this.handle = null;
  }
};

Alarm.prototype.trapexit = function () {
  this.cancelTimer();
};

Alarm.prototype.handleEvent = function (e) {
  if (e.type === 'stateChange' && e.patch.hasRemoved()) {
    Dataspace.exit();
  }
};

///////////////////////////////////////////////////////////////////////////

module.exports.spawnTimerDriver = spawnTimerDriver;
module.exports.periodicTick = periodicTick;
module.exports.timeLaterThan = timeLaterThan;
module.exports.Tick = Tick;
module.exports.Alarm = Alarm;
