text: // utility functions
  
  // first check if moment.js is already loaded in the browser window, if so,
  // use this instance. Else, load via commonjs.
  var moment = require('./module/moment');
  
  /**
   * Test whether given object is a number
   * @param {*} object
   * @return {Boolean} isNumber
   */
  exports.isNumber = function(object) {
    return (object instanceof Number || typeof object == 'number');
  };
  
  
  /**
   * this function gives you a range between 0 and 1 based on the min and max values in the set, the total sum of all values and the current value.
   *
   * @param min
   * @param max
   * @param total
   * @param value
   * @returns {number}
   */
  exports.giveRange = function(min,max,total,value) {
    if (max == min) {
      return 0.5;
    }
    else {
      var scale = 1 / (max - min);
      return Math.max(0,(value - min)*scale);
    }
  }
  
  /**
   * Test whether given object is a string
   * @param {*} object
   * @return {Boolean} isString
   */
  exports.isString = function(object) {
    return (object instanceof String || typeof object == 'string');
  };
  
  /**
   * Test whether given object is a Date, or a String containing a Date
   * @param {Date | String} object
   * @return {Boolean} isDate
   */
  exports.isDate = function(object) {
    if (object instanceof Date) {
      return true;
    }
    else if (exports.isString(object)) {
      // test whether this string contains a date
      var match = ASPDateRegex.exec(object);
      if (match) {
        return true;
      }
      else if (!isNaN(Date.parse(object))) {
        return true;
      }
    }
  
    return false;
  };
  
  /**
   * Test whether given object is an instance of google.visualization.DataTable
   * @param {*} object
   * @return {Boolean} isDataTable
   */
  exports.isDataTable = function(object) {
    return (typeof (google) !== 'undefined') &&
        (google.visualization) &&
        (google.visualization.DataTable) &&
        (object instanceof google.visualization.DataTable);
  };
  
  /**
   * Create a semi UUID
   * source: http://stackoverflow.com/a/105074/1262753
   * @return {String} uuid
   */
  exports.randomUUID = function() {
    var S4 = function () {
      return Math.floor(
          Math.random() * 0x10000 /* 65536 */
      ).toString(16);
    };
  
    return (
        S4() + S4() + '-' +
            S4() + '-' +
            S4() + '-' +
            S4() + '-' +
            S4() + S4() + S4()
        );
  };
  
  /**
   * Extend object a with the properties of object b or a series of objects
   * Only properties with defined values are copied
   * @param {Object} a
   * @param {... Object} b
   * @return {Object} a
   */
  exports.extend = function (a, b) {
    for (var i = 1, len = arguments.length; i < len; i++) {
      var other = arguments[i];
      for (var prop in other) {
        if (other.hasOwnProperty(prop)) {
          a[prop] = other[prop];
        }
      }
    }
  
    return a;
  };
  
  /**
   * Extend object a with selected properties of object b or a series of objects
   * Only properties with defined values are copied
   * @param {Array.<String>} props
   * @param {Object} a
   * @param {... Object} b
   * @return {Object} a
   */
  exports.selectiveExtend = function (props, a, b) {
    if (!Array.isArray(props)) {
      throw new Error('Array with property names expected as first argument');
    }
  
    for (var i = 2; i < arguments.length; i++) {
      var other = arguments[i];
  
      for (var p = 0; p < props.length; p++) {
        var prop = props[p];
        if (other.hasOwnProperty(prop)) {
          a[prop] = other[prop];
        }
      }
    }
    return a;
  };
  
  /**
   * Extend object a with selected properties of object b or a series of objects
   * Only properties with defined values are copied
   * @param {Array.<String>} props
   * @param {Object} a
   * @param {... Object} b
   * @return {Object} a
   */
  exports.selectiveDeepExtend = function (props, a, b) {
    // TODO: add support for Arrays to deepExtend
    if (Array.isArray(b)) {
      throw new TypeError('Arrays are not supported by deepExtend');
    }
    for (var i = 2; i < arguments.length; i++) {
      var other = arguments[i];
      for (var p = 0; p < props.length; p++) {
        var prop = props[p];
        if (other.hasOwnProperty(prop)) {
          if (b[prop] && b[prop].constructor === Object) {
            if (a[prop] === undefined) {
              a[prop] = {};
            }
            if (a[prop].constructor === Object) {
              exports.deepExtend(a[prop], b[prop]);
            }
            else {
              a[prop] = b[prop];
            }
          } else if (Array.isArray(b[prop])) {
            throw new TypeError('Arrays are not supported by deepExtend');
          } else {
            a[prop] = b[prop];
          }
  
        }
      }
    }
    return a;
  };
  
  /**
   * Extend object a with selected properties of object b or a series of objects
   * Only properties with defined values are copied
   * @param {Array.<String>} props
   * @param {Object} a
   * @param {... Object} b
   * @return {Object} a
   */
  exports.selectiveNotDeepExtend = function (props, a, b) {
    // TODO: add support for Arrays to deepExtend
    if (Array.isArray(b)) {
      throw new TypeError('Arrays are not supported by deepExtend');
    }
    for (var prop in b) {
      if (b.hasOwnProperty(prop)) {
        if (props.indexOf(prop) == -1) {
          if (b[prop] && b[prop].constructor === Object) {
            if (a[prop] === undefined) {
              a[prop] = {};
            }
            if (a[prop].constructor === Object) {
              exports.deepExtend(a[prop], b[prop]);
            }
            else {
              a[prop] = b[prop];
            }
          } else if (Array.isArray(b[prop])) {
            throw new TypeError('Arrays are not supported by deepExtend');
          } else {
            a[prop] = b[prop];
          }
        }
      }
    }
    return a;
  };
  
  /**
   * Deep extend an object a with the properties of object b
   * @param {Object} a
   * @param {Object} b
   * @returns {Object}
   */
  exports.deepExtend = function(a, b) {
    // TODO: add support for Arrays to deepExtend
    if (Array.isArray(b)) {
      throw new TypeError('Arrays are not supported by deepExtend');
    }
  
    for (var prop in b) {
      if (b.hasOwnProperty(prop)) {
        if (b[prop] && b[prop].constructor === Object) {
          if (a[prop] === undefined) {
            a[prop] = {};
          }
          if (a[prop].constructor === Object) {
            exports.deepExtend(a[prop], b[prop]);
          }
          else {
            a[prop] = b[prop];
          }
        } else if (Array.isArray(b[prop])) {
          throw new TypeError('Arrays are not supported by deepExtend');
        } else {
          a[prop] = b[prop];
        }
      }
    }
    return a;
  };
  
  /**
   * Test whether all elements in two arrays are equal.
   * @param {Array} a
   * @param {Array} b
   * @return {boolean} Returns true if both arrays have the same length and same
   *                   elements.
   */
  exports.equalArray = function (a, b) {
    if (a.length != b.length) return false;
  
    for (var i = 0, len = a.length; i < len; i++) {
      if (a[i] != b[i]) return false;
    }
  
    return true;
  };
  
  /**
   * Convert an object to another type
   * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
   * @param {String | undefined} type   Name of the type. Available types:
   *                                    'Boolean', 'Number', 'String',
   *                                    'Date', 'Moment', ISODate', 'ASPDate'.
   * @return {*} object
   * @throws Error
   */
  exports.convert = function(object, type) {
    var match;
  
    if (object === undefined) {
      return undefined;
    }
    if (object === null) {
      return null;
    }
  
    if (!type) {
      return object;
    }
    if (!(typeof type === 'string') && !(type instanceof String)) {
      throw new Error('Type must be a string');
    }
  
    //noinspection FallthroughInSwitchStatementJS
    switch (type) {
      case 'boolean':
      case 'Boolean':
        return Boolean(object);
  
      case 'number':
      case 'Number':
        return Number(object.valueOf());
  
      case 'string':
      case 'String':
        return String(object);
  
      case 'Date':
        if (exports.isNumber(object)) {
          return new Date(object);
        }
        if (object instanceof Date) {
          return new Date(object.valueOf());
        }
        else if (moment.isMoment(object)) {
          return new Date(object.valueOf());
        }
        if (exports.isString(object)) {
          match = ASPDateRegex.exec(object);
          if (match) {
            // object is an ASP date
            return new Date(Number(match[1])); // parse number
          }
          else {
            return moment(object).toDate(); // parse string
          }
        }
        else {
          throw new Error(
              'Cannot convert object of type ' + exports.getType(object) +
                  ' to type Date');
        }
  
      case 'Moment':
        if (exports.isNumber(object)) {
          return moment(object);
        }
        if (object instanceof Date) {
          return moment(object.valueOf());
        }
        else if (moment.isMoment(object)) {
          return moment(object);
        }
        if (exports.isString(object)) {
          match = ASPDateRegex.exec(object);
          if (match) {
            // object is an ASP date
            return moment(Number(match[1])); // parse number
          }
          else {
            return moment(object); // parse string
          }
        }
        else {
          throw new Error(
              'Cannot convert object of type ' + exports.getType(object) +
                  ' to type Date');
        }
  
      case 'ISODate':
        if (exports.isNumber(object)) {
          return new Date(object);
        }
        else if (object instanceof Date) {
          return object.toISOString();
        }
        else if (moment.isMoment(object)) {
          return object.toDate().toISOString();
        }
        else if (exports.isString(object)) {
          match = ASPDateRegex.exec(object);
          if (match) {
            // object is an ASP date
            return new Date(Number(match[1])).toISOString(); // parse number
          }
          else {
            return new Date(object).toISOString(); // parse string
          }
        }
        else {
          throw new Error(
              'Cannot convert object of type ' + exports.getType(object) +
                  ' to type ISODate');
        }
  
      case 'ASPDate':
        if (exports.isNumber(object)) {
          return '/Date(' + object + ')/';
        }
        else if (object instanceof Date) {
          return '/Date(' + object.valueOf() + ')/';
        }
        else if (exports.isString(object)) {
          match = ASPDateRegex.exec(object);
          var value;
          if (match) {
            // object is an ASP date
            value = new Date(Number(match[1])).valueOf(); // parse number
          }
          else {
            value = new Date(object).valueOf(); // parse string
          }
          return '/Date(' + value + ')/';
        }
        else {
          throw new Error(
              'Cannot convert object of type ' + exports.getType(object) +
                  ' to type ASPDate');
        }
  
      default:
        throw new Error('Unknown type "' + type + '"');
    }
  };
  
  // parse ASP.Net Date pattern,
  // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  // code from http://momentjs.com/
  var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  
  /**
   * Get the type of an object, for example exports.getType([]) returns 'Array'
   * @param {*} object
   * @return {String} type
   */
  exports.getType = function(object) {
    var type = typeof object;
  
    if (type == 'object') {
      if (object == null) {
        return 'null';
      }
      if (object instanceof Boolean) {
        return 'Boolean';
      }
      if (object instanceof Number) {
        return 'Number';
      }
      if (object instanceof String) {
        return 'String';
      }
      if (Array.isArray(object)) {
        return 'Array';
      }
      if (object instanceof Date) {
        return 'Date';
      }
      return 'Object';
    }
    else if (type == 'number') {
      return 'Number';
    }
    else if (type == 'boolean') {
      return 'Boolean';
    }
    else if (type == 'string') {
      return 'String';
    }
  
    return type;
  };
  
  /**
   * Retrieve the absolute left value of a DOM element
   * @param {Element} elem        A dom element, for example a div
   * @return {number} left        The absolute left position of this element
   *                              in the browser page.
   */
  exports.getAbsoluteLeft = function(elem) {
    return elem.getBoundingClientRect().left + window.pageXOffset;
  };
  
  /**
   * Retrieve the absolute top value of a DOM element
   * @param {Element} elem        A dom element, for example a div
   * @return {number} top        The absolute top position of this element
   *                              in the browser page.
   */
  exports.getAbsoluteTop = function(elem) {
    return elem.getBoundingClientRect().top + window.pageYOffset;
  };
  
  /**
   * add a className to the given elements style
   * @param {Element} elem
   * @param {String} className
   */
  exports.addClassName = function(elem, className) {
    var classes = elem.className.split(' ');
    if (classes.indexOf(className) == -1) {
      classes.push(className); // add the class to the array
      elem.className = classes.join(' ');
    }
  };
  
  /**
   * add a className to the given elements style
   * @param {Element} elem
   * @param {String} className
   */
  exports.removeClassName = function(elem, className) {
    var classes = elem.className.split(' ');
    var index = classes.indexOf(className);
    if (index != -1) {
      classes.splice(index, 1); // remove the class from the array
      elem.className = classes.join(' ');
    }
  };
  
  /**
   * For each method for both arrays and objects.
   * In case of an array, the built-in Array.forEach() is applied.
   * In case of an Object, the method loops over all properties of the object.
   * @param {Object | Array} object   An Object or Array
   * @param {function} callback       Callback method, called for each item in
   *                                  the object or array with three parameters:
   *                                  callback(value, index, object)
   */
  exports.forEach = function(object, callback) {
    var i,
        len;
    if (Array.isArray(object)) {
      // array
      for (i = 0, len = object.length; i < len; i++) {
        callback(object[i], i, object);
      }
    }
    else {
      // object
      for (i in object) {
        if (object.hasOwnProperty(i)) {
          callback(object[i], i, object);
        }
      }
    }
  };
  
  /**
   * Convert an object into an array: all objects properties are put into the
   * array. The resulting array is unordered.
   * @param {Object} object
   * @param {Array} array
   */
  exports.toArray = function(object) {
    var array = [];
  
    for (var prop in object) {
      if (object.hasOwnProperty(prop)) array.push(object[prop]);
    }
  
    return array;
  }
  
  /**
   * Update a property in an object
   * @param {Object} object
   * @param {String} key
   * @param {*} value
   * @return {Boolean} changed
   */
  exports.updateProperty = function(object, key, value) {
    if (object[key] !== value) {
      object[key] = value;
      return true;
    }
    else {
      return false;
    }
  };
  
  /**
   * Add and event listener. Works for all browsers
   * @param {Element}     element    An html element
   * @param {string}      action     The action, for example "click",
   *                                 without the prefix "on"
   * @param {function}    listener   The callback function to be executed
   * @param {boolean}     [useCapture]
   */
  exports.addEventListener = function(element, action, listener, useCapture) {
    if (element.addEventListener) {
      if (useCapture === undefined)
        useCapture = false;
  
      if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
        action = "DOMMouseScroll";  // For Firefox
      }
  
      element.addEventListener(action, listener, useCapture);
    } else {
      element.attachEvent("on" + action, listener);  // IE browsers
    }
  };
  
  /**
   * Remove an event listener from an element
   * @param {Element}     element         An html dom element
   * @param {string}      action          The name of the event, for example "mousedown"
   * @param {function}    listener        The listener function
   * @param {boolean}     [useCapture]
   */
  exports.removeEventListener = function(element, action, listener, useCapture) {
    if (element.removeEventListener) {
      // non-IE browsers
      if (useCapture === undefined)
        useCapture = false;
  
      if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
        action = "DOMMouseScroll";  // For Firefox
      }
  
      element.removeEventListener(action, listener, useCapture);
    } else {
      // IE browsers
      element.detachEvent("on" + action, listener);
    }
  };
  
  /**
   * Cancels the event if it is cancelable, without stopping further propagation of the event.
   */
  exports.preventDefault = function (event) {
    if (!event)
      event = window.event;
  
    if (event.preventDefault) {
      event.preventDefault();  // non-IE browsers
    }
    else {
      event.returnValue = false;  // IE browsers
    }
  };
  
  /**
   * Get HTML element which is the target of the event
   * @param {Event} event
   * @return {Element} target element
   */
  exports.getTarget = function(event) {
    // code from http://www.quirksmode.org/js/events_properties.html
    if (!event) {
      event = window.event;
    }
  
    var target;
  
    if (event.target) {
      target = event.target;
    }
    else if (event.srcElement) {
      target = event.srcElement;
    }
  
    if (target.nodeType != undefined && target.nodeType == 3) {
      // defeat Safari bug
      target = target.parentNode;
    }
  
    return target;
  };
  
  exports.option = {};
  
  /**
   * Convert a value into a boolean
   * @param {Boolean | function | undefined} value
   * @param {Boolean} [defaultValue]
   * @returns {Boolean} bool
   */
  exports.option.asBoolean = function (value, defaultValue) {
    if (typeof value == 'function') {
      value = value();
    }
  
    if (value != null) {
      return (value != false);
    }
  
    return defaultValue || null;
  };
  
  /**
   * Convert a value into a number
   * @param {Boolean | function | undefined} value
   * @param {Number} [defaultValue]
   * @returns {Number} number
   */
  exports.option.asNumber = function (value, defaultValue) {
    if (typeof value == 'function') {
      value = value();
    }
  
    if (value != null) {
      return Number(value) || defaultValue || null;
    }
  
    return defaultValue || null;
  };
  
  /**
   * Convert a value into a string
   * @param {String | function | undefined} value
   * @param {String} [defaultValue]
   * @returns {String} str
   */
  exports.option.asString = function (value, defaultValue) {
    if (typeof value == 'function') {
      value = value();
    }
  
    if (value != null) {
      return String(value);
    }
  
    return defaultValue || null;
  };
  
  /**
   * Convert a size or location into a string with pixels or a percentage
   * @param {String | Number | function | undefined} value
   * @param {String} [defaultValue]
   * @returns {String} size
   */
  exports.option.asSize = function (value, defaultValue) {
    if (typeof value == 'function') {
      value = value();
    }
  
    if (exports.isString(value)) {
      return value;
    }
    else if (exports.isNumber(value)) {
      return value + 'px';
    }
    else {
      return defaultValue || null;
    }
  };
  
  /**
   * Convert a value into a DOM element
   * @param {HTMLElement | function | undefined} value
   * @param {HTMLElement} [defaultValue]
   * @returns {HTMLElement | null} dom
   */
  exports.option.asElement = function (value, defaultValue) {
    if (typeof value == 'function') {
      value = value();
    }
  
    return value || defaultValue || null;
  };
  
  /**
   * http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
   *
   * @param {String} hex
   * @returns {{r: *, g: *, b: *}} | 255 range
   */
  exports.hexToRGB = function(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;
  };
  
  /**
   * This function takes color in hex format or rgb() or rgba() format and overrides the opacity. Returns rgba() string.
   * @param color
   * @param opacity
   * @returns {*}
   */
  exports.overrideOpacity = function(color,opacity) {
    if (color.indexOf("rgb") != -1) {
      var rgb = color.substr(color.indexOf("(")+1).replace(")","").split(",");
      return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + opacity + ")"
    }
    else {
      var rgb = exports.hexToRGB(color);
      if (rgb == null) {
        return color;
      }
      else {
        return "rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + "," + opacity + ")"
      }
    }
  }
  
  /**
   *
   * @param red     0 -- 255
   * @param green   0 -- 255
   * @param blue    0 -- 255
   * @returns {string}
   * @constructor
   */
  exports.RGBToHex = function(red,green,blue) {
    return "#" + ((1 << 24) + (red << 16) + (green << 8) + blue).toString(16).slice(1);
  };
  
  /**
   * Parse a color property into an object with border, background, and
   * highlight colors
   * @param {Object | String} color
   * @return {Object} colorObject
   */
  exports.parseColor = function(color) {
    var c;
    if (exports.isString(color)) {
      if (exports.isValidRGB(color)) {
        var rgb = color.substr(4).substr(0,color.length-5).split(',');
        color = exports.RGBToHex(rgb[0],rgb[1],rgb[2]);
      }
      if (exports.isValidHex(color)) {
        var hsv = exports.hexToHSV(color);
        var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)};
        var darkerColorHSV  = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6};
        var darkerColorHex  = exports.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v);
        var lighterColorHex = exports.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v);
  
        c = {
          background: color,
          border:darkerColorHex,
          highlight: {
            background:lighterColorHex,
            border:darkerColorHex
          },
          hover: {
            background:lighterColorHex,
            border:darkerColorHex
          }
        };
      }
      else {
        c = {
          background:color,
          border:color,
          highlight: {
            background:color,
            border:color
          },
          hover: {
            background:color,
            border:color
          }
        };
      }
    }
    else {
      c = {};
      c.background = color.background || 'white';
      c.border = color.border || c.background;
  
      if (exports.isString(color.highlight)) {
        c.highlight = {
          border: color.highlight,
          background: color.highlight
        }
      }
      else {
        c.highlight = {};
        c.highlight.background = color.highlight && color.highlight.background || c.background;
        c.highlight.border = color.highlight && color.highlight.border || c.border;
      }
  
      if (exports.isString(color.hover)) {
        c.hover = {
          border: color.hover,
          background: color.hover
        }
      }
      else {
        c.hover = {};
        c.hover.background = color.hover && color.hover.background || c.background;
        c.hover.border = color.hover && color.hover.border || c.border;
      }
    }
  
    return c;
  };
  
  /**
   * http://www.javascripter.net/faq/rgb2hsv.htm
   *
   * @param red
   * @param green
   * @param blue
   * @returns {*}
   * @constructor
   */
  exports.RGBToHSV = function(red,green,blue) {
    red=red/255; green=green/255; blue=blue/255;
    var minRGB = Math.min(red,Math.min(green,blue));
    var maxRGB = Math.max(red,Math.max(green,blue));
  
    // Black-gray-white
    if (minRGB == maxRGB) {
      return {h:0,s:0,v:minRGB};
    }
  
    // Colors other than black-gray-white:
    var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
    var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
    var hue = 60*(h - d/(maxRGB - minRGB))/360;
    var saturation = (maxRGB - minRGB)/maxRGB;
    var value = maxRGB;
    return {h:hue,s:saturation,v:value};
  };
  
  var cssUtil = {
    // split a string with css styles into an object with key/values
    split: function (cssText) {
      var styles = {};
  
      cssText.split(';').forEach(function (style) {
        if (style.trim() != '') {
          var parts = style.split(':');
          var key = parts[0].trim();
          var value = parts[1].trim();
          styles[key] = value;
        }
      });
  
      return styles;
    },
  
    // build a css text string from an object with key/values
    join: function (styles) {
      return Object.keys(styles)
          .map(function (key) {
            return key + ': ' + styles[key];
          })
          .join('; ');
    }
  };
  
  /**
   * Append a string with css styles to an element
   * @param {Element} element
   * @param {String} cssText
   */
  exports.addCssText = function (element, cssText) {
    var currentStyles = cssUtil.split(element.style.cssText);
    var newStyles = cssUtil.split(cssText);
    var styles = exports.extend(currentStyles, newStyles);
  
    element.style.cssText = cssUtil.join(styles);
  };
  
  /**
   * Remove a string with css styles from an element
   * @param {Element} element
   * @param {String} cssText
   */
  exports.removeCssText = function (element, cssText) {
    var styles = cssUtil.split(element.style.cssText);
    var removeStyles = cssUtil.split(cssText);
  
    for (var key in removeStyles) {
      if (removeStyles.hasOwnProperty(key)) {
        delete styles[key];
      }
    }
  
    element.style.cssText = cssUtil.join(styles);
  };
  
  /**
   * https://gist.github.com/mjijackson/5311256
   * @param h
   * @param s
   * @param v
   * @returns {{r: number, g: number, b: number}}
   * @constructor
   */
  exports.HSVToRGB = function(h, s, v) {
    var r, g, b;
  
    var i = Math.floor(h * 6);
    var f = h * 6 - i;
    var p = v * (1 - s);
    var q = v * (1 - f * s);
    var t = v * (1 - (1 - f) * s);
  
    switch (i % 6) {
      case 0: r = v, g = t, b = p; break;
      case 1: r = q, g = v, b = p; break;
      case 2: r = p, g = v, b = t; break;
      case 3: r = p, g = q, b = v; break;
      case 4: r = t, g = p, b = v; break;
      case 5: r = v, g = p, b = q; break;
    }
  
    return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
  };
  
  exports.HSVToHex = function(h, s, v) {
    var rgb = exports.HSVToRGB(h, s, v);
    return exports.RGBToHex(rgb.r, rgb.g, rgb.b);
  };
  
  exports.hexToHSV = function(hex) {
    var rgb = exports.hexToRGB(hex);
    return exports.RGBToHSV(rgb.r, rgb.g, rgb.b);
  };
  
  exports.isValidHex = function(hex) {
    var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
    return isOk;
  };
  
  exports.isValidRGB = function(rgb) {
    rgb = rgb.replace(" ","");
    var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb);
    return isOk;
  }
  
  /**
   * This recursively redirects the prototype of JSON objects to the referenceObject
   * This is used for default options.
   *
   * @param referenceObject
   * @returns {*}
   */
  exports.selectiveBridgeObject = function(fields, referenceObject) {
    if (typeof referenceObject == "object") {
      var objectTo = Object.create(referenceObject);
      for (var i = 0; i < fields.length; i++) {
        if (referenceObject.hasOwnProperty(fields[i])) {
          if (typeof referenceObject[fields[i]] == "object") {
            objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]);
          }
        }
      }
      return objectTo;
    }
    else {
      return null;
    }
  };
  
  /**
   * This recursively redirects the prototype of JSON objects to the referenceObject
   * This is used for default options.
   *
   * @param referenceObject
   * @returns {*}
   */
  exports.bridgeObject = function(referenceObject) {
    if (typeof referenceObject == "object") {
      var objectTo = Object.create(referenceObject);
      for (var i in referenceObject) {
        if (referenceObject.hasOwnProperty(i)) {
          if (typeof referenceObject[i] == "object") {
            objectTo[i] = exports.bridgeObject(referenceObject[i]);
          }
        }
      }
      return objectTo;
    }
    else {
      return null;
    }
  };
  
  
  /**
   * this is used to set the options of subobjects in the options object. A requirement of these subobjects
   * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
   *
   * @param [object] mergeTarget | this is either this.options or the options used for the groups.
   * @param [object] options     | options
   * @param [String] option      | this is the option key in the options argument
   * @private
   */
  exports.mergeOptions = function (mergeTarget, options, option) {
    if (options[option] !== undefined) {
      if (typeof options[option] == 'boolean') {
        mergeTarget[option].enabled = options[option];
      }
      else {
        mergeTarget[option].enabled = true;
        for (var prop in options[option]) {
          if (options[option].hasOwnProperty(prop)) {
            mergeTarget[option][prop] = options[option][prop];
          }
        }
      }
    }
  }
  
  
  /**
   * This function does a binary search for a visible item in a sorted list. If we find a visible item, the code that uses
   * this function will then iterate in both directions over this sorted list to find all visible items.
   *
   * @param {Item[]} orderedItems       | Items ordered by start
   * @param {function} searchFunction   | -1 is lower, 0 is found, 1 is higher
   * @param {String} field
   * @param {String} field2
   * @returns {number}
   * @private
   */
  exports.binarySearchCustom = function(orderedItems, searchFunction, field, field2) {
    var maxIterations = 10000;
    var iteration = 0;
    var low = 0;
    var high = orderedItems.length - 1;
  
    while (low <= high && iteration < maxIterations) {
      var middle = Math.floor((low + high) / 2);
  
      var item = orderedItems[middle];
      var value = (field2 === undefined) ? item[field] : item[field][field2];
  
      var searchResult = searchFunction(value);
      if (searchResult == 0) { // jihaa, found a visible item!
        return middle;
      }
      else if (searchResult == -1) {  // it is too small --> increase low
        low = middle + 1;
      }
      else {  // it is too big --> decrease high
        high = middle - 1;
      }
  
      iteration++;
    }
  
    return -1;
  };
  
  /**
   * This function does a binary search for a specific value in a sorted array. If it does not exist but is in between of
   * two values, we return either the one before or the one after, depending on user input
   * If it is found, we return the index, else -1.
   *
   * @param {Array} orderedItems
   * @param {{start: number, end: number}} target
   * @param {String} field
   * @param {String} sidePreference   'before' or 'after'
   * @returns {number}
   * @private
   */
  exports.binarySearchValue = function(orderedItems, target, field, sidePreference) {
    var maxIterations = 10000;
    var iteration = 0;
    var low = 0;
    var high = orderedItems.length - 1;
    var prevValue, value, nextValue, middle;
  
    while (low <= high && iteration < maxIterations) {
      // get a new guess
      middle = Math.floor(0.5*(high+low));
      prevValue = orderedItems[Math.max(0,middle - 1)][field];
      value     = orderedItems[middle][field];
      nextValue = orderedItems[Math.min(orderedItems.length-1,middle + 1)][field];
  
      if (value == target) { // we found the target
        return middle;
      }
      else if (prevValue < target && value > target) {  // target is in between of the previous and the current
        return sidePreference == 'before' ? Math.max(0,middle - 1) : middle;
      }
      else if (value < target && nextValue > target) { // target is in between of the current and the next
        return sidePreference == 'before' ? middle : Math.min(orderedItems.length-1,middle + 1);
      }
      else {  // didnt find the target, we need to change our boundaries.
        if (value < target) { // it is too small --> increase low
          low = middle + 1;
        }
        else {  // it is too big --> decrease high
          high = middle - 1;
        }
      }
      iteration++;
    }
  
    // didnt find anything. Return -1.
    return -1;
  };
  
  /**
   * Quadratic ease-in-out
   * http://gizma.com/easing/
   * @param {number} t        Current time
   * @param {number} start    Start value
   * @param {number} end      End value
   * @param {number} duration Duration
   * @returns {number} Value corresponding with current time
   */
  exports.easeInOutQuad = function (t, start, end, duration) {
    var change = end - start;
    t /= duration/2;
    if (t < 1) return change/2*t*t + start;
    t--;
    return -change/2 * (t*(t-2) - 1) + start;
  };
  
  
  
  /*
   * Easing Functions - inspired from http://gizma.com/easing/
   * only considering the t value for the range [0, 1] => [0, 1]
   * https://gist.github.com/gre/1650294
   */
  exports.easingFunctions = {
    // no easing, no acceleration
    linear: function (t) {
      return t
    },
    // accelerating from zero velocity
    easeInQuad: function (t) {
      return t * t
    },
    // decelerating to zero velocity
    easeOutQuad: function (t) {
      return t * (2 - t)
    },
    // acceleration until halfway, then deceleration
    easeInOutQuad: function (t) {
      return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
    },
    // accelerating from zero velocity
    easeInCubic: function (t) {
      return t * t * t
    },
    // decelerating to zero velocity
    easeOutCubic: function (t) {
      return (--t) * t * t + 1
    },
    // acceleration until halfway, then deceleration
    easeInOutCubic: function (t) {
      return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
    },
    // accelerating from zero velocity
    easeInQuart: function (t) {
      return t * t * t * t
    },
    // decelerating to zero velocity
    easeOutQuart: function (t) {
      return 1 - (--t) * t * t * t
    },
    // acceleration until halfway, then deceleration
    easeInOutQuart: function (t) {
      return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
    },
    // accelerating from zero velocity
    easeInQuint: function (t) {
      return t * t * t * t * t
    },
    // decelerating to zero velocity
    easeOutQuint: function (t) {
      return 1 + (--t) * t * t * t * t
    },
    // acceleration until halfway, then deceleration
    easeInOutQuint: function (t) {
      return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
    }
  };