"use strict";
module.exports = URL;

function URL(url) {
  if (!url) return Object.create(URL.prototype);
  // Can't use String.trim() since it defines whitespace differently than HTML
  this.url = url.replace(/^[ \t\n\r\f]+|[ \t\n\r\f]+$/g, "");

  // See http://tools.ietf.org/html/rfc3986#appendix-B
  // and https://url.spec.whatwg.org/#parsing
  var match = URL.pattern.exec(this.url);
  if (match) {
    if (match[2]) this.scheme = match[2];
    if (match[4]) {
      // parse username/password
      var userinfo = match[4].match(URL.userinfoPattern);
      if (userinfo) {
        this.username = userinfo[1];
        this.password = userinfo[3];
        match[4] = match[4].substring(userinfo[0].length);
      }
      if (match[4].match(URL.portPattern)) {
        var pos = match[4].lastIndexOf(':');
        this.host = match[4].substring(0, pos);
        this.port = match[4].substring(pos+1);
      }
      else {
        this.host = match[4];
      }
    }
    if (match[5]) this.path = match[5];
    if (match[6]) this.query = match[7];
    if (match[8]) this.fragment = match[9];
  }
}

URL.pattern = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/;
URL.userinfoPattern = /^([^@:]*)(:([^@]*))?@/;
URL.portPattern = /:\d+$/;
URL.authorityPattern = /^[^:\/?#]+:\/\//;
URL.hierarchyPattern = /^[^:\/?#]+:\//;

// Return a percentEncoded version of s.
// S should be a single-character string
// XXX: needs to do utf-8 encoding?
URL.percentEncode = function percentEncode(s) {
  var c = s.charCodeAt(0);
  if (c < 256) return "%" + c.toString(16);
  else throw Error("can't percent-encode codepoints > 255 yet");
};

URL.prototype = {
  constructor: URL,

  // XXX: not sure if this is the precise definition of absolute
  isAbsolute: function() { return !!this.scheme; },
  isAuthorityBased: function() {
    return URL.authorityPattern.test(this.url);
  },
  isHierarchical: function() {
    return URL.hierarchyPattern.test(this.url);
  },

  toString: function() {
    var s = "";
    if (this.scheme !== undefined) s += this.scheme + ":";
    if (this.isAbsolute()) {
      s += '//';
      if (this.username || this.password) {
        s += this.username || '';
        if (this.password) {
          s += ':' + this.password;
        }
        s += '@';
      }
      if (this.host) {
        s += this.host;
      }
    }
    if (this.port !== undefined) s += ":" + this.port;
    if (this.path !== undefined) s += this.path;
    if (this.query !== undefined) s += "?" + this.query;
    if (this.fragment !== undefined) s += "#" + this.fragment;
    return s;
  },

  // See: http://tools.ietf.org/html/rfc3986#section-5.2
  // and https://url.spec.whatwg.org/#constructors
  resolve: function(relative) {
    var base = this;           // The base url we're resolving against
    var r = new URL(relative); // The relative reference url to resolve
    var t = new URL();         // The absolute target url we will return

    if (r.scheme !== undefined) {
      t.scheme = r.scheme;
      t.username = r.username;
      t.password = r.password;
      t.host = r.host;
      t.port = r.port;
      t.path = remove_dot_segments(r.path);
      t.query = r.query;
    }
    else {
      t.scheme = base.scheme;
      if (r.host !== undefined) {
        t.username = r.username;
        t.password = r.password;
        t.host = r.host;
        t.port = r.port;
        t.path = remove_dot_segments(r.path);
        t.query = r.query;
      }
      else {
        t.username = base.username;
        t.password = base.password;
        t.host = base.host;
        t.port = base.port;
        if (!r.path) { // undefined or empty
          t.path = base.path;
          if (r.query !== undefined)
            t.query = r.query;
          else
            t.query = base.query;
        }
        else {
          if (r.path.charAt(0) === "/") {
            t.path = remove_dot_segments(r.path);
          }
          else {
            t.path = merge(base.path, r.path);
            t.path = remove_dot_segments(t.path);
          }
          t.query = r.query;
        }
      }
    }
    t.fragment = r.fragment;

    return t.toString();


    function merge(basepath, refpath) {
      if (base.host !== undefined && !base.path)
        return "/" + refpath;

      var lastslash = basepath.lastIndexOf("/");
      if (lastslash === -1)
        return refpath;
      else
        return basepath.substring(0, lastslash+1) + refpath;
    }

    function remove_dot_segments(path) {
      if (!path) return path; // For "" or undefined

      var output = "";
      while(path.length > 0) {
        if (path === "." || path === "..") {
          path = "";
          break;
        }

        var twochars = path.substring(0,2);
        var threechars = path.substring(0,3);
        var fourchars = path.substring(0,4);
        if (threechars === "../") {
          path = path.substring(3);
        }
        else if (twochars === "./") {
          path = path.substring(2);
        }
        else if (threechars === "/./") {
          path = "/" + path.substring(3);
        }
        else if (twochars === "/." && path.length === 2) {
          path = "/";
        }
        else if (fourchars === "/../" ||
             (threechars === "/.." && path.length === 3)) {
          path = "/" + path.substring(4);

          output = output.replace(/\/?[^\/]*$/, "");
        }
        else {
          var segment = path.match(/(\/?([^\/]*))/)[0];
          output += segment;
          path = path.substring(segment.length);
        }
      }

      return output;
    }
  },
};
