// 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 loadImageInWindow(src, callback, attributes, w) {
  var image = new w.Image();
  image.crossOrigin = "Anonymous";
  image.onload = function() {
    callback(image);
  }

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

  image.src = src;
  w.document.body.appendChild(image)
}

function extractImageData(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);
    return imgData.data;
}

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 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, attributes, referrerPolicy, test) {
  // For images, we'll test:
  // - images in a `srcdoc` frame to ensure that it uses the referrer
  //   policy of its parent,
  // - images in a top-level document,
  // - and images in a `srcdoc` frame with its own referrer policy to
  //   override its parent.

  var noSrcDocPolicy = new Promise((resolve, reject) => {
    var iframeWithoutOwnPolicy = document.createElement('iframe');
    iframeWithoutOwnPolicy.srcdoc = "Hello, world.";
    iframeWithoutOwnPolicy.onload = function () {
      var nextUrl = url + "&cache_destroyer2=" + (new Date()).getTime();
      loadImageInWindow(nextUrl, function (img) {
        resolve(decodeImageData(extractImageData(img)));
      }, attributes, iframeWithoutOwnPolicy.contentWindow);
    };
    document.body.appendChild(iframeWithoutOwnPolicy);
  });

  // Give a srcdoc iframe a referrer policy different from the top-level page's policy.
  var iframePolicy = (referrerPolicy === "no-referrer") ? "unsafe-url" : "no-referrer";
  var srcDocPolicy = new Promise((resolve, reject) => {
    var iframeWithOwnPolicy = document.createElement('iframe');
    iframeWithOwnPolicy.srcdoc = "<meta name='referrer' content='" + iframePolicy + "'>Hello world.";

    iframeWithOwnPolicy.onload = function () {
      var nextUrl = url + "&cache_destroyer3=" + (new Date()).getTime();
      loadImageInWindow(nextUrl, function (img) {
        resolve(decodeImageData(extractImageData(img)));
      }, null, iframeWithOwnPolicy.contentWindow);
    };
    document.body.appendChild(iframeWithOwnPolicy);
  });

  var pagePolicy = new Promise((resolve, reject) => {
    loadImageInWindow(url, function (img) {
      resolve(decodeImageData(extractImageData(img)));
    }, attributes, window);
  });

  Promise.all([noSrcDocPolicy, srcDocPolicy, pagePolicy]).then(test.step_func(values => {
    assert_equals(values[0].headers.referer, values[2].headers.referer, "Referrer inside 'srcdoc' without its own policy should be the same as embedder's referrer.");
    assert_equals((iframePolicy === "no-referrer" ? undefined : document.location.href), values[1].headers.referer, "Referrer inside 'srcdoc' should use the iframe's policy if it has one");
    callback(wrapResult(url, values[2]), url);
  }));
}

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);
