(function() {

  var ua = function() {
    var o = {

    /**
     * Internet Explorer version number or 0.  Example: 6
     * @property ie
     * @type float
     */
      ie:0,

    /**
     * Opera version number or 0.  Example: 9.2
     * @property opera
     * @type float
     */
      opera:0,

    /**
     * Gecko engine revision number.  Will evaluate to 1 if Gecko
     * is detected but the revision could not be found. Other browsers
     * will be 0.  Example: 1.8
     * <pre>
     * Firefox 1.0.0.4: 1.7.8   <-- Reports 1.7
     * Firefox 1.5.0.9: 1.8.0.9 <-- Reports 1.8
     * Firefox 2.0.0.3: 1.8.1.3 <-- Reports 1.8
     * Firefox 3 alpha: 1.9a4   <-- Reports 1.9
     * </pre>
     * @property gecko
     * @type float
     */
      gecko:0,

    /**
     * AppleWebKit version.  KHTML browsers that are not WebKit browsers
     * will evaluate to 1, other browsers 0.  Example: 418.9.1
     * <pre>
     * Safari 1.3.2 (312.6): 312.8.1 <-- Reports 312.8 -- currently the
     *                                   latest available for Mac OSX 10.3.
     * Safari 2.0.2:         416     <-- hasOwnProperty introduced
     * Safari 2.0.4:         418     <-- preventDefault fixed
     * Safari 2.0.4 (419.3): 418.9.1 <-- One version of Safari may run
     *                                   different versions of webkit
     * Safari 2.0.4 (419.3): 419     <-- Tiger installations that have been
     *                                   updated, but not updated
     *                                   to the latest patch.
     * Webkit 212 nightly:   522+    <-- Safari 3.0 precursor (with native SVG
     *                                   and many major issues fixed).
     * 3.x yahoo.com, flickr:422     <-- Safari 3.x hacks the user agent
     *                                   string when hitting yahoo.com and
     *                                   flickr.com.
     * Safari 3.0.4 (523.12):523.12  <-- First Tiger release - automatic update
     *                                   from 2.x via the 10.4.11 OS patch
     * Webkit nightly 1/2008:525+    <-- Supports DOMContentLoaded event.
     *                                   yahoo.com user agent hack removed.
     *
     * </pre>
     * http://developer.apple.com/internet/safari/uamatrix.html
     * @property webkit
     * @type float
     */
      webkit: 0,

    /**
     * The mobile property will be set to a string containing any relevant
     * user agent information when a modern mobile browser is detected.
     * Currently limited to Safari on the iPhone/iPod Touch, Nokia N-series
     * devices with the WebKit-based browser, and Opera Mini.
     * @property mobile
     * @type string
     */
      mobile: null,

    /**
     * Adobe AIR version number or 0.  Only populated if webkit is detected.
     * Example: 1.0
     * @property air
     * @type float
     */
      air: 0

    };

    var ua = navigator.userAgent, m;

    // Modern KHTML browsers should qualify as Safari X-Grade
    if ((/KHTML/).test(ua)) {
      o.webkit = 1;
    }
    // Modern WebKit browsers are at least X-Grade
    m = ua.match(/AppleWebKit\/([^\s]*)/);
    if (m && m[1]) {
      o.webkit = parseFloat(m[1]);

      // Mobile browser check
      if (/ Mobile\//.test(ua)) {
        o.mobile = "Apple";
        // iPhone or iPod Touch
      }
      else {
        m = ua.match(/NokiaN[^\/]*/);
        if (m) {
          o.mobile = m[0];
          // Nokia N-series, ex: NokiaN95
        }
      }

      m = ua.match(/AdobeAIR\/([^\s]*)/);
      if (m) {
        o.air = m[0];
        // Adobe AIR 1.0 or better
      }

    }

    if (!o.webkit) { // not webkit
      // @todo check Opera/8.01 (J2ME/MIDP; Opera Mini/2.0.4509/1316; fi; U; ssr)
      m = ua.match(/Opera[\s\/]([^\s]*)/);
      if (m && m[1]) {
        o.opera = parseFloat(m[1]);
        m = ua.match(/Opera Mini[^;]*/);
        if (m) {
          o.mobile = m[0];
          // ex: Opera Mini/2.0.4509/1316
        }
      }
      else { // not opera or webkit
        m = ua.match(/MSIE\s([^;]*)/);
        if (m && m[1]) {
          o.ie = parseFloat(m[1]);
        }
        else { // not opera, webkit, or ie
          m = ua.match(/Gecko\/([^\s]*)/);
          if (m) {
            o.gecko = 1;
            // Gecko detected, look for revision
            m = ua.match(/rv:([^\s\)]*)/);
            if (m && m[1]) {
              o.gecko = parseFloat(m[1]);
            }
          }
        }
      }
    }

    return o;
  }();

  /**
   * @description <p>A wrapper for cross-browser text selection methods.</p>
   *              <p>Default usage is to disable text selection.</p>
   * @class DisableTextSelection
   *
   * Disables text selection by clearing any selection made in non-input elements. 
   * @constructor
   * @param {Boolean/Number} when The interval length for disabling text selection.
  */
  var DisableTextSelection = function(when) {
    if (when !== false) {
      this.disableSelection(when);
    }
  };

  DisableTextSelection.prototype = {

  /**
   * @method getSelection
   * @description Handles the different selection objects across the A-Grade list.
   * @return {Object} Selection Object
   */
    getSelection: (function() {

      if (ua.ie) {
        return function() {
          return document.selection;
        };
      }
      else {
        return function() {
          return window.getSelection();
        };
      }

    })(),

  /**
   * @method getRange
   * @description Handles the different range objects across the A-Grade list.
   * @return {Object} Range Object
   */
    getRange: (function() {

      // Used in gecko browsers, and more recent versions of webkit
      var rangeAt = function(sel) {
        if (sel.rangeCount > 0) {
          return sel.getRangeAt(0);
        }
        else {
          return null;
        }
      };

      if (ua.ie || ua.opera) {
        return function() {
          try {
            return this.getSelection().createRange();
          } catch(e) {
            return null;
          }
        };
      }
      else if (ua.webkit) {
        return function() {
          var sel = this.getSelection();

          // If we have the option to use getRangeAt, do so.
          if (sel.getRangeAt) {
            return rangeAt(sel);
          }
          // Otherwise, mimic the other browsers range support.
          else {
            var _range = document.createRange();
            try {
              _range.setStart(sel.anchorNode, sel.anchorOffset);
              _range.setEnd(sel.focusNode, sel.focusOffset);
            } catch (e) {
              _range = this.getSelection() + '';
            }
            return _range;
          }
        };
      }
      else if (ua.gecko) {
        return function() {
          return rangeAt(this.getSelection());
        };
      }

    })(),

  /**
   * @method clearSelection
   * @description Clears the current text selection across the A-Grade list.
   */
    clearSelection: (function() {
      if (ua.gecko || ua.opera) {
        return function() {
          window.getSelection().removeAllRanges();
        };
      }
      else if (ua.webkit) {
        return function() {
          var sel = window.getSelection();
          if (sel.removeAllRanges) {
            sel.removeAllRanges();
          }
          else {
            sel.collapse(true);
          }
        };
      }
      else if (ua.ie) {
        var re = /input|textarea/i;
        return function() {
          var sel = this.getSelection();
          try {
            var range = this.getRange();
            if (!range || !re.test(range.parentElement().nodeName)) {
              sel.empty();
            }
          }
          catch (e) {
            sel.empty();
          }
        };
      }

    })(),

  /**
   * @method isDisabledWithCSS
   * @description Determines if text-selection is disabled with CSS.
   */
    isDisabledWithCSS: function() {
      try {
        if (document.defaultView && document.defaultView.getComputedStyle) {
          var computed = document.defaultView.getComputedStyle(document.body, ''),
              userSelect = computed['MozUserSelect'] ||
                           computed['KhtmlUserSelect'] ||
                           computed['WebkitUserSelect'] ||
                           computed['userSelect'];
          return (userSelect.indexOf("none") != -1);
        }
        else {
          return false;
        }
      } catch(e) {
        return null;
      }
    },

  /**
   * @method disableSelection
   * @param when {int} The number of milliseconds for the interval.
   *                   Default interval length is 5 milliseconds.
   * @description Creates an interval that calls clearSelection continuously.
   */
    disableSelection: function(when) {
      when = when || 5;
      var oSelf = this;
      oSelf.enableSelection();
      oSelf._tid = setInterval(function() {
        try {
          oSelf.clearSelection();
        } catch(e) {}
      }, when);
      oSelf._tid2 = setInterval(function() {
        var isDisabled = oSelf.isDisabledWithCSS();
        if (isDisabled) {
          oSelf.enableSelection();
        }
        if (isDisabled != null) {
          clearInterval(oSelf._tid2);
        }
      }, when);
    },

  /**
   * @method enableSelection
   * @description Clears the interval created by disableSelection. 
   */
    enableSelection: function() {
      clearInterval(this._tid);
    }

  };

  // Start disabling text selection.
  new DisableTextSelection();

  // Disable the context menu as well
  document.oncontextmenu = function() {
    return false;
  }

})();