# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from page_sets import press_story
from telemetry import story


class WebrtcPage(press_story.PressStory):
  def __init__(self, url, page_set, name, tags, extra_browser_args=None):
    assert url.startswith('file://webrtc_cases/')
    self.URL = url
    self.NAME = name
    super(WebrtcPage, self).__init__(
      page_set, tags=tags, extra_browser_args=extra_browser_args
    )


class GetUserMedia(WebrtcPage):
  """Why: Acquires a high definition (720p) local stream."""

  def __init__(self, page_set, tags):
    super(GetUserMedia, self).__init__(
      url='file://webrtc_cases/resolution.html',
      name='hd_local_stream_10s',
      page_set=page_set,
      tags=tags,
    )

  def ExecuteTest(self, action_runner):
    action_runner.ClickElement('button[id="hd"]')
    action_runner.Wait(10)


class DataChannel(WebrtcPage):
  """Why: Transfer as much data as possible through a data channel in 10s."""

  def __init__(self, page_set, tags):
    super(DataChannel, self).__init__(
      url='file://webrtc_cases/datatransfer.html',
      name='10s_datachannel_transfer',
      page_set=page_set,
      tags=tags,
    )

  def ExecuteTest(self, action_runner):
    action_runner.ExecuteJavaScript('megsToSend.value = 100;')
    action_runner.ClickElement('button[id="sendTheData"]')
    action_runner.Wait(10)

  def ParseTestResults(self, action_runner):
    self.AddJavaScriptMeasurement(
      'data_transferred',
      'sizeInBytes_biggerIsBetter',
      'receiveProgress.value',
      description='Amount of data transferred by data channel in 10 seconds',
    )
    self.AddJavaScriptMeasurement(
      'data_throughput',
      'bytesPerSecond_biggerIsBetter',
      'currentThroughput',
      description='Throughput of the data transfer.',
    )


class CanvasCapturePeerConnection(WebrtcPage):
  """Why: Sets up a canvas capture stream connection to a peer connection."""

  def __init__(self, page_set, tags):
    super(CanvasCapturePeerConnection, self).__init__(
      url='file://webrtc_cases/canvas-capture.html',
      name='canvas_capture_peer_connection',
      page_set=page_set,
      tags=tags,
    )

  def ExecuteTest(self, action_runner):
    with action_runner.CreateInteraction(
      'Action_Canvas_PeerConnection', repeatable=False
    ):
      action_runner.ClickElement('button[id="startButton"]')
      action_runner.Wait(10)


class VideoCodecConstraints(WebrtcPage):
  """Why: Sets up a video codec to a peer connection."""

  def __init__(self, page_set, video_codec, tags):
    super(VideoCodecConstraints, self).__init__(
      url='file://webrtc_cases/codec_constraints.html',
      name='codec_constraints_%s' % video_codec.lower(),
      page_set=page_set,
      tags=tags,
    )
    self.video_codec = video_codec

  def ExecuteTest(self, action_runner):
    with action_runner.CreateInteraction(
      'Action_Codec_Constraints', repeatable=False
    ):
      action_runner.ClickElement('input[id="%s"]' % self.video_codec)
      action_runner.ClickElement('button[id="startButton"]')
      action_runner.WaitForElement('button[id="callButton"]:enabled')
      action_runner.ClickElement('button[id="callButton"]')
      action_runner.Wait(20)
      action_runner.ClickElement('button[id="hangupButton"]')


class MultiplePeerConnections(WebrtcPage):
  """Why: Sets up several peer connections in the same page."""

  def __init__(self, page_set, tags):
    super(MultiplePeerConnections, self).__init__(
      url='file://webrtc_cases/multiple-peerconnections.html',
      name='multiple_peerconnections',
      page_set=page_set,
      tags=tags,
    )

  def ExecuteTest(self, action_runner):
    with action_runner.CreateInteraction(
      'Action_Create_PeerConnection', repeatable=False
    ):
      # Set the number of peer connections to create to 10.
      action_runner.ExecuteJavaScript(
        'document.getElementById("num-peerconnections").value=10'
      )
      action_runner.ExecuteJavaScript(
        'document.getElementById("cpuoveruse-detection").checked=false'
      )
      action_runner.ClickElement('button[id="start-test"]')
      action_runner.Wait(20)


class PausePlayPeerConnections(WebrtcPage):
  """Why: Ensures frequent pause and plays of peer connection streams work."""

  def __init__(self, page_set, tags):
    super(PausePlayPeerConnections, self).__init__(
      url='file://webrtc_cases/pause-play.html',
      name='pause_play_peerconnections',
      page_set=page_set,
      tags=tags,
    )

  def ExecuteTest(self, action_runner):
    action_runner.ExecuteJavaScript(
      'startTest({test_runtime_s}, {num_peerconnections},'
      '{iteration_delay_ms}, "video");'.format(
        test_runtime_s=20, num_peerconnections=10, iteration_delay_ms=20
      )
    )
    action_runner.Wait(20)


class InsertableStreamsAudioProcessing(WebrtcPage):
  """Why: processes/transforms audio using insertable streams."""

  def __init__(self, page_set, tags):
    super(InsertableStreamsAudioProcessing, self).__init__(
      url='file://webrtc_cases/audio-processing.html',
      name='insertable_streams_audio_processing',
      page_set=page_set,
      tags=tags,
      extra_browser_args=(
        '--enable-blink-features=WebCodecs,MediaStreamInsertableStreams'
      ),
    )
    self.supported = None

  def RunNavigateSteps(self, action_runner):
    self.supported = action_runner.EvaluateJavaScript('''(function () {
  try {
    new MediaStreamTrackGenerator('audio');
    return true;
  } catch (e) {
    return false;
  }
})()''')
    if self.supported:
      super(InsertableStreamsAudioProcessing, self).RunNavigateSteps(
        action_runner
      )

  def ExecuteTest(self, action_runner):
    self.AddMeasurement(
      'supported',
      'count_biggerIsBetter',
      1 if self.supported else 0,
      'Boolean flag indicating if this benchmark is supported by the browser.',
    )
    if not self.supported:
      return
    action_runner.WaitForJavaScriptCondition('!!audio')
    action_runner.ExecuteJavaScript('start()')
    action_runner.Wait(10)


class InsertableStreamsVideoProcessing(WebrtcPage):
  """Why: processes/transforms video in various ways."""

  def __init__(self, page_set, source, transform, sink, tags):
    super(InsertableStreamsVideoProcessing, self).__init__(
      url='file://webrtc_cases/video-processing.html',
      name=(
        'insertable_streams_video_processing_%s_%s_%s'
        % (source, transform, sink)
      ),
      page_set=page_set,
      tags=tags,
      extra_browser_args=(
        '--enable-blink-features=WebCodecs,MediaStreamInsertableStreams'
      ),
    )
    self.source = source
    self.transform = transform
    self.sink = sink
    self.supported = None

  def RunNavigateSteps(self, action_runner):
    self.supported = action_runner.EvaluateJavaScript(
      "typeof MediaStreamTrackProcessor !== 'undefined' &&"
      "typeof MediaStreamTrackGenerator !== 'undefined'"
    )
    if self.supported:
      super(InsertableStreamsVideoProcessing, self).RunNavigateSteps(
        action_runner
      )

  def ExecuteTest(self, action_runner):
    self.AddMeasurement(
      'supported',
      'count_biggerIsBetter',
      1 if self.supported else 0,
      'Boolean flag indicating if this benchmark is supported by the browser.',
    )
    if not self.supported:
      return
    with action_runner.CreateInteraction('Start_Pipeline', repeatable=True):
      action_runner.WaitForElement('select[id="sourceSelector"]:enabled')
      action_runner.ExecuteJavaScript(
        'document.getElementById("sourceSelector").value="%s";' % self.source
      )
      action_runner.WaitForElement('select[id="transformSelector"]:enabled')
      action_runner.ExecuteJavaScript(
        'document.getElementById("transformSelector").value="%s";'
        % self.transform
      )
      action_runner.WaitForElement('select[id="sinkSelector"]:enabled')
      action_runner.ExecuteJavaScript(
        'document.getElementById("sinkSelector").value="%s";' % self.sink
      )
      action_runner.ExecuteJavaScript(
        'document.getElementById("sourceSelector").dispatchEvent('
        '  new InputEvent("input", {}));'
      )
      action_runner.WaitForElement('.sinkVideo')
      action_runner.Wait(10)
    self.AddJavaScriptMeasurement(
      'sink_decoded_frames',
      'count_biggerIsBetter',
      'document.querySelector(".sinkVideo").webkitDecodedFrameCount',
      description='Number of frames received at the sink video.',
    )


class NegotiateTiming(WebrtcPage):
  """Why: Measure how long renegotiation takes with large SDP blobs."""

  def __init__(self, page_set, tags):
    super(NegotiateTiming, self).__init__(
      url='file://webrtc_cases/negotiate-timing.html',
      name='negotiate-timing',
      page_set=page_set,
      tags=tags,
    )

  def ExecuteTest(self, action_runner):
    action_runner.ExecuteJavaScript('start()')
    action_runner.WaitForJavaScriptCondition('!callButton.disabled')
    action_runner.ExecuteJavaScript('call()')
    action_runner.WaitForJavaScriptCondition('!renegotiateButton.disabled')
    # Due to suspicion of renegotiate activating too early:
    action_runner.Wait(1)
    # Negotiate 50 transceivers, then negotiate back to 1, simulating Meet "pin"
    action_runner.ExecuteJavaScript('videoSectionsField.value = 50')
    action_runner.ExecuteJavaScript('renegotiate()')
    action_runner.WaitForJavaScriptCondition('!renegotiateButton.disabled')
    action_runner.ExecuteJavaScript('videoSectionsField.value = 1')
    action_runner.ExecuteJavaScript('renegotiate()')
    action_runner.WaitForJavaScriptCondition('!renegotiateButton.disabled')
    # Negotiate back up to 50, simulating Meet "unpin". This is what gets measured.
    action_runner.ExecuteJavaScript('videoSectionsField.value = 50')
    action_runner.ExecuteJavaScript('renegotiate()')
    action_runner.WaitForJavaScriptCondition('!renegotiateButton.disabled')
    result = action_runner.EvaluateJavaScript('result')

    self.AddMeasurement(
      'callerSetLocalDescription',
      'ms',
      result['callerSetLocalDescription'],
      description='Time for caller SetLocalDescription',
    )
    self.AddMeasurement(
      'calleeSetLocalDescription',
      'ms',
      result['calleeSetLocalDescription'],
      description='Time for callee SetLocalDescription',
    )
    self.AddMeasurement(
      'callerSetRemoteDescription',
      'ms',
      result['callerSetRemoteDescription'],
      description='Time for caller SetRemoteDescription',
    )
    self.AddMeasurement(
      'calleeSetRemoteDescription',
      'ms',
      result['calleeSetRemoteDescription'],
      description='Time for callee SetRemoteDescription',
    )
    self.AddMeasurement(
      'callerCreateOffer',
      'ms',
      result['callerCreateOffer'],
      description='Time for overall offer/answer handshake',
    )
    self.AddMeasurement(
      'calleeCreateAnswer',
      'ms',
      result['calleeCreateAnswer'],
      description='Time for overall offer/answer handshake',
    )
    self.AddMeasurement(
      'elapsedTime',
      'ms',
      result['elapsedTime'],
      description='Time for overall offer/answer handshake',
    )
    self.AddMeasurement(
      'audioImpairment',
      'count',
      result['audioImpairment'],
      description='Number of late audio samples concealed during negotiation',
    )


class EncodedInsertableStreams(WebrtcPage):
  """Why: Performs encoded insertable streams."""

  def __init__(self, page_set, tags):
    super(EncodedInsertableStreams, self).__init__(
      url='file://webrtc_cases/encoded-insertable-streams.html',
      name='encoded_insertable_streams',
      page_set=page_set,
      tags=tags,
    )

  def ExecuteTest(self, action_runner):
    with action_runner.CreateInteraction(
      'Action_Create_PeerConnection', repeatable=False
    ):
      # Set the number of peer connections to create to 10.
      action_runner.ExecuteJavaScript(
        'document.getElementById("num-peerconnections").value=10'
      )
      action_runner.ClickElement('button[id="start-test"]')
      action_runner.Wait(20)


class WebrtcPageSet(story.StorySet):
  def __init__(self):
    super(WebrtcPageSet, self).__init__(
      cloud_storage_bucket=story.PUBLIC_BUCKET
    )

    self.AddStory(PausePlayPeerConnections(self, tags=['pauseplay']))
    self.AddStory(MultiplePeerConnections(self, tags=['stress']))
    self.AddStory(DataChannel(self, tags=['datachannel']))
    self.AddStory(GetUserMedia(self, tags=['getusermedia']))
    self.AddStory(CanvasCapturePeerConnection(self, tags=['smoothness']))
    self.AddStory(
      VideoCodecConstraints(self, 'H264', tags=['videoConstraints'])
    )
    self.AddStory(VideoCodecConstraints(self, 'VP8', tags=['videoConstraints']))
    self.AddStory(VideoCodecConstraints(self, 'VP9', tags=['videoConstraints']))
    self.AddStory(
      InsertableStreamsAudioProcessing(self, tags=['insertableStreams'])
    )
    self.AddStory(
      InsertableStreamsVideoProcessing(
        self, 'camera', 'webgl', 'video', tags=['insertableStreams']
      )
    )
    self.AddStory(
      InsertableStreamsVideoProcessing(
        self, 'video', 'webgl', 'video', tags=['insertableStreams']
      )
    )
    self.AddStory(
      InsertableStreamsVideoProcessing(
        self, 'pc', 'webgl', 'video', tags=['insertableStreams']
      )
    )
    self.AddStory(
      InsertableStreamsVideoProcessing(
        self, 'camera', 'canvas2d', 'video', tags=['insertableStreams']
      )
    )
    self.AddStory(
      InsertableStreamsVideoProcessing(
        self, 'camera', 'noop', 'video', tags=['insertableStreams']
      )
    )
    self.AddStory(
      InsertableStreamsVideoProcessing(
        self, 'camera', 'webgl', 'pc', tags=['insertableStreams']
      )
    )
    self.AddStory(NegotiateTiming(self, tags=['sdp']))
    self.AddStory(EncodedInsertableStreams(self, tags=['stress']))
