// NOTE: This method only strips the fragment and is not in accordance to the
// recommended draft specification:
// https://w3c.github.io/webappsec/specs/referrer-policy/#null
// TODO(kristijanburnik): Implement this helper as defined by spec once added
// scenarios for URLs containing username/password/etc.
function stripUrlForUseAsReferrer(url) {
  return url.replace(/#.*$/, "");
}

function parseUrlQueryString(queryString) {
  var queries = queryString.replace(/^\?/, "").split("&");
  var params = {};

  for (var i in queries) {
    var kvp = queries[i].split("=");
    params[kvp[0]] = kvp[1];
  }

  return params;
};

function appendIframeToBody(url, attributes) {
  var iframe = document.createElement("iframe");
  iframe.src = url;
  // Extend element with attributes. (E.g. "referrerPolicy" or "rel")
  if (attributes) {
    for (var attr in attributes) {
      iframe[attr] = attributes[attr];
    }
  }
  document.body.appendChild(iframe);

  return iframe;
}

function loadImage(src, callback, attributes) {
  var image = new Image();
  image.crossOrigin = "Anonymous";
  image.onload = function() {
    callback(image);
  }
  image.src = src;
  // Extend element with attributes. (E.g. "referrerPolicy" or "rel")
  if (attributes) {
    for (var attr in attributes) {
      image[attr] = attributes[attr];
    }
  }
  document.body.appendChild(image)
}

function decodeImageData(rgba) {
  var rgb = new Uint8ClampedArray(rgba.length);

  // RGBA -> RGB.
  var rgb_length = 0;
  for (var i = 0; i < rgba.length; ++i) {
    // Skip alpha component.
    if (i % 4 == 3)
      continue;

    // Zero is the string terminator.
    if (rgba[i] == 0)
      break;

    rgb[rgb_length++] = rgba[i];
  }

  // Remove trailing nulls from data.
  rgb = rgb.subarray(0, rgb_length);
  var string_data = (new TextDecoder("ascii")).decode(rgb);

  return JSON.parse(string_data);
}

function decodeImage(url, callback, referrer_policy) {
  loadImage(url, function(img) {
    var canvas = document.createElement("canvas");
    var context = canvas.getContext('2d');
    context.drawImage(img, 0, 0);
    var imgData = context.getImageData(0, 0, img.clientWidth, img.clientHeight);
    callback(decodeImageData(imgData.data))
  }, referrer_policy);
}

function normalizePort(targetPort) {
  var defaultPorts = [80, 443];
  var isDefaultPortForProtocol = (defaultPorts.indexOf(targetPort) >= 0);

  return (targetPort == "" || isDefaultPortForProtocol) ?
          "" : ":" + targetPort;
}

function wrapResult(url, server_data) {
  return {
    location: url,
    referrer: server_data.headers.referer,
    headers: server_data.headers
  }
}

function queryIframe(url, callback, referrer_policy) {
  var iframe = appendIframeToBody(url, referrer_policy);
  var listener = function(event) {
    if (event.source != iframe.contentWindow)
      return;

    callback(event.data, url);
    window.removeEventListener("message", listener);
  }
  window.addEventListener("message", listener);
}

function queryImage(url, callback, referrer_policy) {
  decodeImage(url, function(server_data) {
    callback(wrapResult(url, server_data), url);
  }, referrer_policy)
}

function queryXhr(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.onreadystatechange = function(e) {
    if (this.readyState == 4 && this.status == 200) {
      var server_data = JSON.parse(this.responseText);
      callback(wrapResult(url, server_data), url);
    }
  };
  xhr.send();
}

function queryWorker(url, callback) {
  var worker = new Worker(url);
  worker.onmessage = function(event) {
    var server_data = event.data;
    callback(wrapResult(url, server_data), url);
  };
}

function queryFetch(url, callback) {
  fetch(url).then(function(response) {
      response.json().then(function(server_data) {
        callback(wrapResult(url, server_data), url);
      });
    }
  );
}

function queryNavigable(element, url, callback, attributes) {
  var navigable = element
  navigable.href = url;
  navigable.target = "helper-iframe";

  var helperIframe = document.createElement("iframe")
  helperIframe.name = "helper-iframe"
  document.body.appendChild(helperIframe)

  // Extend element with attributes. (E.g. "referrer_policy" or "rel")
  if (attributes) {
    for (var attr in attributes) {
      navigable[attr] = attributes[attr];
    }
  }

  var listener = function(event) {
    if (event.source != helperIframe.contentWindow)
      return;

    callback(event.data, url);
    window.removeEventListener("message", listener);
  }
  window.addEventListener("message", listener);

  navigable.click();
}

function queryLink(url, callback, referrer_policy) {
  var a = document.createElement("a");
  a.innerHTML = "Link to subresource";
  document.body.appendChild(a);
  queryNavigable(a, url, callback, referrer_policy)
}

function queryAreaLink(url, callback, referrer_policy) {
  var area = document.createElement("area");
  // TODO(kristijanburnik): Append to map and add image.
  document.body.appendChild(area);
  queryNavigable(area, url, callback, referrer_policy)
}

function queryScript(url, callback) {
  var script = document.createElement("script");
  script.src = url;

  var listener = function(event) {
    var server_data = event.data;
    callback(wrapResult(url, server_data), url);
    window.removeEventListener("message", listener);
  }
  window.addEventListener("message", listener);

  document.body.appendChild(script);
}

 // SanityChecker does nothing in release mode.
function SanityChecker() {}
SanityChecker.prototype.checkScenario = function() {};
SanityChecker.prototype.checkSubresourceResult = function() {};

// TODO(kristijanburnik): document.origin is supported since Chrome 41,
// other browsers still don't support it. Remove once they do.
document.origin = document.origin || (location.protocol + "//" + location.host);
