text: /**
   * Basic structure: TC_Class is the public class that is returned upon being called
   * 
   * So, if you do
   *      var tc = $(".timer").TimeCircles();
   *      
   * tc will contain an instance of the public TimeCircles class. It is important to
   * note that TimeCircles is not chained in the conventional way, check the
   * documentation for more info on how TimeCircles can be chained.
   * 
   * After being called/created, the public TimerCircles class will then- for each element
   * within it's collection, either fetch or create an instance of the private class.
   * Each function called upon the public class will be forwarded to each instance
   * of the private classes within the relevant element collection
   **/
  (function($) {
      /**
       * Converts hex color code into object containing integer values for the r,g,b use
       * This function (hexToRgb) originates from:
       * http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
       * @param {string} hex color code
       */
      function hexToRgb(hex) {
          // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
          var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
          hex = hex.replace(shorthandRegex, function(m, r, g, b) {
              return r + r + g + g + b + b;
          });
  
          var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
          return result ? {
              r: parseInt(result[1], 16),
              g: parseInt(result[2], 16),
              b: parseInt(result[3], 16)
          } : null;
      }
  
      /**
       * Function s4() and guid() originate from:
       * http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
       */
      function s4() {
          return Math.floor((1 + Math.random()) * 0x10000)
              .toString(16)
              .substring(1);
      }
  
      /**
       * Creates a unique id
       * @returns {String}
       */
      function guid() {
          return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
              s4() + '-' + s4() + s4() + s4();
      }
      
      var TC_Instance_List = {};
  
      var TC_Instance = function(element, options) {
          this.element = element;
          this.container;
          this.timer = null;
          this.data = {
              text_elements: {
                  Days: null,
                  Hours: null,
                  Minutes: null,
                  Seconds: null
              },
              attributes: {
                  canvas: null,
                  context: null,
                  item_size: null,
                  line_width: null,
                  radius: null,
                  outer_radius: null
              },
              state: {
                  fading: {
                      Days: false,
                      Hours: false,
                      Minutes: false,
                      Seconds: false
                  }
              }
          };
          this.listeners = [];
          this.config = null;
          this.setOptions(options);
          
          this.container = $("<div>");
          this.container.addClass('time_circles');
          this.container.appendTo(this.element);
  
          this.data.attributes.canvas = $("<canvas>");
          this.data.attributes.context = this.data.attributes.canvas[0].getContext('2d');
          
          var height = this.element.offsetHeight;
          var width = this.element.offsetWidth;
          if(height === 0 && width > 0) height = width / 4;
          else if(width === 0 && height > 0) width = height * 2;
          
          this.data.attributes.canvas[0].height = height;
          this.data.attributes.canvas[0].width = width;
          this.data.attributes.canvas.appendTo(this.container);
  
          this.data.attributes.item_size = Math.min(this.data.attributes.canvas[0].width / 4, this.data.attributes.canvas[0].height);
          this.data.attributes.line_width = this.data.attributes.item_size * this.config.fg_width;
          this.data.attributes.radius = ((this.data.attributes.item_size * 0.8) - this.data.attributes.line_width) / 2;
          this.data.attributes.outer_radius = this.data.attributes.radius + 0.5 * Math.max(this.data.attributes.line_width, this.data.attributes.line_width * this.config.bg_width);
  
          // Prepare Time Elements
          var i = 0;
          for (var key in this.data.text_elements) {
              var textElement = $("<div>");
              textElement.addClass('textDiv_' + key);
              textElement.css("top", Math.round(0.35 * this.data.attributes.item_size));
              textElement.css("left", Math.round(i++ * this.data.attributes.item_size));
              textElement.css("width", this.data.attributes.item_size);
              textElement.appendTo(this.container);
              
              var headerElement = $("<h4>");
              headerElement.text(this.config.time[key].text); // Options
              headerElement.css("font-size", Math.round(0.07 * this.data.attributes.item_size));
              headerElement.css("line-height", Math.round(0.07 * this.data.attributes.item_size) + "px");
              headerElement.appendTo(textElement);
              
              var numberElement = $("<span>");
              numberElement.css("font-size", Math.round(0.21 * this.data.attributes.item_size));
              numberElement.css("line-height", Math.round(0.07 * this.data.attributes.item_size) + "px");
              numberElement.appendTo(textElement);
              
              this.data.text_elements[key] = numberElement;
          }
  
          if (this.config.start)
              this.start();
      };
  
      TC_Instance.prototype.updateArc = function() {
          var diff, old_diff;
  
          var interval = (1000 * this.config.refresh_interval);
          var curDate = new Date();
  
          // Compare current time with reference
          if (this.config.count_past_zero) {
              var prevDate = curDate - interval;
              diff = Math.abs(curDate - this.data.attributes.ref_date) / 1000;
              old_diff = Math.abs(this.data.attributes.ref_date - prevDate) / 1000;
          }
          else {
              diff = Math.max(this.data.attributes.ref_date - curDate, 0) / 1000;
              old_diff = diff + (curDate > this.data.attributes.ref_date) ? 0 : interval;
          }
  
          var time = {
              Days: (diff / 60 / 60 / 24),
              Hours: (diff / 60 / 60) % 24,
              Minutes: (diff / 60) % 60,
              Seconds: diff % 60
          };
          var pct = {
              Days: time.Days / 365,
              Hours: time.Hours / 24,
              Minutes: time.Minutes / 60,
              Seconds: time.Seconds / 60
          };
  
          var old_time = {
              Days: (old_diff / 60 / 60 / 24),
              Hours: (old_diff / 60 / 60) % 24,
              Minutes: (old_diff / 60) % 60,
              Seconds: old_diff % 60
          };
  
          var i = 0;
          var lastKey = null;
          for (var key in time) {
              // Set the text value
              this.data.text_elements[key].text(Math.floor(time[key]));
  
              var x = (i * this.data.attributes.item_size) + (this.data.attributes.item_size / 2);
              var y = this.data.attributes.item_size / 2;
              var color = this.config.time[key].color;
  
              if(Math.floor(time[key]) !== Math.floor(old_time[key])) {
                  this.notifyListeners(key, Math.floor(time[key]), Math.floor(diff));
              }
              // TODO: Check options for fading == true
              if (lastKey !== null) {
                  if (Math.floor(time[lastKey]) > Math.floor(old_time[lastKey])) {
                      this.radialFade(x, y, color, 1, key);
                      this.data.state.fading[key] = true;
                  }
                  else if (Math.floor(time[lastKey]) < Math.floor(old_time[lastKey])) {
                      this.radialFade(x, y, color, 0, key);
                      this.data.state.fading[key] = true;
                  }
              }
              if (!this.data.state.fading[key]) {
                  this.drawArc(x, y, color, pct[key]);
              }
              lastKey = key;
              i++;
          }
      };
      
      TC_Instance.prototype.drawArc = function(x, y, color, pct) {
          var clear_radius = Math.max(this.data.attributes.outer_radius, this.data.attributes.item_size / 2);
          this.data.attributes.context.clearRect(
              x - clear_radius,
              y - clear_radius,
              clear_radius * 2,
              clear_radius * 2
              );
  
          if (this.config.use_background) {
              this.data.attributes.context.beginPath();
              this.data.attributes.context.arc(x, y, this.data.attributes.radius, 0, 2 * Math.PI, false);
              this.data.attributes.context.lineWidth = this.data.attributes.line_width * this.config.bg_width;
  
              // line color
              this.data.attributes.context.strokeStyle = this.config.circle_bg_color;
              this.data.attributes.context.stroke();
          }
  
          var startAngle = (-0.5 * Math.PI);
          var endAngle = (-0.5 * Math.PI) + (2 * pct * Math.PI);
          var counterClockwise = false;
  
          this.data.attributes.context.beginPath();
          this.data.attributes.context.arc(x, y, this.data.attributes.radius, startAngle, endAngle, counterClockwise);
          this.data.attributes.context.lineWidth = this.data.attributes.line_width;
  
          // line color
          this.data.attributes.context.strokeStyle = color;
          this.data.attributes.context.stroke();
      };
  
      TC_Instance.prototype.radialFade = function(x, y, color, from, key) {
          // TODO: Make fade_time option
          var rgb = hexToRgb(color);
          var _this = this; // We have a few inner scopes here that will need access to our instance
  
          var step = 0.2 * ((from === 1) ? -1 : 1);
          var i;
          for (i = 0; from <= 1 && from >= 0; i++) {
              // Create inner scope so our variables are not changed by the time the Timeout triggers
              (function() {
                  var rgba = "rgba(" + rgb.r + ", " + rgb.g + ", " + rgb.b + ", " + (Math.round(from * 10) / 10) + ")";
                  setTimeout(function() {
                      _this.drawArc(x, y, rgba, 1);
                  }, 50 * i);
              }());
              from += step;
          }
          setTimeout(function() {
              _this.data.state.fading[key] = false;
          }, 50 * i);
      };
  
      TC_Instance.prototype.timeLeft = function() {
          var now = new Date();
          return ((this.data.attributes.ref_date - now) / 1000);
      };
  
      TC_Instance.prototype.start = function() {
          // Check if a date was passed in html attribute, if not, fall back to config
          var attr_data_date = $(this.element).data('date');
          if (typeof attr_data_date === "string") {
              if (attr_data_date.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2}\s[0-9]{1,2}:[0-9]{2}:[0-9]{2}$/).length > 0) {
                  attr_data_date = attr_data_date.replace(' ', 'T');
              }
              this.data.attributes.ref_date = Date.parse(attr_data_date);
          }
          else {
              var attr_data_timer = $(this.element).attr('data-timer');
              if (typeof attr_data_timer === "string") {
                  this.data.attributes.timer = parseFloat(attr_data_timer);
                  $(this.element).removeAttr('data-timer');
              }
              else if (typeof this.config.timer === "string") {
                  this.data.attributes.timer = parseFloat(this.config.timer);
                  this.config.timer = null;
              }
              else if (typeof this.config.timer === "number") {
                  this.data.attributes.timer = _this.config.timer;
                  this.config.timer = null;
              }
  
              if (typeof this.data.attributes.timer === "number") {
                  this.data.attributes.ref_date = (new Date()).getTime() + (this.data.attributes.timer * 1000);
              }
              else {
                  this.data.attributes.ref_date = this.config.ref_date;
              }
          }
  
          // Start running
          var _this = this;
          this.timer = setInterval(function() { _this.updateArc(); }, this.config.refresh_interval * 1000);
      };
  
      TC_Instance.prototype.stop = function() {
          if (typeof this.data.attributes.timer === "number") {
              this.data.attributes.timer = this.timeLeft(this);
          }
          // Stop running
          clearInterval(this.timer);
      };
  
      TC_Instance.prototype.destroy = function() {
          this.stop();
          this.container.remove();
          $(this.element).removeData('tc-id');
      };
  
      TC_Instance.prototype.setOptions = function(options) {
          if(this.config === null) {
              this.default_options.ref_date = new Date();
              this.config = $.extend(true, {}, this.default_options);
          }
          $.extend(true, this.config, options);
      };
      
      TC_Instance.prototype.addListener = function(f) {
          if(typeof f !== "function") return;
          this.listeners.push(f);
      };
      
      TC_Instance.prototype.notifyListeners = function(unit, value, total) {
          for(var i = 0; i < this.listeners.length; i++) {
              this.listeners[i](unit, value, total);
          }
      }
      
      TC_Instance.prototype.default_options = {
          ref_date: new Date(),
          start: true,
          refresh_interval: 0.1,
          count_past_zero: true,
          circle_bg_color: "#60686F",
          use_background: true,
          fg_width: 0.1,
          bg_width: 1.2,
          time: {
              Days: {
                  show: true,
                  text: "Days",
                  color: "#FC6"
              },
              Hours: {
                  show: true,
                  text: "Hours",
                  color: "#9CF"
              },
              Minutes: {
                  show: true,
                  text: "Minutes",
                  color: "#BFB"
              },
              Seconds: {
                  show: true,
                  text: "Seconds",
                  color: "#F99"
              }
          }
      };
  
      // Time circle class
      var TC_Class = function(elements, options) {
          this.elements = elements;
          this.options = options;
          this.foreach();
      };
  
      TC_Class.prototype.foreach = function(callback) {
          var _this = this;
          this.elements.each(function() {
              var instance;
              var cur_id = $(this).data("tc-id");
              if (typeof cur_id === "undefined") {
                  cur_id = guid();
                  $(this).data("tc-id", cur_id);
              }
              if (typeof TC_Instance_List[cur_id] === "undefined") {
                  var element_options = $(this).data('options');
                  var options = _this.options;
                  if(typeof element_options === "object") {
                      options = $.extend(true, {}, _this.options, element_options);
                  }
                  instance = new TC_Instance(this, options);
                  TC_Instance_List[cur_id] = instance;
              }
              else {
                  instance = TC_Instance_List[cur_id];
                  if (typeof _this.options !== "undefined") {
                      instance.setOptions(_this.options);
                  }
              }
  
              if (typeof callback === "function") {
                  callback(instance);
              }
          });
          return this;
      };
  
      TC_Class.prototype.start = function() {
          this.foreach(function(instance) {
              instance.start();
          });
          return this;
      };
  
      TC_Class.prototype.stop = function() {
          this.foreach(function(instance) {
              instance.stop();
          });
          return this;
      };
  
      TC_Class.prototype.addListener = function(f) {
          this.foreach(function(instance) {
              instance.addListener(f);
          });
          return this;
      };
      
      TC_Class.prototype.destroy = function() {
          this.foreach(function(instance) {
              instance.destroy();
          });
          return this;
      };
  
      TC_Class.prototype.end = function() {
          return this.elements;
      };
  
      $.fn.TimeCircles = function(options) {
          return new TC_Class(this, options);
      };
  }(jQuery));