#!/usr/bin/env python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import os
import platform
import re
import subprocess

import pyauto


class MissingRequiredBinaryException(Exception):
  pass


class WebrtcTestBase(pyauto.PyUITest):
  """This base class provides helpers for WebRTC calls."""

  DEFAULT_TEST_PAGE = 'webrtc_jsep01_test.html'

  def ExtraChromeFlags(self):
    """Adds flags to the Chrome command line."""
    extra_flags = ['--enable-data-channels', '--enable-dcheck']
    return pyauto.PyUITest.ExtraChromeFlags(self) + extra_flags

  def LoadTestPageInTwoTabs(self, test_page=DEFAULT_TEST_PAGE):
    url = self.GetFileURLForDataPath('webrtc', test_page)
    self.NavigateToURL(url)
    self.AppendTab(pyauto.GURL(url))

  def LoadTestPageInOneTab(self, test_page=DEFAULT_TEST_PAGE):
    url = self.GetFileURLForDataPath('webrtc', test_page)
    self.NavigateToURL(url)

  def GetUserMedia(self, tab_index, action='accept',
                   request_video=True, request_audio=True):
    """Acquires webcam or mic for one tab and returns the result.

    Args:
      tab_index: The tab to request user media on.
      action: The action to take on the info bar. Can be 'accept', 'cancel' or
          'dismiss'.
      request_video: Whether to request video.
      request_audio: Whether to request audio.

    Returns:
      A string as specified by the getUserMedia javascript function.
    """
    constraints = '{ video: %s, audio: %s }' % (str(request_video).lower(),
                                                str(request_audio).lower())
    self.assertEquals('ok-requested', self.ExecuteJavascript(
        'getUserMedia("%s")' % constraints, tab_index=tab_index))

    self.WaitForInfobarCount(1, tab_index=tab_index)
    self.PerformActionOnInfobar(action, infobar_index=0, tab_index=tab_index)
    self.WaitForGetUserMediaResult(tab_index=0)

    result = self.GetUserMediaResult(tab_index=0)
    self.AssertNoFailures(tab_index)
    return result

  def WaitForGetUserMediaResult(self, tab_index):
    """Waits until WebRTC has responded to a getUserMedia query.

    Fails an assert if WebRTC doesn't respond within the default timeout.

    Args:
      tab_index: the tab to query.
    """
    def HasResult():
      return self.GetUserMediaResult(tab_index) != 'not-called-yet'
    self.assertTrue(self.WaitUntil(HasResult),
                    msg='Timed out while waiting for getUserMedia callback.')

  def GetUserMediaResult(self, tab_index):
    """Retrieves WebRTC's answer to a user media query.

    Args:
      tab_index: the tab to query.

    Returns:
      Specified in obtainGetUserMediaResult() in getusermedia.js.
    """
    return self.ExecuteJavascript(
        'obtainGetUserMediaResult()', tab_index=tab_index)

  def AssertNoFailures(self, tab_index):
    """Ensures the javascript hasn't registered any asynchronous errors.

    Args:
      tab_index: The tab to check.
    """
    self.assertEquals('ok-no-errors', self.ExecuteJavascript(
        'getAnyTestFailures()', tab_index=tab_index))

  def Connect(self, user_name, tab_index):
    self.assertEquals('ok-connected', self.ExecuteJavascript(
        'connect("http://localhost:8888", "%s")' % user_name,
        tab_index=tab_index))
    self.AssertNoFailures(tab_index)

  def CreatePeerConnection(self, tab_index):
    self.assertEquals('ok-peerconnection-created', self.ExecuteJavascript(
        'preparePeerConnection()', tab_index=tab_index))

  def AddUserMediaLocalStream(self, tab_index):
    self.assertEquals('ok-added', self.ExecuteJavascript(
        'addLocalStream()', tab_index=tab_index))

  def AddWebAudioFile(self, tab_index, input_relative_path):
    """The path must be relative to where the javascript is.

    This call just loads and adds a file to a peer connection, but it doesn't
    start to play it until you call PlayWebAudioFile.
    """
    self.assertEquals('ok-added', self.ExecuteJavascript(
        'addAudioFile("%s")' % re.escape(input_relative_path),
        tab_index=tab_index))

  def PlayWebAudioFile(self, tab_index):
    """Plays a web audio file which was added earlier."""
    self.assertEquals('ok-playing', self.ExecuteJavascript(
        'playAudioFile()', tab_index=tab_index))

  def EstablishCall(self, from_tab_with_index, to_tab_with_index):
    self.WaitUntilPeerConnects(tab_index=from_tab_with_index)

    self.assertEquals('ok-negotiating', self.ExecuteJavascript(
        'negotiateCall()', tab_index=from_tab_with_index))
    self.AssertNoFailures(from_tab_with_index)

    self.WaitUntilReadyState(ready_state='active',
                             tab_index=from_tab_with_index)

    # Double-check the call reached the other side.
    self.WaitUntilReadyState(ready_state='active',
                             tab_index=to_tab_with_index)

  def HangUp(self, from_tab_with_index):
    self.assertEquals('ok-call-hung-up', self.ExecuteJavascript(
        'hangUp()', tab_index=from_tab_with_index))
    self.WaitUntilHangUpVerified(tab_index=from_tab_with_index)
    self.AssertNoFailures(tab_index=from_tab_with_index)

  def WaitUntilPeerConnects(self, tab_index):
    peer_connected = self.WaitUntil(
        function=lambda: self.ExecuteJavascript('remotePeerIsConnected()',
                                                tab_index=tab_index),
        expect_retval='peer-connected')
    self.assertTrue(peer_connected,
                    msg='Timed out while waiting for peer to connect.')

  def WaitUntilReadyState(self, ready_state, tab_index):
    got_ready_state = self.WaitUntil(
        function=lambda: self.ExecuteJavascript('getPeerConnectionReadyState()',
                                                tab_index=tab_index),
        expect_retval=ready_state)
    self.assertTrue(got_ready_state,
                    msg=('Timed out while waiting for peer connection ready '
                         'state to change to %s for tab %d.' % (ready_state,
                                                                tab_index)))

  def WaitUntilHangUpVerified(self, tab_index):
    self.WaitUntilReadyState('no-peer-connection', tab_index=tab_index)

  def Disconnect(self, tab_index):
    self.assertEquals('ok-disconnected', self.ExecuteJavascript(
        'disconnect()', tab_index=tab_index))

  def BinPathForPlatform(self, path):
    """Form a platform specific path to a binary.

    Args:
      path(string): The path to the binary without an extension.
    Return:
      (string): The platform-specific bin path.
    """
    if self.IsWin():
      path += '.exe'
    return path

  def PlatformIsWinXP(self):
    """Check if the executing platform is Windows XP.

    Return:
      True if the platform is Windows XP.
    """
    return platform.system() == 'Windows' and platform.release() == 'XP'

  def StartPeerConnectionServer(self):
    """Starts peerconnection_server.

    Peerconnection_server is a custom binary allowing two WebRTC clients to find
    each other. For more details, see the source code which is available at the
    site http://code.google.com/p/libjingle/source/browse/ (make sure to browse
    to trunk/talk/examples/peerconnection/server).
    """
    # Start the peerconnection_server. It should be next to chrome.
    binary_path = os.path.join(self.BrowserPath(), 'peerconnection_server')
    binary_path = self.BinPathForPlatform(binary_path)

    if not os.path.exists(binary_path):
      raise MissingRequiredBinaryException(
        'Could not locate peerconnection_server. Have you built the '
        'peerconnection_server target? We expect to have a '
        'peerconnection_server binary next to the chrome binary.')

    self._server_process = subprocess.Popen(binary_path)

  def StopPeerConnectionServer(self):
    """Stops the peerconnection_server."""
    assert self._server_process
    self._server_process.kill()
