/**
 * backtalk v0.0.0 build 22.07.2015
 * https://github.com/vanruesc/backtalk
 * Copyright 2015 Raoul van Rueschen, Zlib
 */

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";

/**
 * Event Dispatcher.
 * A base class for adding and removing event listeners and dispatching events.
 *
 * @constructor
 */

function EventDispatcher()
{
 this._listeners = {};
}

/**
 * Adds an event listener.
 *
 * @param {string} type - The event type.
 * @param {function} listener - The event listener.
 */

EventDispatcher.prototype.addEventListener = function(type, listener)
{
 if(this._listeners[type] === undefined)
 {
  this._listeners[type] = [];
 }

 if(this._listeners[type].indexOf(listener) === -1)
 {
  this._listeners[type].push(listener);
 }
};

/**
 * Checks if the event listener exists.
 *
 * @param {string} type - The event type.
 * @param {function} listener - The event listener.
 */

EventDispatcher.prototype.hasEventListener = function(type, listener)
{
 return(this._listeners[type] !== undefined && this._listeners[type].indexOf(listener) !== -1);
};

/**
 * Removes an event listener.
 *
 * @param {string} type - The event type.
 * @param {function} listener - The event listener.
 */

EventDispatcher.prototype.removeEventListener = function(type, listener)
{
 var i, listeners = this._listeners,
  listenerArray = listeners[type];

 if(listenerArray !== undefined)
 {
  i = listenerArray.indexOf(listener);

  if(i !== -1)
  {
   listenerArray.splice(i, 1);
  }
 }
};

/**
 * Dispatches an event to all respective listeners.
 *
 * @param {Object} event - The event.
 */

EventDispatcher.prototype.dispatchEvent = function(event)
{
 var i, l, listeners = this._listeners,
  listenerArray = listeners[event.type];

 if(listenerArray !== undefined)
 {
  event.target = this;

  for(i = 0, l = listenerArray.length; i < l; ++i)
  {
   listenerArray[i].call(this, event);
  }
 }
};

module.exports = EventDispatcher;

},{}],2:[function(require,module,exports){
"use strict";

var EventDispatcher = require("@zayesh/eventdispatcher");

/**
 * Overtime.
 * A time limit visualization library.
 *
 * @constructor
 * @param {Object} options - The settings.
 * @param {number} [options.time] - The time limit.
 * @param {number} [options.canvas] - The canvas to use. A new one will be created if none is supplied.
 * @param {boolean} [options.clearCanvas] - Whether the canvas should be cleared before rendering. Default is true.
 * @param {Array} [options.size] - The size of the canvas as an array: [width, height].
 * @param {Overtime.TimeMeasure} [options.timeMeasure] - The time measure of the supplied time limit. Defaults to seconds.
 */

function Overtime(options)
{
 var self = this, o;

 EventDispatcher.call(this);

 this.TWO_PI = Math.PI * 2.0;
 this.HALF_PI = Math.PI * 0.5;

 this.clear = true;
 this.animId = 0;
 this.now = Date.now();
 this.then = this.now;
 this.ctx = null;
 this.canvas = document.createElement("canvas");

 this.startAngle = -this.HALF_PI;
 this.threshold = 0.023; // Chrome hack.
 this.fullCircle = this.startAngle + this.TWO_PI;
 this.primaryStrokeStyle = "rgba(255, 100, 0, 0.9)";
 this.secondaryStrokeStyle = "rgba(0, 0, 0, 0.1)";
 this.updateEvent = {type: "update", time: 0};

 this.tm = Overtime.TimeMeasure.MILLISECONDS;
 this.t = 1;

 if(options !== undefined)
 {
  if(options.timeMeasure > 0) { this.tm = options.timeMeasure; }
  if(options.time > 0) { this.t = options.time; }
  if(options.canvas !== undefined) { this.canvas = options.canvas; }
  this.size = options.size;
 }

 this.t *= this.tm;
 this.T = this.t;

 // Try to recover time values from a previous session.
 if(localStorage.getItem("overtime"))
 {
  try
  {
   o = JSON.parse(localStorage.getItem("overtime"));
   if(o.tm !== undefined) { this.tm = o.tm; }
   if(o.t !== undefined) { this.t = o.t; }
   if(o.T !== undefined) { this.T = o.T; }
  }
  catch(e) { /* Swallow. */ }
 }

 // Store the time values for the next session.
 window.addEventListener("unload", function()
 {
  localStorage.setItem("overtime", JSON.stringify({
   tm: self.tm,
   t: self.t,
   T: self.T
  }));
 });

 /**
  * Bind the correct context to the internal update function.
  */

 this.update = function() { self._update(); };
}

Overtime.prototype = Object.create(EventDispatcher.prototype);
Overtime.prototype.constructor = Overtime;

/**
 * Getter for the internal canvas.
 */

Object.defineProperty(Overtime.prototype, "clearCanvas", {
 get: function() { return this.clear; },
 set: function(c) { this.clear = c; }
});

/**
 * Getter and Setter for the internal canvas.
 * 
 * @param {canvas} c - The new canvas to draw on.
 */

Object.defineProperty(Overtime.prototype, "canvas", {
 get: function() { return this.ctx.canvas; },
 set: function(c)
 {
  if(c !== undefined && c.getContext !== undefined)
  {
   this.stop();
   this.ctx = c.getContext("2d");
   this.ctx.strokeStyle = this.primaryStrokeStyle;
   this.size = [c.width, c.height];
  }
 }
});

/**
 * Getter and Setter for the time.
 * 
 * @param {number} t - The new time. Will be translated to the current time measure.
 */

Object.defineProperty(Overtime.prototype, "time", {
 get: function() { return this.t; },
 set: function(t)
 {
  if(t >= 0)
  {
   this.stop();
   this.t = t * this.tm;
   this.T = this.t;
   this._render();
  }
 }
});

/**
 * Getter and Setter for the time measure.
 * The current time will not be affected by this in any way.
 * 
 * @param {Overtime.TimeMeasure} tm - The new time measure.
 */

Object.defineProperty(Overtime.prototype, "timeMeasure", {
 get: function() { return this.tm; },
 set: function(tm)
 {
  if(tm > 0)
  {
   this.tm = tm;
  }
 }
});

/**
 * Getter and Setter for the size of the internal canvas.
 * 
 * @param {Array} s - The new size in the form of [width, height].
 */

Object.defineProperty(Overtime.prototype, "size", {
 get: function()
 {
  return [
   this.ctx.canvas.width,
   this.ctx.canvas.height
  ];
 },
 set: function(s)
 {
  if(s !== undefined)
  {
   this.ctx.canvas.width = s[0];
   this.ctx.canvas.height = s[1];
   this.ctx.lineWidth = (s[0] < s[1]) ? s[0] * 0.05 : s[1] * 0.05;
   this._render();
  }
 }
});

/**
 * Renders the time progress on the canvas.
 */

Overtime.prototype._render = function()
{
 var ctx = this.ctx,
  w = ctx.canvas.width,
  h = ctx.canvas.height,
  hw = w >> 1, hh = h >> 1,
  radius = w < h ? hw : hh,
  endAngle,
  tooThin; // Chrome hack.

 if(this.clear) { ctx.clearRect(0, 0, w, h); }

 // Don't bleed over the edge.
 radius -= ctx.lineWidth;

 // Draw the progress.
 endAngle = this.startAngle + this.TWO_PI * ((this.T - this.t) / this.T);
 tooThin = (endAngle - this.startAngle < this.threshold); // Chrome hack.
 ctx.strokeStyle = this.primaryStrokeStyle;
 ctx.beginPath();
 ctx.arc(hw, hh, radius, tooThin ? this.startAngle - this.threshold : this.startAngle, endAngle, false); // Chrome hack.
 //ctx.arc(hw, hh, radius, this.startAngle, endAngle, false);
 ctx.stroke();
 if(tooThin) { ctx.clearRect(0, 0, hw - this.threshold, hh); } // Chrome hack.

 // Draw the rest of the circle in another color.
 if(endAngle < this.fullCircle)
 {
  // No hacking here cause can't clear.
  ctx.strokeStyle = this.secondaryStrokeStyle;
  ctx.beginPath();
  ctx.arc(hw, hh, radius, endAngle, this.fullCircle, false);
  ctx.stroke();
 }
};

/**
 * Steps the system forward.
 * This is the main loop.
 */

Overtime.prototype._update = function()
{
 var elapsed;

 // Calculate the time span between this run and the last.
 this.now = Date.now();
 elapsed = this.now - this.then;
 this.then = this.now;

 // Update the time.
 this.t -= elapsed;
 if(this.t < 0) { this.t = 0; }
 this.updateEvent.time = this.t;
 this.dispatchEvent(this.updateEvent);

 // Render the time.
 this._render();

 // Continue or exit.
 if(this.t > 0)
 {
  this.animId = requestAnimationFrame(this.update);
 }
 else
 {
  this.dispatchEvent({type: "elapsed"});
 }
};

/**
 * Stops the rendering cycle. Does nothing else besides that.
 */

Overtime.prototype.stop = function()
{
 if(this.animId !== 0)
 {
  cancelAnimationFrame(this.animId);
  this.animId = 0;
 }
};

/**
 * Tries to start the rendering cycle if it isn't
 * running. Otherwise it restarts it.
 */

Overtime.prototype.start = function()
{
 this.stop();
 this.now = Date.now();
 this.then = this.now;
 this.update();
};

/**
 * Sets the time back to its original length.
 */

Overtime.prototype.rewind = function()
{
 this.stop();
 this.t = this.T;
 this._render();
};

/**
 * Sets the time back by the given value.
 * The time will not go back beyond the initial length.
 *
 * @param {number} t - The time by which to rewind. Interpreted according to the current time measure. A negative value corresponds to fast-forwarding.
 */

Overtime.prototype.rewindBy = function(t)
{
 if(typeof t === "number" && !isNaN(t) && t !== 0)
 {
  this.stop();
  this.t += t * this.tm;
  if(this.t > this.T) { this.t = this.T; }
  this._render();
 }
};

/**
 * Goes ahead in time by a given value.
 *
 * @param {number} t - The time value by which to rewind. Will be interpreted according to the current time measure. A negative value corresponds to rewinding.
 */

Overtime.prototype.advanceBy = function(t)
{
 if(typeof t === "number" && !isNaN(t))
 {
  this.rewindBy(-t);
 }
};

/**
 * Adds time.
 *
 * @param {number} t - The time value to add. Will be interpreted according to the current time measure. A negative value corresponds to shortening.
 */

Overtime.prototype.prolongBy = function(t)
{
 if(typeof t === "number" && !isNaN(t) && t !== 0)
 {
  this.stop();
  t *= this.tm;
  this.t += t;
  this.T += t;
  if(this.T < 0) { this.T = this.t = 0; }
  this._render();
 }
};

/**
 * Reduces the total duration of the countdown.
 *
 * @param {number} t - The time value to subtract. Will be interpreted according to the current time measure. A negative value corresponds to prolonging.
 */

Overtime.prototype.shortenBy = function(t)
{
 if(typeof t === "number" && !isNaN(t))
 {
  this.prolongBy(-t);
 }
};

/**
 * Static enumeration of time measure constants.
 */

Overtime.TimeMeasure = Object.freeze({
 MILLISECONDS: 1,
 SECONDS: 1000,
 MINUTES: 60000,
 HOURS: 3600000
});

module.exports = Overtime;

},{"@zayesh/eventdispatcher":1}],3:[function(require,module,exports){
"use strict";

var Overtime = require("overtime"),
 overtime, container, minutes, seconds,
 intervalId, votes, xhr, timeout = 5000, voteCount;

/**
 * Dynamically resize the canvas.
 */

function resize()
{
 var min = (container.offsetWidth < container.offsetHeight) ?
  container.offsetWidth : container.offsetHeight;

 overtime.size = [min, min];
}

/**
 * Sets the time based on the values in the input fields.
 */

function copyTime()
{
 var m, s;

 try
 {
  m = parseInt(minutes.value);
  s = parseInt(seconds.value);
  --s;

  if(m > 59) { m = 59; }
  if(s > 59) { s = 59; }

  if(s < 0 && m === 0)
  {
   overtime.timeMeasure = Overtime.TimeMeasure.MILLISECONDS;
   overtime.time = 1;
  }
  else if(s === 0 && m === 0)
  {
   overtime.timeMeasure = Overtime.TimeMeasure.SECONDS;
   overtime.time = 1;
  }
  else
  {
   overtime.timeMeasure = Overtime.TimeMeasure.MINUTES;
   overtime.time = m;
   overtime.timeMeasure = Overtime.TimeMeasure.SECONDS;
   overtime.prolongBy(s + 1);
  }

  overtime.rewind();
  displayTime(overtime);
 }
 catch(e) { /* Ignore invalid input. */ }
}

/**
 * Displays the time as digits.
 */

function displayTime(event)
{
 var ms = event.time, m, s;

 if(ms > 1) { ms += 1000; }

 m = ((ms / 60000) % 60) | 0;
 s = ((ms / 1000) % 60) | 0;
 minutes.value = (m < 10) ? "0" + m : m;
 seconds.value = (s < 10) ? "0" + s : s;
}

/**
 * Adds event listeners to the time controls number inputs.
 */

function enableTimeControls()
{
 minutes.disabled = false;
 seconds.disabled = false;
}

/**
 * Removes event listeners from the time controls number inputs.
 *
 * @return {boolean} Whether the time controls were editable before they were disabled.
 */

function disableTimeControls()
{
 var enabled = !minutes.disabled && !seconds.disabled;

 if(enabled)
 {
  minutes.disabled = true;
  seconds.disabled = true;
 }

 return enabled;
}

/**
 * Handle xhr responses.
 */

function handleResponse()
{
 var oldVoteCount = voteCount;

 if(this.readyState === 4)
 {
  try
  {
   voteCount = parseInt(this.responseText);
  }
  catch(e)
  {
   voteCount = Number.NaN;
  }

  if(typeof voteCount === "number" && !isNaN(voteCount))
  {
   if(voteCount !== oldVoteCount)
   {
    // Fancy update animation goes here.
    votes.innerHTML = voteCount;
    oldVoteCount = voteCount;
   }
  }
  else
  {
   clearInterval(intervalId);
  }
 }
}

/**
 * Request the current vote count.
 */

function requestVoteCount()
{
 xhr.open("GET", window.location.href.replace(new RegExp("evaluation/"), "evaluation/vote-count?uid="), true);
 xhr.timeout = timeout - 1000;
 xhr.send();
}

/**
 * The Setup.
 */

window.addEventListener("load", function init()
{
 overtime = new Overtime();

 container = document.getElementById("overtime");
 seconds = document.getElementById("seconds");
 minutes = document.getElementById("minutes");
 votes = document.getElementById("votes");

 // Make sure that all elements are present.
 if(container && minutes && seconds && votes)
 {
  // Adjust the size of the canvas and add it to the page.
  resize();
  window.addEventListener("resize", resize);
  container.appendChild(overtime.canvas);

  // Reset time to 30 minutes.
  overtime.timeMeasure = Overtime.TimeMeasure.MINUTES;
  overtime.time = 30;
  overtime.timeMeasure = Overtime.TimeMeasure.SECONDS;
  overtime.shortenBy(1);
  overtime.rewind();
  displayTime(overtime);

  // Update the time in the two input fields.
  overtime.addEventListener("update", displayTime);

  overtime.addEventListener("elapsed", function()
  {
   enableTimeControls();

   // Implement fancy finish effect here maybe.
   window.alert("Die Zeit ist um!");
  });

  document.getElementById("stop").addEventListener("click", function()
  {
   overtime.stop();
   enableTimeControls();
  });

  document.getElementById("start").addEventListener("click", function()
  {
   if(disableTimeControls())
   {
    copyTime();
   }

   overtime.start();
  });

  // Show the current time (persists over multiple sessions).
  displayTime(overtime);
  enableTimeControls();

  // Try to request the current vote count.
  if(XMLHttpRequest !== undefined)
  {
   xhr = new XMLHttpRequest();
   xhr.addEventListener("readystatechange", handleResponse);
   xhr.addEventListener("timeout", function() {});
   intervalId = setInterval(requestVoteCount, timeout);
  }
 }

 // Clean up.
 window.removeEventListener("load", init);
});

},{"overtime":2}]},{},[3]);
