/*!
 * URI.js - Mutating URLs
 * jQuery Plugin
 *
 * Version: 1.16.0
 *
 * Author: Rodney Rehm
 * Web: http://medialize.github.io/URI.js/jquery-uri-plugin.html
 *
 * Licensed under
 *   MIT License http://www.opensource.org/licenses/mit-license
 *   GPL v3 http://opensource.org/licenses/GPL-3.0
 *
 */
(function (root, factory) {
  'use strict';
  // https://github.com/umdjs/umd/blob/master/returnExports.js
  if (typeof exports === 'object') {
    // Node
    module.exports = factory(require('jquery', './URI'));
  } else if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(['jquery', './URI'], factory);
  } else {
    // Browser globals (root is window)
    factory(root.jQuery, root.URI);
  }
}(this, function ($, URI) {
  'use strict';
  // FIXME: v2.0.0 renamce non-camelCase properties to uppercase
  /*jshint camelcase: false */

  var comparable = {};
  var compare = {
    // equals 
    '=': function(value, target) {
      return value === target;
    },
    // ~= translates to value.match((?:^|\s)target(?:\s|$)) which is useless for URIs
    // |= translates to value.match((?:\b)target(?:-|\s|$)) which is useless for URIs
    // begins with
    '^=': function(value, target) {
      return !!(value + '').match(new RegExp('^' + escapeRegEx(target), 'i'));
    },
    // ends with
    '$=': function(value, target) {
      return !!(value + '').match(new RegExp(escapeRegEx(target) + '$', 'i'));
    },
    // contains
    '*=': function(value, target, property) {
      if (property === 'directory') {
        // add trailing slash so /dir/ will match the deep-end as well
        value += '/';
      }
    
      return !!(value + '').match(new RegExp(escapeRegEx(target), 'i'));
    },
    'equals:': function(uri, target) {
      return uri.equals(target);
    },
    'is:': function(uri, target) {
      return uri.is(target);
    }
  };

  function escapeRegEx(string) {
    // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963
    return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
  }

  function getUriProperty(elem) {
    var nodeName = elem.nodeName.toLowerCase();
    var property = URI.domAttributes[nodeName];
    if (nodeName === 'input' && elem.type !== 'image') {
      // compensate ambiguous <input> that is not an image
      return undefined;
    }
  
    // NOTE: as we use a static mapping from element to attribute,
    // the HTML5 attribute issue should not come up again
    // https://github.com/medialize/URI.js/issues/69
    return property;
  }

  function generateAccessor(property) {
    return {
      get: function(elem) {
        return $(elem).uri()[property]();
      },
      set: function(elem, value) {
        $(elem).uri()[property](value);
        return value;
      }
    };
  }

  // populate lookup table and register $.attr('uri:accessor') handlers
  $.each('authority directory domain filename fragment hash host hostname href password path pathname port protocol query resource scheme search subdomain suffix tld username'.split(' '), function(k, v) {
    comparable[v] = true;
    $.attrHooks['uri:' + v] = generateAccessor(v);
  });

  // pipe $.attr('src') and $.attr('href') through URI.js
  var _attrHooks = {
    get: function(elem) {
      return $(elem).uri();
    },
    set: function(elem, value) {
      return $(elem).uri().href(value).toString();
    }
  };
  $.each(['src', 'href', 'action', 'uri', 'cite'], function(k, v) {
    $.attrHooks[v] = {
      set: _attrHooks.set
    };
  });
  $.attrHooks.uri.get = _attrHooks.get;

  // general URI accessor
  $.fn.uri = function(uri) {
    var $this = this.first();
    var elem = $this.get(0);
    var property = getUriProperty(elem);
  
    if (!property) {
      throw new Error('Element "' + elem.nodeName + '" does not have either property: href, src, action, cite');
    }
  
    if (uri !== undefined) {
      var old = $this.data('uri');
      if (old) {
        return old.href(uri);
      }
    
      if (!(uri instanceof URI)) {
        uri = URI(uri || '');
      }
    } else {
      uri = $this.data('uri');
      if (uri) {
        return uri;
      } else {
        uri = URI($this.attr(property) || '');
      }
    }
  
    uri._dom_element = elem;
    uri._dom_attribute = property;
    uri.normalize();
    $this.data('uri', uri);
    return uri;
  };

  // overwrite URI.build() to update associated DOM element if necessary
  URI.prototype.build = function(deferBuild) {
    if (this._dom_element) {
      // cannot defer building when hooked into a DOM element
      this._string = URI.build(this._parts);
      this._deferred_build = false;
      this._dom_element.setAttribute(this._dom_attribute, this._string);
      this._dom_element[this._dom_attribute] = this._string;
    } else if (deferBuild === true) {
      this._deferred_build = true;
    } else if (deferBuild === undefined || this._deferred_build) {
      this._string = URI.build(this._parts);
      this._deferred_build = false;
    }
  
    return this;
  };

  // add :uri() pseudo class selector to sizzle
  var uriSizzle;
  var pseudoArgs = /^([a-zA-Z]+)\s*([\^\$*]?=|:)\s*(['"]?)(.+)\3|^\s*([a-zA-Z0-9]+)\s*$/;
  function uriPseudo (elem, text) {
    var match, property, uri;
  
    // skip anything without src|href|action and bad :uri() syntax
    if (!getUriProperty(elem) || !text) {
      return false;
    }
  
    match = text.match(pseudoArgs);

    if (!match || (!match[5] && match[2] !== ':' && !compare[match[2]])) {
      // abort because the given selector cannot be executed
      // filers seem to fail silently
      return false;
    }

    uri = $(elem).uri();
  
    if (match[5]) {
      return uri.is(match[5]);
    } else if (match[2] === ':') {
      property = match[1].toLowerCase() + ':';
      if (!compare[property]) {
        // filers seem to fail silently
        return false;
      }
    
      return compare[property](uri, match[4]);
    } else {
      property = match[1].toLowerCase();
      if (!comparable[property]) {
        // filers seem to fail silently
        return false;
      }
    
      return compare[match[2]](uri[property](), match[4], property);
    }

    return false;
  }

  if ($.expr.createPseudo) {
    // jQuery >= 1.8
    uriSizzle = $.expr.createPseudo(function (text) {
      return function (elem) {
        return uriPseudo(elem, text);
      };
    });
  } else {
    // jQuery < 1.8
    uriSizzle = function (elem, i, match) {
      return uriPseudo(elem, match[3]);
    };
  }

  $.expr[':'].uri = uriSizzle;

  // extending existing object rather than defining something new,
  // return jQuery anyway
  return $;
}));
