text: /**
   * @classdesc This is an implementation of tooltip. The tooltip can render any HTML content
   * within and the arrow's direction can be configured. This module is capable of rearranging
   * the position to a best suited place based on preferred and allowed locations.
   *
   * @class Callout
   * @param {Object} options Valid options are "allowedLocations", "autohide", "container", "content",
   *                         "preferredLocations", "size", "theme". @see "Members" section for details
   *
   * @example
   *
   * var callout = new Callout({
   *     content: "I'm callout",
   *     container: ".container",
   *     theme: "light",
   *     size: {
   *         width: 150,
   *         height: 20
   *     }
   * });
   *
   * $(".targetNode").on("click", function () {
   *     callout.show(this, ["top"]); // show on top if possible
   * });
   *
   * $("body").on("contextmenu", function(event) {
   *     callout.show(event);
   *     event.preventDefault();
   * });
   *
   * @author rintoj (Rinto Jose)
   * @version 1.0
   *
   * @license The MIT License (MIT)
   *
   * Copyright (c) 2015 rintoj
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in all
   * copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   * SOFTWARE.
   */
  
  (function (factory, globalScope) {
  
      "use strict";
  
      if (typeof define === "function" && define.amd) {
          // AMD. Register as an anonymous module.
          define(["jquery"], factory);
      } else {
          // Browser globals
          globalScope.Callout = factory(globalScope.jQuery);
      }
  })(function ($) {
  
      "use strict";
  
      return function Callout(options) {
  
          /**
           * @member
           * @access private
           */
          var _domNode;
  
          /**
           * @member
           * @access private
           */
          var _arrowNode;
  
          /**
           * @member
           * @access private
           */
          var _contentNode;
  
          /**
           * @member
           * @access private
           */
          var _arrowHeight = 10;
  
          /**
           * @member
           * @access private
           */
          var _borderWidth = 1;
  
          /**
           * @member
           * @access private
           */
          var _config = {
  
              /**
               * Specify content for the callout within this member.
               *
               * @kind member
               * @type {String}
               * @instance
               * @memberof Callout
               */
              content: "",
  
              /**
               * Theme. Valid values are 'dark', 'light', 'default'. Default value is 'default'
               * @kind member
               * @type {String}
               * @instance
               * @memberof Callout
               */
              theme: "default",
  
              /**
               * Expect an object with 'width' and 'height'. Eg: {width: 25, height: 25}
               *
               * @kind member
               * @type {Object}
               * @instance
               * @memberof Callout
               */
              size: undefined,
  
              /**
               * If set to true callout will be made hidden when focus is lost; default value is true.
               *
               * @kind member
               * @type {Booean}
               * @instance
               * @memberof Callout
               */
              autohide: true,
  
              /**
               * If specified, callout will try to position itself within the specified dom node; default value is body
               *
               * @kind member
               * @type {String|Node}
               * @instance
               * @memberof Callout
               */
              container: "body",
  
              /**
               * The preferred locations of callout. This must be a subset of allowed location. Any other value will be
               * ignored.
               *
               * @kind member
               * @type {Array}
               * @instance
               * @memberof Callout
               */
              preferredLocations: [],
  
              /**
               * Locations that are allowed for the callout. Valid values are "top", "top-left", "top-right", "right"
               * "right-top", "right-bottom", "bottom", "bottom-right", "bottom-left", "left", "left-top", "left-bottom"
               *
               * @kind member
               * @type {Array}
               * @instance
               * @memberof Callout
               */
              allowedLocations: ["top",
                                 "top-left",
                                 "top-right",
                                 "right",
                                 "right-top",
                                 "right-bottom",
                                 "bottom",
                                 "bottom-right",
                                 "bottom-left",
                                 "left",
                                 "left-top",
                                 "left-bottom"]
          };
  
          /**
           * Init this instance. Called once at startup.
           *
           * @alias Callout.prototype.init
           * @private
           */
          var init = function () {
  
              _config = $.extend(true, _config, options);
  
              _domNode = $("<div>").addClass("callout animate").hide().attr("tabindex", -1);
              _arrowNode = $("<div>").addClass("arrow").appendTo(_domNode);
              _contentNode = $("<div>").html(_config.content).addClass("content").appendTo(_domNode);
  
              this.setTheme(_config.theme);
              this.setArrow(_config.arrow);
              if (_config.size) {
                  this.setSize(_config.size.width, _config.size.height);
              }
  
              var self = this;
              _domNode.on("focusout", function () {
                  if (_config.autohide) {
                      self.hide();
                  }
              });
          };
  
          /**
           * Get view port of the given node.
           *
           * @alias Callout.prototype.getViewPort
           * @private
           * @param   {Object} node Dom node
           * @returns {Object} View port object with attributes {left: Number, top: Number, width: Number, height: Number}
           */
          var getViewPort = function (node) {
              node = $(node);
              return {
                  left: node.offset().left - parseInt(node.css("marginLeft")),
                  top: node.offset().top - parseInt(node.css("marginTop")),
                  width: node.width() + parseInt(node.css("marginRight")) + parseInt(node.css("marginLeft")) +
                      parseInt(node.css("paddingRight")) + parseInt(node.css("paddingLeft")),
                  height: node.height() + parseInt(node.css("marginBottom")) + parseInt(node.css("marginTop")) +
                      parseInt(node.css("paddingBottom")) + parseInt(node.css("paddingTop"))
              };
          };
  
          /**
           * Check if value is presend in an array
           *
           * @alias Callout.prototype.contains
           * @private
           * @param   {Array}   array Array of values
           * @param   {Any}     value The value if searched in the array
           * @returns {Boolean} True if the value is found in array
           */
          var contains = function (array, value) {
              var valueContains = false;
              $.each(array, function (index, item) {
                  valueContains = valueContains || item === value;
              });
  
              return valueContains;
          };
  
          /**
           * Check if target is a valid view port object
           *
           * @alias Callout.prototype.isViewPort
           * @private
           * @param   {Object}  target An object
           * @returns {Boolean} True if target is a view port
           */
          var isViewPort = function (target) {
              return target && target.left && target.top && target.width && target.height;
          };
  
          /**
           * Get target locations. This merges preferredLocations with allowedLocations in the preferred order
           *
           * @alias Callout.prototype.getTargetLocations
           * @private
           * @param   {Array} preferredLocations Array of locations
           * @returns {Array} Array of target locations
           */
          var getTargetLocations = function (preferredLocations) {
              preferredLocations = (preferredLocations instanceof Array) ? preferredLocations :
                  _config.preferredLocations;
  
              var targetLocations = [];
              $.each(preferredLocations, function (index, location) {
                  if (contains(_config.allowedLocations, location)) {
                      targetLocations.push(location);
                  }
              });
  
              $.each(_config.allowedLocations, function (index, location) {
                  if (!contains(preferredLocations, location)) {
                      targetLocations.push(location);
                  }
              });
  
              return targetLocations;
          };
  
          /**
           * Rank a view port by comparing co-oridnates agaist container's view port
           *
           * @alias Callout.prototype.rankViewPort
           * @private
           * @param   {Object}  calculatedViewPort Calculated view port
           * @param   {Boolean} withinParent       If callout is with target node
           * @param   {Number}  index              The index of the view port (used for score)
           * @returns {Number}  A score for the view port
           */
          var rankViewPort = function (calculatedViewPort, withinParent, index) {
  
              var domViewPort = getViewPort(_domNode);
              var containerViewPort = getViewPort(_config.container);
  
              if (withinParent) {
                  var parentViewPort = getViewPort(_domNode.parent());
                  domViewPort = {
                      left: parentViewPort.left + calculatedViewPort.left,
                      top: parentViewPort.top + calculatedViewPort.top,
                      width: domViewPort.width,
                      height: domViewPort.height
                  };
              } else {
                  domViewPort = {
                      left: calculatedViewPort.left,
                      top: calculatedViewPort.top,
                      width: domViewPort.width,
                      height: domViewPort.height
                  };
              }
  
              var score = Math.min(domViewPort.left - containerViewPort.left,
                  domViewPort.top - containerViewPort.top,
                  containerViewPort.width - domViewPort.left - domViewPort.width,
                  containerViewPort.height - domViewPort.top - domViewPort.height);
  
              // maximum 12 allowed locations
              return score > 0 ? (12 - index) : score;
          };
  
          /**
           * Position dom node around the view port which was derived from an event
           *
           * @alias Callout.prototype.positionAroundViewPort
           * @private
           * @param   {Object}  viewPort           View port
           * @param   {Array}   preferredLocations Array of preferred locations
           * @returns {Callout} Returns 'this' to enable method chaining
           */
          var positionAroundViewPort = function (viewPort, preferredLocations) {
  
              var maxScore;
              var calculatedViewPort = {};
              var bestViewPort = {};
              var domViewPort = getViewPort(_domNode);
              var targetLocations = getTargetLocations(preferredLocations);
  
              $.each(targetLocations, function (index, location) {
  
                  if (location === "right") {
                      calculatedViewPort = {
                          left: viewPort.left + viewPort.width + _arrowHeight,
                          top: viewPort.top + (viewPort.height / 2) - (domViewPort.height / 2),
                          arrow: "left"
                      };
                  } else if (location === "right-top") {
                      calculatedViewPort = {
                          left: viewPort.left + viewPort.width + _arrowHeight,
                          top: viewPort.top,
                          arrow: "left-top"
                      };
                  } else if (location === "right-bottom") {
                      calculatedViewPort = {
                          left: viewPort.left + viewPort.width + _arrowHeight,
                          top: viewPort.top + viewPort.height - domViewPort.height,
                          arrow: "left-bottom"
                      };
                  } else if (location === "left") {
                      calculatedViewPort = {
                          left: viewPort.left - domViewPort.width - _arrowHeight,
                          top: viewPort.top + (viewPort.height / 2) - (domViewPort.height / 2),
                          arrow: "right"
                      };
                  } else if (location === "left-top") {
                      calculatedViewPort = {
                          left: viewPort.left - domViewPort.width - _arrowHeight,
                          top: viewPort.top,
                          arrow: "right-top"
                      };
                  } else if (location === "left-bottom") {
                      calculatedViewPort = {
                          left: viewPort.left - domViewPort.width - _arrowHeight,
                          top: viewPort.top + viewPort.height - domViewPort.height,
                          arrow: "right-bottom"
                      };
                  } else if (location === "top") {
                      calculatedViewPort = {
                          left: viewPort.left + (viewPort.width / 2) - (domViewPort.width / 2),
                          top: viewPort.top - domViewPort.height - _arrowHeight,
                          arrow: "bottom"
                      };
                  } else if (location === "top-left") {
                      calculatedViewPort = {
                          left: viewPort.left,
                          top: viewPort.top - domViewPort.height - _arrowHeight,
                          arrow: "bottom-left"
                      };
                  } else if (location === "top-right") {
                      calculatedViewPort = {
                          left: viewPort.left + viewPort.width - domViewPort.width,
                          top: viewPort.top - domViewPort.height - _arrowHeight,
                          arrow: "bottom-right"
                      };
                  } else if (location === "bottom") {
                      calculatedViewPort = {
                          left: viewPort.left + (viewPort.width / 2) - (domViewPort.width / 2),
                          top: viewPort.top + viewPort.height + _arrowHeight,
                          arrow: "top"
                      };
                  } else if (location === "bottom-left") {
                      calculatedViewPort = {
                          left: viewPort.left,
                          top: viewPort.top + viewPort.height + _arrowHeight,
                          arrow: "top-left"
                      };
                  } else if (location === "bottom-right") {
                      calculatedViewPort = {
                          left: viewPort.left + viewPort.width - domViewPort.width,
                          top: viewPort.top + viewPort.height + _arrowHeight,
                          arrow: "top-right"
                      };
                  }
  
                  // score view port and identify best view port based on score
                  var score = rankViewPort(calculatedViewPort, false, index);
                  if (maxScore === undefined || maxScore < score) {
                      maxScore = score;
                      bestViewPort = {
                          left: calculatedViewPort.left,
                          top: calculatedViewPort.top,
                          arrow: calculatedViewPort.arrow
                      };
                  }
              });
  
              if (bestViewPort) {
                  _domNode.css({
                      left: bestViewPort.left,
                      top: bestViewPort.top
                  });
                  this.setArrow(bestViewPort.arrow);
              }
              return this;
          };
  
          /**
           * Position aroud a node's view port
           *
           * @alias Callout.prototye.positionAroundNode
           * @private
           * @param   {Object}  viewPort           View port
           * @param   {Array}   preferredLocations Array of preferred locations
           * @returns {Callout} Returns 'this' to enable method chaining
           */
          var positionAroundNode = function (viewPort, preferredLocations) {
  
              var maxScore = 0;
              var bestViewPort;
              var calculatedViewPort;
              var domViewPort = getViewPort(_domNode);
              var targetLocations = getTargetLocations(preferredLocations);
  
              $.each(targetLocations, function (index, location) {
  
                  if (location === "right-top") {
                      calculatedViewPort = {
                          left: viewPort.width + _arrowHeight,
                          top: 0,
                          arrow: "left-top"
                      };
                  } else if (location === "right") {
                      calculatedViewPort = {
                          left: viewPort.width + _arrowHeight,
                          top: (viewPort.height / 2) - (domViewPort.height / 2),
                          arrow: "left"
                      };
                  } else if (location === "right-bottom") {
                      calculatedViewPort = {
                          left: viewPort.width + _arrowHeight,
                          top: viewPort.height - domViewPort.height,
                          arrow: "left-bottom"
                      };
                  } else if (location === "left-top") {
                      calculatedViewPort = {
                          left: -domViewPort.width - _arrowHeight,
                          top: 0,
                          arrow: "right-top"
                      };
                  } else if (location === "left") {
                      calculatedViewPort = {
                          left: -domViewPort.width - _arrowHeight,
                          top: (viewPort.height / 2) - (domViewPort.height / 2),
                          arrow: "right"
                      };
                  } else if (location === "left-bottom") {
                      calculatedViewPort = {
                          left: -domViewPort.width - _arrowHeight,
                          top: viewPort.height - domViewPort.height,
                          arrow: "right-bottom"
                      };
                  } else if (location === "top-left") {
                      calculatedViewPort = {
                          left: 0,
                          top: 0 - domViewPort.height - _arrowHeight,
                          arrow: "bottom-left"
                      };
                  } else if (location === "top") {
                      calculatedViewPort = {
                          left: (viewPort.width / 2) - (domViewPort.width / 2),
                          top: 0 - domViewPort.height - _arrowHeight,
                          arrow: "bottom"
                      };
                  } else if (location === "top-right") {
                      calculatedViewPort = {
                          left: viewPort.width - domViewPort.width,
                          top: 0 - domViewPort.height - _arrowHeight,
                          arrow: "bottom-right"
                      };
                  } else if (location === "bottom-left") {
                      calculatedViewPort = {
                          left: 0,
                          top: viewPort.height + _arrowHeight,
                          arrow: "top-left"
                      };
                  } else if (location === "bottom") {
                      calculatedViewPort = {
                          left: (viewPort.width / 2) - (domViewPort.width / 2),
                          top: viewPort.height + _arrowHeight,
                          arrow: "top"
                      };
                  } else if (location === "bottom-right") {
                      calculatedViewPort = {
                          left: viewPort.width - domViewPort.width,
                          top: viewPort.height + _arrowHeight,
                          arrow: "top-right"
                      };
                  }
  
                  // rank and calculate best score (maxScore) and viewPort (bestViewPort)
                  var score = rankViewPort(calculatedViewPort, true, index);
                  if (maxScore < score) {
                      maxScore = score;
                      bestViewPort = {
                          left: calculatedViewPort.left,
                          top: calculatedViewPort.top,
                          arrow: calculatedViewPort.arrow
                      };
                  }
              });
  
              if (bestViewPort) {
  
                  // if a suitable view port was found; set it
                  _domNode.css({
                      left: bestViewPort.left - _borderWidth,
                      top: bestViewPort.top - _borderWidth
                  });
  
                  // set arrow.
                  this.setArrow(bestViewPort.arrow);
              } else {
  
                  // if bestViewPort could not be calculated, place tooltip with the target node
                  // and at the right top corner
                  _domNode.css({
                      left: viewPort.width - domViewPort.width - (_arrowHeight / 2),
                      top: _arrowHeight - _borderWidth
                  });
  
                  // set arrow.
                  this.setArrow("top-right");
              }
  
              return this;
          };
  
          /**
           * Show callout and position it around a view port or a node. All the valid positions from the preferred
           * locations and the reset of the positions from the allowed positions will be iterated until a position is
           * found that places callout's viewport within the container's view port without overflowing. If no suitable
           * position the callout is place within the target node.
           *
           * @alias Callout.prototype.show
           * @param   {Node|jQuery.Event} target             (Mandatory) Dom node or an instance of jQuery.Event
           * @param   {Array}             preferredLocations (optional) Array of prefered locations;
           * @returns {Callout}           Returns 'this' to enable method chaining
           *                                                 @see 'allowedLocation' for valid values
           */
          this.show = function (target, preferredLocations) {
  
              if (!target) {
                  return null; // a node or event must be passed
              }
  
              // make dom node visible and detach
              if (_domNode.parent().length !== 0) {
                  try {
                      _domNode.detach();
                  } catch (e) {
                      // ignore; it's ok even if the _domNode was already removed
                  }
              }
  
              // show first, so that width, height calculations can be made
              _domNode.show();
  
              // this function will be attached to positionAroundViewPort or positionAroundNode
              // based on the type of target
              var repositionDomNode = function () {};
  
              // if jquery event do this
              if (target instanceof $.Event) {
                  // calculate the view port around the point where event happend
                  var eventPort = {
                      left: target.clientX,
                      top: target.clientY,
                      width: 0,
                      height: 0
                  };
  
                  // append to body
                  _domNode.prependTo(_config.container);
  
                  // calculate view port using positionAroundViewPort function
                  repositionDomNode = function () {
                      positionAroundViewPort.call(this, eventPort, preferredLocations);
                  };
              } else if (isViewPort(target)) {
                  // append to body
                  _domNode.prependTo(_config.container);
  
                  // calculate view port using positionAroundViewPort function
                  repositionDomNode = function () {
                      positionAroundViewPort.call(this, target, preferredLocations);
                  };
              } else {
                  // if a node or a selector text, do this
                  var viewPort = getViewPort($(target));
  
                  // attach to target node
                  _domNode.prependTo(target);
  
                  // calculate view port using positionAroundNode function
                  repositionDomNode = function () {
                      positionAroundNode.call(this, viewPort, preferredLocations);
                  };
              }
  
              var viewPortBefore, viewPortAfter, retryCount = 5;
  
              // retry if viewport's width and height are different before and after positioning (only 5 times at max)
              while (retryCount-- > 0 && (!viewPortBefore || !viewPortAfter ||
                      viewPortBefore.width !== viewPortAfter.width || viewPortBefore.height !== viewPortAfter.height)) {
  
                  // calculate view port before
                  viewPortBefore = getViewPort(_domNode);
  
                  // calculate new view port and reposition
                  repositionDomNode.call(this);
  
                  // calculate view port after
                  viewPortAfter = getViewPort(_domNode);
              }
  
              // show and focus
              _domNode.focus();
  
              return this;
          };
  
          /**
           * Hide callout.
           *
           * @alias Callout.prototype.hide
           * @returns {Callout} Returns 'this' to enable method chaining.
           */
          this.hide = function () {
              _domNode.hide().detach();
              return this;
          };
  
          /**
           * Remove callout from DOM
           *
           * @alias Callout.prototype.remove
           * @returns {Callout} Returns 'this' to enable method chaining
           */
          this.remove = function () {
              _domNode.remove();
              return this;
          };
  
          /**
           * Return the root node of callout. To avoid unexpected behaviour do NOT manipulate this node
           *
           * @alias Callout.prototype.getDomNode
           * @returns {Object} The node object
           */
          this.getDomNode = function () {
              return _domNode;
          };
  
          /**
           * Get content node. Attach your content into this node.
           *
           * @example
           * callout.getContentNode().html("This is a tooltip"); // setting text
           * callout.getContentNode().append(bannerNode); // setting inner node
           *
           * @alias Callout.prototype.getContentNode
           * @returns {Object} Content node
           */
          this.getContentNode = function () {
              return _contentNode;
          };
  
          /**
           * Set content of the callout. Any string/html content can be passed as a string.
           *
           * @alias Callout.prototype.setContent
           * @param   {String}  content The content as string
           * @returns {Callout} Returns 'this' to enable method chaining
           */
          this.setContent = function (content) {
              if (content) {
                  _config.content = content;
                  _contentNode.html(content);
              }
              return this;
          };
  
          /**
           * Get current direction of arrow.
           *
           * @alias Callout.prototype.getArrow
           * @returns {String} Returns direction of arrow
           */
          this.getArrow = function () {
              return _config.direction;
          };
  
          /**
           * Set direction of arrow. The arrow's direction will be overriden by show(), to place the callout in the
           * correct location.
           *
           * @alias Callout.prototype.setArrow
           * @param   {String} direction All possible values for 'allowedLocations' are valid here
           * @returns {Callout} Returns 'this' to enable method chaining
           */
          this.setArrow = function (direction) {
  
              // remove direction classes from arrow node
              _arrowNode.removeClass("left top right bottom align-left align-right align-top align-bottom");
  
              if (direction === "top-left") {
                  _arrowNode.addClass("top align-left");
              } else if (direction === "top") {
                  _arrowNode.addClass("top");
              } else if (direction === "top-right") {
                  _arrowNode.addClass("top align-right");
              } else if (direction === "bottom-left") {
                  _arrowNode.addClass("bottom align-left");
              } else if (direction === "bottom") {
                  _arrowNode.addClass("bottom");
              } else if (direction === "bottom-right") {
                  _arrowNode.addClass("bottom align-right");
              } else if (direction === "left-top") {
                  _arrowNode.addClass("left align-top");
              } else if (direction === "left") {
                  _arrowNode.addClass("left");
              } else if (direction === "left-bottom") {
                  _arrowNode.addClass("left align-bottom");
              } else if (direction === "right-top") {
                  _arrowNode.addClass("right align-top");
              } else if (direction === "right") {
                  _arrowNode.addClass("right");
              } else if (direction === "right-bottom") {
                  _arrowNode.addClass("right align-bottom");
              }
  
              _config.direction = direction;
              return this;
          };
  
          /**
           * Get container node. Callout will always try to place it self within the container node.
           *
           * @alias Callout.prototype.getContainer
           * @returns {Node|String} Returns 'container'
           */
          this.getContainer = function () {
              return _config.container;
          };
  
          /**
           * Container node for the callout. The view port of the callout will be
           * always calculated to be with in container's view port.
           *
           * @alias Callout.prototype.setContainer
           * @param   {Object}  container The container node
           * @returns {Callout} Returns 'this' to enable method chaining
           */
          this.setContainer = function (container) {
              if (container) {
                  _config.container = container;
              }
              return this;
          };
  
          /**
           * Check if auto hide is on or off
           *
           * @alias Callout.prototype.getAutoHide
           * @returns {Boolean} Returns 'true' if auto hide is on
           */
          this.getAutoHide = function () {
              return _config.autohide;
          };
  
          /**
           * Turn on/off auto hide. Auto hide feature will hide the callout
           * when focus is removed.
           *
           * @alias Callout.prototype.setAutoHide
           * @param   {Boolean} autohideOn True to turn on auto hide.
           * @returns {Callout} Returns 'this' to enable method chaining
           */
          this.setAutoHide = function (autohideOn) {
              _config.autohide = autohideOn;
              return this;
          };
  
          /**
           * Get preferred locations. @see setPreferredLocations() for details
           *
           * @alias Callout.prototype.getPreferredLocations
           * @returns {Array} Array of locations that are preferred
           */
          this.getPreferredLocations = function () {
              return _config.preferredLocations;
          };
  
          /**
           * Set preferred locations for the callout. Eg. ["right"] will place
           * callout on the right side of the attached node with left arrow.
           * However this location will be ignored if the calculated view port
           * is outside containerNode's view port.
           *
           * @alias Callout.prototype.setPreferredLocations
           * @param   {Array}   preferredLocations Array of preferred locations
           * @returns {Callout} Returns 'this' to enable method chaining
           * @see 'allowedLocation' for valid values
           */
          this.setPreferredLocations = function (preferredLocations) {
              if (preferredLocations instanceof Array) {
                  _config.preferredLocations = preferredLocations;
              }
              return this;
          };
  
          /**
           * Get the list of locations allowed for the callout.
           *
           * @alias Callout.prototype.getAllowedLocations
           * @returns {Array} Array of locations that are allowed
           */
          this.getAllowedLocations = function () {
              return _config.allowedLocations;
          };
  
          /**
           * Set the allowed locations for the callout. This is the final list of locations allowed for
           * callout. Any value outside this list, if used in preferred locations, will be ignored.
           *
           * @alias Callout.prototype.setAllowedLocations
           * @param   {Array}   allowedLocations Array of allowed locations
           * @returns {Callout} Returns 'this' to enable method chaining
           * @see 'allowedLocation' for valid values
           */
          this.setAllowedLocations = function (allowedLocations) {
              if (allowedLocations instanceof Array) {
                  _config.allowedLocations = allowedLocations;
              }
              return this;
          };
  
          /**
           * Get the current theme of the callout. Valid values are 'dark', 'light' and 'normal'
           *
           * @alias Callout.prototype.getTheme
           * @returns {String} Theme.
           */
          this.getTheme = function () {
              return _config.theme;
          };
  
          /**
           * Set theme. Valid values are 'dark', 'light', 'normal'
           *
           * @alias Callout.prototype.setTheme
           * @param   {String} theme Valid values are 'dark', 'light', 'normal'
           * @returns {Callout} Returns 'this' to enable method chaining
           */
          this.setTheme = function (theme) {
              _domNode.removeClass(_config.theme).addClass(theme);
              _config.theme = theme;
              return this;
          };
  
          /**
           * Get size of the callout. This function returns the defined size of the callout, but not the current size.
           *
           * @alias Callout.prototype.getSize
           * @returns {Object} An object in the format {width: Number, height: Number}.
           */
          this.getSize = function () {
              return _config.size;
          };
  
          /**
           * Set size of the callout
           *
           * @alias Callout.prototype.setSize
           * @param   {Number}  width  Width in pixels
           * @param   {Number}  height height in pixels
           * @returns {Callout} Returns 'this' to enable method chaining
           */
          this.setSize = function (width, height) {
              if (width || height) {
                  _config.size = {
                      width: width,
                      height: height
                  };
                  _domNode.css(_config.size);
              }
              return this;
          };
  
          // initialize
          init.call(this);
      };
  
  }, this);