text: ((typeof define === "function" && define.amd && function (m) { define("samsam", m); }) ||
   (typeof module === "object" &&
        function (m) { module.exports = m(); }) || // Node
   function (m) { this.samsam = m(); } // Browser globals
  )(function () {
      var o = Object.prototype;
      var div = typeof document !== "undefined" && document.createElement("div");
  
      function isNaN(value) {
          // Unlike global isNaN, this avoids type coercion
          // typeof check avoids IE host object issues, hat tip to
          // lodash
          var val = value; // JsLint thinks value !== value is "weird"
          return typeof value === "number" && value !== val;
      }
  
      function getClass(value) {
          // Returns the internal [[Class]] by calling Object.prototype.toString
          // with the provided value as this. Return value is a string, naming the
          // internal class, e.g. "Array"
          return o.toString.call(value).split(/[ \]]/)[1];
      }
  
      /**
       * @name samsam.isArguments
       * @param Object object
       *
       * Returns ``true`` if ``object`` is an ``arguments`` object,
       * ``false`` otherwise.
       */
      function isArguments(object) {
          if (getClass(object) === 'Arguments') { return true; }
          if (typeof object !== "object" || typeof object.length !== "number" ||
                  getClass(object) === "Array") {
              return false;
          }
          if (typeof object.callee == "function") { return true; }
          try {
              object[object.length] = 6;
              delete object[object.length];
          } catch (e) {
              return true;
          }
          return false;
      }
  
      /**
       * @name samsam.isElement
       * @param Object object
       *
       * Returns ``true`` if ``object`` is a DOM element node. Unlike
       * Underscore.js/lodash, this function will return ``false`` if ``object``
       * is an *element-like* object, i.e. a regular object with a ``nodeType``
       * property that holds the value ``1``.
       */
      function isElement(object) {
          if (!object || object.nodeType !== 1 || !div) { return false; }
          try {
              object.appendChild(div);
              object.removeChild(div);
          } catch (e) {
              return false;
          }
          return true;
      }
  
      /**
       * @name samsam.keys
       * @param Object object
       *
       * Return an array of own property names.
       */
      function keys(object) {
          var ks = [], prop;
          for (prop in object) {
              if (o.hasOwnProperty.call(object, prop)) { ks.push(prop); }
          }
          return ks;
      }
  
      /**
       * @name samsam.isDate
       * @param Object value
       *
       * Returns true if the object is a ``Date``, or *date-like*. Duck typing
       * of date objects work by checking that the object has a ``getTime``
       * function whose return value equals the return value from the object's
       * ``valueOf``.
       */
      function isDate(value) {
          return typeof value.getTime == "function" &&
              value.getTime() == value.valueOf();
      }
  
      /**
       * @name samsam.isNegZero
       * @param Object value
       *
       * Returns ``true`` if ``value`` is ``-0``.
       */
      function isNegZero(value) {
          return value === 0 && 1 / value === -Infinity;
      }
  
      /**
       * @name samsam.equal
       * @param Object obj1
       * @param Object obj2
       *
       * Returns ``true`` if two objects are strictly equal. Compared to
       * ``===`` there are two exceptions:
       *
       *   - NaN is considered equal to NaN
       *   - -0 and +0 are not considered equal
       */
      function identical(obj1, obj2) {
          if (obj1 === obj2 || (isNaN(obj1) && isNaN(obj2))) {
              return obj1 !== 0 || isNegZero(obj1) === isNegZero(obj2);
          }
      }
  
  
      /**
       * @name samsam.deepEqual
       * @param Object obj1
       * @param Object obj2
       *
       * Deep equal comparison. Two values are "deep equal" if:
       *
       *   - They are equal, according to samsam.identical
       *   - They are both date objects representing the same time
       *   - They are both arrays containing elements that are all deepEqual
       *   - They are objects with the same set of properties, and each property
       *     in ``obj1`` is deepEqual to the corresponding property in ``obj2``
       *
       * Supports cyclic objects.
       */
      function deepEqualCyclic(obj1, obj2) {
  
          // used for cyclic comparison
          // contain already visited objects
          var objects1 = [],
              objects2 = [],
          // contain pathes (position in the object structure)
          // of the already visited objects
          // indexes same as in objects arrays
              paths1 = [],
              paths2 = [],
          // contains combinations of already compared objects
          // in the manner: { "$1['ref']$2['ref']": true }
              compared = {};
  
          /**
           * used to check, if the value of a property is an object
           * (cyclic logic is only needed for objects)
           * only needed for cyclic logic
           */
          function isObject(value) {
  
              if (typeof value === 'object' && value !== null &&
                      !(value instanceof Boolean) &&
                      !(value instanceof Date)    &&
                      !(value instanceof Number)  &&
                      !(value instanceof RegExp)  &&
                      !(value instanceof String)) {
  
                  return true;
              }
  
              return false;
          }
  
          /**
           * returns the index of the given object in the
           * given objects array, -1 if not contained
           * only needed for cyclic logic
           */
          function getIndex(objects, obj) {
  
              var i;
              for (i = 0; i < objects.length; i++) {
                  if (objects[i] === obj) {
                      return i;
                  }
              }
  
              return -1;
          }
  
          // does the recursion for the deep equal check
          return (function deepEqual(obj1, obj2, path1, path2) {
              var type1 = typeof obj1;
              var type2 = typeof obj2;
  
              // == null also matches undefined
              if (obj1 === obj2 ||
                      isNaN(obj1) || isNaN(obj2) ||
                      obj1 == null || obj2 == null ||
                      type1 !== "object" || type2 !== "object") {
  
                  return identical(obj1, obj2);
              }
  
              // Elements are only equal if identical(expected, actual)
              if (isElement(obj1) || isElement(obj2)) { return false; }
  
              var isDate1 = isDate(obj1), isDate2 = isDate(obj2);
              if (isDate1 || isDate2) {
                  if (!isDate1 || !isDate2 || obj1.getTime() !== obj2.getTime()) {
                      return false;
                  }
              }
  
              if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
                  if (obj1.toString() !== obj2.toString()) { return false; }
              }
  
              var class1 = getClass(obj1);
              var class2 = getClass(obj2);
              var keys1 = keys(obj1);
              var keys2 = keys(obj2);
  
              if (isArguments(obj1) || isArguments(obj2)) {
                  if (obj1.length !== obj2.length) { return false; }
              } else {
                  if (type1 !== type2 || class1 !== class2 ||
                          keys1.length !== keys2.length) {
                      return false;
                  }
              }
  
              var key, i, l,
                  // following vars are used for the cyclic logic
                  value1, value2,
                  isObject1, isObject2,
                  index1, index2,
                  newPath1, newPath2;
  
              for (i = 0, l = keys1.length; i < l; i++) {
                  key = keys1[i];
                  if (!o.hasOwnProperty.call(obj2, key)) {
                      return false;
                  }
  
                  // Start of the cyclic logic
  
                  value1 = obj1[key];
                  value2 = obj2[key];
  
                  isObject1 = isObject(value1);
                  isObject2 = isObject(value2);
  
                  // determine, if the objects were already visited
                  // (it's faster to check for isObject first, than to
                  // get -1 from getIndex for non objects)
                  index1 = isObject1 ? getIndex(objects1, value1) : -1;
                  index2 = isObject2 ? getIndex(objects2, value2) : -1;
  
                  // determine the new pathes of the objects
                  // - for non cyclic objects the current path will be extended
                  //   by current property name
                  // - for cyclic objects the stored path is taken
                  newPath1 = index1 !== -1
                      ? paths1[index1]
                      : path1 + '[' + JSON.stringify(key) + ']';
                  newPath2 = index2 !== -1
                      ? paths2[index2]
                      : path2 + '[' + JSON.stringify(key) + ']';
  
                  // stop recursion if current objects are already compared
                  if (compared[newPath1 + newPath2]) {
                      return true;
                  }
  
                  // remember the current objects and their pathes
                  if (index1 === -1 && isObject1) {
                      objects1.push(value1);
                      paths1.push(newPath1);
                  }
                  if (index2 === -1 && isObject2) {
                      objects2.push(value2);
                      paths2.push(newPath2);
                  }
  
                  // remember that the current objects are already compared
                  if (isObject1 && isObject2) {
                      compared[newPath1 + newPath2] = true;
                  }
  
                  // End of cyclic logic
  
                  // neither value1 nor value2 is a cycle
                  // continue with next level
                  if (!deepEqual(value1, value2, newPath1, newPath2)) {
                      return false;
                  }
              }
  
              return true;
  
          }(obj1, obj2, '$1', '$2'));
      }
  
      var match;
  
      function arrayContains(array, subset) {
          if (subset.length === 0) { return true; }
          var i, l, j, k;
          for (i = 0, l = array.length; i < l; ++i) {
              if (match(array[i], subset[0])) {
                  for (j = 0, k = subset.length; j < k; ++j) {
                      if (!match(array[i + j], subset[j])) { return false; }
                  }
                  return true;
              }
          }
          return false;
      }
  
      /**
       * @name samsam.match
       * @param Object object
       * @param Object matcher
       *
       * Compare arbitrary value ``object`` with matcher.
       */
      match = function match(object, matcher) {
          if (matcher && typeof matcher.test === "function") {
              return matcher.test(object);
          }
  
          if (typeof matcher === "function") {
              return matcher(object) === true;
          }
  
          if (typeof matcher === "string") {
              matcher = matcher.toLowerCase();
              var notNull = typeof object === "string" || !!object;
              return notNull &&
                  (String(object)).toLowerCase().indexOf(matcher) >= 0;
          }
  
          if (typeof matcher === "number") {
              return matcher === object;
          }
  
          if (typeof matcher === "boolean") {
              return matcher === object;
          }
  
          if (typeof(matcher) === "undefined") {
              return typeof(object) === "undefined";
          }
  
          if (matcher === null) {
              return object === null;
          }
  
          if (getClass(object) === "Array" && getClass(matcher) === "Array") {
              return arrayContains(object, matcher);
          }
  
          if (matcher && typeof matcher === "object") {
              if (matcher === object) {
                  return true;
              }
              var prop;
              for (prop in matcher) {
                  var value = object[prop];
                  if (typeof value === "undefined" &&
                          typeof object.getAttribute === "function") {
                      value = object.getAttribute(prop);
                  }
                  if (matcher[prop] === null || typeof matcher[prop] === 'undefined') {
                      if (value !== matcher[prop]) {
                          return false;
                      }
                  } else if (typeof  value === "undefined" || !match(value, matcher[prop])) {
                      return false;
                  }
              }
              return true;
          }
  
          throw new Error("Matcher was not a string, a number, a " +
                          "function, a boolean or an object");
      };
  
      return {
          isArguments: isArguments,
          isElement: isElement,
          isDate: isDate,
          isNegZero: isNegZero,
          identical: identical,
          deepEqual: deepEqualCyclic,
          match: match,
          keys: keys
      };
  });
  