'use strict';

// Test is based on the following editor draft:
// https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html

// Code using this helper should also include RTCPeerConnection-helper.js
// in the main HTML file

// The following helper functions are called from RTCPeerConnection-helper.js:
//   getTrackFromUserMedia
//   exchangeOfferAnswer

// Create a RTCDTMFSender using getUserMedia()
// Connect the PeerConnection to another PC and wait until it is
// properly connected, so that DTMF can be sent.
function createDtmfSender(pc = new RTCPeerConnection()) {
  let dtmfSender;
  return getTrackFromUserMedia('audio')
  .then(([track, mediaStream]) => {
    const sender = pc.addTrack(track, mediaStream);
    dtmfSender = sender.dtmf;
    assert_true(dtmfSender instanceof RTCDTMFSender,
                'Expect audio sender.dtmf to be set to a RTCDTMFSender');
    // Note: spec bug open - https://github.com/w3c/webrtc-pc/issues/1774
    // on whether sending should be possible before negotiation.
    const pc2 = new RTCPeerConnection();
    Object.defineProperty(pc, 'otherPc', { value: pc2 });
    exchangeIceCandidates(pc, pc2);
    return exchangeOfferAnswer(pc, pc2);
  }).then(() => {
    if (!('canInsertDTMF' in dtmfSender)) {
      return Promise.resolve();
    }
    // Wait until dtmfSender.canInsertDTMF becomes true.
    // Up to 150 ms has been observed in test. Wait 1 second
    // in steps of 10 ms.
    // Note: Using a short timeout and rejected promise in order to
    // make test return a clear error message on failure.
    return new Promise((resolve, reject) => {
      let counter = 0;
      step_timeout(function checkCanInsertDTMF() {
        if (dtmfSender.canInsertDTMF) {
          resolve();
        } else {
          if (counter >= 100) {
            reject('Waited too long for canInsertDTMF');
            return;
          }
          ++counter;
          step_timeout(checkCanInsertDTMF, 10);
        }
      }, 0);
    });
  }).then(() => {
    return dtmfSender;
  });
}

/*
  Create an RTCDTMFSender and test tonechange events on it.
    testFunc
      Test function that is going to manipulate the DTMFSender.
      It will be called with:
        t - the test object
        sender - the created RTCDTMFSender
        pc - the associated RTCPeerConnection as second argument.
    toneChanges
      Array of expected tonechange events fired. The elements
      are array of 3 items:
        expectedTone
          The expected character in event.tone
        expectedToneBuffer
          The expected new value of dtmfSender.toneBuffer
        expectedDuration
          The rough time since beginning or last tonechange event
          was fired.
    desc
      Test description.
 */
function test_tone_change_events(testFunc, toneChanges, desc) {
  // Convert to cumulative time
  let cumulativeTime = 0;
  const cumulativeToneChanges = toneChanges.map(c => {
    cumulativeTime += c[2];
    return [c[0], c[1], cumulativeTime];
  });

  // Wait for same duration as last expected duration + 100ms
  // before passing test in case there are new tone events fired,
  // in which case the test should fail.
  const lastWait = toneChanges.pop()[2] + 100;

  promise_test(async t => {
    const pc = new RTCPeerConnection();
    const dtmfSender = await createDtmfSender(pc);
    const start = Date.now();

    const allEventsReceived = new Promise(resolve => {
      const onToneChange = t.step_func(ev => {
        assert_true(ev instanceof RTCDTMFToneChangeEvent,
          'Expect tone change event object to be an RTCDTMFToneChangeEvent');

        const { tone } = ev;
        assert_equals(typeof tone, 'string',
          'Expect event.tone to be the tone string');

        assert_greater_than(cumulativeToneChanges.length, 0,
          'More tonechange event is fired than expected');

        const [
          expectedTone, expectedToneBuffer, expectedTime
        ] = cumulativeToneChanges.shift();

        assert_equals(tone, expectedTone,
          `Expect current event.tone to be ${expectedTone}`);

        assert_equals(dtmfSender.toneBuffer, expectedToneBuffer,
          `Expect dtmfSender.toneBuffer to be updated to ${expectedToneBuffer}`);

        // We check that the cumulative delay is at least the expected one.
        // Note that as a UA optimization events can fire a bit (<1ms) early,
        // and system load may cause random delays. We therefore allow events
        // to be 1ms early and do not put any realistic expectation on the upper
        // bound of their timing.
        assert_between_inclusive(Date.now() - start, Math.max(0, expectedTime - 1),
                                 expectedTime + 4000,
          `Expect tonechange event for "${tone}" to be fired approximately after ${expectedTime} milliseconds`);
        if (cumulativeToneChanges.length === 0) {
          resolve();
        }
      });

      dtmfSender.addEventListener('tonechange', onToneChange);
    });

    testFunc(t, dtmfSender, pc);
    await allEventsReceived;
    const wait = ms => new Promise(resolve => t.step_timeout(resolve, ms));
    await wait(lastWait);
  }, desc);
}

// Get the one and only tranceiver from pc.getTransceivers().
// Assumes that there is only one tranceiver in pc.
function getTransceiver(pc) {
  const transceivers = pc.getTransceivers();
  assert_equals(transceivers.length, 1,
    'Expect there to be only one tranceiver in pc');

  return transceivers[0];
}
