'use strict';
/* Helper functions to munge SDP and split the sending track into
 * separate tracks on the receiving end. This can be done in a number
 * of ways, the one used here uses the fact that the MID and RID header
 * extensions which are used for packet routing share the same wire
 * format. The receiver interprets the rids from the sender as mids
 * which allows receiving the different spatial resolutions on separate
 * m-lines and tracks.
 */
const extensionsToFilter = [
  'urn:ietf:params:rtp-hdrext:sdes:mid',
  'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id',
  'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id',
];

function swapRidAndMidExtensionsInSimulcastOffer(offer, rids) {
  const sections = SDPUtils.splitSections(offer.sdp);
  const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
  const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
  const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);

  // The gist of this hack is that rid and mid have the same wire format.
  const rid = rtpParameters.headerExtensions.find(ext => ext.uri === 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id');
  rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(ext => {
    return !extensionsToFilter.includes(ext.uri);
  });
  // This tells the other side that the RID packets are actually mids.
  rtpParameters.headerExtensions.push({id: rid.id, uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', direction: 'sendrecv'});

  // Filter rtx as we have no way to (re)interpret rrid.
  // Not doing this makes probing use RTX, it's not understood and ramp-up is slower.
  rtpParameters.codecs = rtpParameters.codecs.filter(c => c.name.toUpperCase() !== 'RTX');

  let sdp = SDPUtils.writeSessionBoilerplate() +
    SDPUtils.writeDtlsParameters(dtls, 'actpass') +
    SDPUtils.writeIceParameters(ice) +
    'a=group:BUNDLE ' + rids.join(' ') + '\r\n';
  const baseRtpDescription = SDPUtils.writeRtpDescription('video', rtpParameters);
  rids.forEach(rid => {
    sdp += baseRtpDescription +
        'a=mid:' + rid + '\r\n' +
        'a=msid:rid-' + rid + ' rid-' + rid + '\r\n';
  });
  return sdp;
}

function swapRidAndMidExtensionsInSimulcastAnswer(answer, localDescription, rids) {
  const sections = SDPUtils.splitSections(answer.sdp);
  const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
  const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
  const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);

  rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(ext => {
    return !extensionsToFilter.includes(ext.uri);
  });
  const localMid = SDPUtils.getMid(SDPUtils.splitSections(localDescription.sdp)[1]);
  let sdp = SDPUtils.writeSessionBoilerplate() +
    SDPUtils.writeDtlsParameters(dtls, 'active') +
    SDPUtils.writeIceParameters(ice) +
    'a=group:BUNDLE ' + localMid + '\r\n';
  sdp += SDPUtils.writeRtpDescription('video', rtpParameters);
  sdp += 'a=mid:' + localMid + '\r\n';

  rids.forEach(rid => {
    sdp += 'a=rid:' + rid + ' recv\r\n';
  });
  sdp += 'a=simulcast:recv ' + rids.join(';') + '\r\n';

  // Re-add headerextensions we filtered.
  const headerExtensions = SDPUtils.parseRtpParameters(SDPUtils.splitSections(localDescription.sdp)[1]).headerExtensions;
  headerExtensions.forEach(ext => {
    if (extensionsToFilter.includes(ext.uri)) {
      sdp += 'a=extmap:' + ext.id + ' ' + ext.uri + '\r\n';
    }
  });
  return sdp;
}

async function negotiateSimulcastAndWaitForVideo(t, rids, pc1, pc2, codec) {
  exchangeIceCandidates(pc1, pc2);

  const metadataToBeLoaded = [];
  pc2.ontrack = (e) => {
    const stream = e.streams[0];
    const v = document.createElement('video');
    v.autoplay = true;
    v.srcObject = stream;
    v.id = stream.id
    metadataToBeLoaded.push(new Promise((resolve) => {
        v.addEventListener('loadedmetadata', () => {
            resolve();
        });
    }));
  };

  const sendEncodings = rids.map(rid => ({rid}));
  // Use a 2X downscale factor between each layer. To improve ramp-up time, the
  // top layer is scaled down by a factor 2. Smaller layer comes first. For
  // example if MediaStreamTrack is 720p and we want to send three layers we'll
  // get {90p, 180p, 360p}.
  let scaleResolutionDownBy = 2;
  for (let i = sendEncodings.length - 1; i >= 0; --i) {
    sendEncodings[i].scaleResolutionDownBy = scaleResolutionDownBy;
    scaleResolutionDownBy *= 2;
  }

  // Use getUserMedia as getNoiseStream does not have enough entropy to ramp-up.
  await setMediaPermission();
  const stream = await navigator.mediaDevices.getUserMedia({video: {width: 1280, height: 720}});
  t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
  const transceiver = pc1.addTransceiver(stream.getVideoTracks()[0], {
    streams: [stream],
    sendEncodings: sendEncodings,
  });
  if (codec) {
    preferCodec(transceiver, codec.mimeType, codec.sdpFmtpLine);
  }

  const offer = await pc1.createOffer();
  await pc1.setLocalDescription(offer),
  await pc2.setRemoteDescription({
    type: 'offer',
    sdp: swapRidAndMidExtensionsInSimulcastOffer(offer, rids),
  });
  const answer = await pc2.createAnswer();
  await pc2.setLocalDescription(answer);
  await pc1.setRemoteDescription({
    type: 'answer',
    sdp: swapRidAndMidExtensionsInSimulcastAnswer(answer, pc1.localDescription, rids),
  });
  assert_equals(metadataToBeLoaded.length, rids.length);
  return Promise.all(metadataToBeLoaded);
}
