# Copyright (c) 2013 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 logging
import operator
import os
import time

from autotest_lib.client.bin import test
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import chrome
from autotest_lib.client.cros.video import helper_logger


class video_YouTubePage(test.test):
    """The main test class of this test.

    """


    version = 1

    PSEUDO_RANDOM_TIME_1 = 20.25
    PSEUDO_RANDOM_TIME_2 = 5.47

    # Minimum number of timeupdates required to fire in the last second.
    MIN_LAST_SECOND_UPDATES = 3

    NO_DELAY = 0
    MINIMAL_DELAY = 1
    MAX_REBUFFER_DELAY = 10

    PLAYING_STATE = 'playing'
    PAUSED_STATE = 'paused'
    ENDED_STATE = 'ended'
    TEST_PAGE = 'http://web-release-qa.youtube.com/watch?v=zuzaxlddWbk&html5=1'

    DISABLE_COOKIES = False

    tab = None


    def initialize_test(self, chrome, player_page):
        """Initializes the test.

        @param chrome: An Autotest Chrome instance.
        @param player_page: The URL (string) of the YouTube player page to test.

        """
        self.tab = chrome.browser.tabs[0]

        self.tab.Navigate(player_page)
        self.tab.WaitForDocumentReadyStateToBeComplete()
        time.sleep(2)

        with open(
                os.path.join(os.path.dirname(__file__),
                'files/video_YouTubePageCommon.js')) as f:
            js = f.read()
            if not self.tab.EvaluateJavaScript(js):
                raise error.TestFail('YouTube page failed to load.')
            logging.info('Loaded accompanying .js script.')


    def get_player_state(self):
        """Simple wrapper to get the JS player state.

        @returns: The state of the player (string).

        """
        return self.tab.EvaluateJavaScript('window.__getVideoState();')


    def play_video(self):
        """Simple wrapper to play the video.

        """
        self.tab.ExecuteJavaScript('window.__playVideo();')


    def pause_video(self):
        """Simple wrapper to pause the video.

        """
        self.tab.ExecuteJavaScript('window.__pauseVideo();')


    def seek_video(self, new_time):
        """Simple wrapper to seek the video to a new time.

        @param new_time: Time to seek to.

        """
        self.tab.ExecuteJavaScript('window.__seek(%f);' % new_time)


    def seek_to_almost_end(self, seconds_before_end):
        """Simple wrapper to seek to almost the end of the video.

        @param seconds_before_end: How many seconds (a float, not integer)
                before end of video.

        """
        self.tab.ExecuteJavaScript(
                'window.__seekToAlmostEnd(%f);' % seconds_before_end)


    def get_current_time(self):
        """Simple wrapper to get the current time in the video.

        @returns: The current time (float).

        """
        return self.tab.EvaluateJavaScript('window.__getCurrentTime();')


    def assert_event_state(self, event, op, error_str):
        """Simple wrapper to get the status of a state in the video.

        @param event: A string denoting the event. Check the accompanying JS
                file for the possible values.
        @param op: truth or not_ operator from the standard Python operator
                module.
        @param error_str: A string for the error output.

        @returns: Whether or not the input event has fired.

        """
        result = self.tab.EvaluateJavaScript(
                'window.__getEventHappened("%s");' % event)
        if not op(result):
            raise error.TestError(error)


    def clear_event_state(self, event):
        """Simple wrapper to clear the status of a state in the video.

        @param event: A string denoting the event. Check the accompanying JS
                file for the possible vlaues.

        """
        self.tab.ExecuteJavaScript('window.__clearEventHappened("%s");' % event)


    def verify_last_second_playback(self):
        """Simple wrapper to check the playback of the last second.

        """
        result = self.tab.EvaluateJavaScript(
                'window.__getLastSecondTimeupdates()')
        if result < self.MIN_LAST_SECOND_UPDATES:
            raise error.TestError(
                    'Last second did not play back correctly (%d events).' %
                    result)


    def assert_player_state(self, state, max_wait_secs):
        """Simple wrapper to busy wait and test the current state of the player.

        @param state: A string denoting the expected state of the player.
        @param max_wait_secs: Maximum amount of time to wait before failing.

        @raises: A error.TestError if the state is not as expected.

        """
        start_time = time.time()
        while True:
            current_state = self.get_player_state()
            if current_state == state:
                return
            elif time.time() < start_time + max_wait_secs:
                time.sleep(0.5)
            else:
                raise error.TestError(
                        'Current player state "%s" is not the expected state '
                        '"%s".' % (current_state, state))


    def perform_test(self):
        """Base method for derived classes to run their test.

        """
        raise error.TestFail('Derived class did not specify a perform_test.')


    def perform_playing_test(self):
        """Test to check if the YT page starts off playing.

        """
        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
        if self.get_current_time() <= 0.0:
            raise error.TestError('perform_playing_test failed.')


    def perform_pausing_test(self):
        """Test to check if the video is in the 'paused' state.

        """
        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
        self.pause_video()
        self.assert_player_state(self.PAUSED_STATE, self.MINIMAL_DELAY)


    def perform_resuming_test(self):
        """Test to check if the video responds to resumption.

        """
        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
        self.pause_video()
        self.assert_player_state(self.PAUSED_STATE, self.MINIMAL_DELAY)
        self.play_video()
        self.assert_player_state(self.PLAYING_STATE, self.MINIMAL_DELAY)


    def perform_seeking_test(self):
        """Test to check if seeking works.

        """
        # Test seeking while playing.
        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
        self.seek_video(self.PSEUDO_RANDOM_TIME_1)
        time.sleep(self.MINIMAL_DELAY)
        if not self.tab.EvaluateJavaScript(
                'window.__getCurrentTime() >= %f;' % self.PSEUDO_RANDOM_TIME_1):
            raise error.TestError(
                    'perform_seeking_test failed because player time is not '
                    'the expected time during playing seeking.')
        self.assert_event_state(
                'seeking', operator.truth,
                'perform_seeking_test failed: "seeking" state did not fire.')
        self.assert_event_state(
                'seeked', operator.truth,
                'perform_seeking_test failed: "seeked" state did not fire.')

        # Now make sure the video is still playing.

        # Let it buffer/play for at most 10 seconds before continuing.
        self.assert_player_state(self.PLAYING_STATE, self.MAX_REBUFFER_DELAY)

        self.clear_event_state('seeking');
        self.clear_event_state('seeked');
        self.assert_event_state(
                'seeking', operator.not_,
                'perform_seeking_test failed: '
                '"seeking" state did not get cleared.')
        self.assert_event_state(
                'seeked', operator.not_,
                'perform_seeking_test failed: '
                '"seeked" state did not get cleared.')

        # Test seeking while paused.
        self.pause_video()
        self.assert_player_state(self.PAUSED_STATE, self.MINIMAL_DELAY)

        self.seek_video(self.PSEUDO_RANDOM_TIME_2)
        time.sleep(self.MINIMAL_DELAY)
        if not self.tab.EvaluateJavaScript(
                'window.__getCurrentTime() === %f;' %
                self.PSEUDO_RANDOM_TIME_2):
            raise error.TestError(
                    'perform_seeking_test failed because player time is not '
                    'the expected time.')
        self.assert_event_state(
                'seeking', operator.truth,
                'perform_seeking_test failed: "seeking" state did not fire '
                'again.')
        self.assert_event_state(
                'seeked', operator.truth,
                'perform_seeking_test failed: "seeked" state did not fire '
                'again.')

        # Make sure the video is paused.
        self.assert_player_state(self.PAUSED_STATE, self.NO_DELAY)


    def perform_frame_drop_test(self):
        """Test to check if there are too many dropped frames.

        """
        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
        time.sleep(15)
        dropped_frames_percentage = self.tab.EvaluateJavaScript(
                'window.__videoElement.webkitDroppedFrameCount /'
                'window.__videoElement.webkitDecodedFrameCount')
        if dropped_frames_percentage > 0.01:
            raise error.TestError((
                    'perform_frame_drop_test failed due to too many dropped '
                    'frames (%f%%)') % (dropped_frames_percentage * 100))


    def perform_ending_test(self):
        """Test to check if the state is 'ended' at the end of a video.

        """
        ALMOST_END = 0.1
        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
        self.seek_to_almost_end(ALMOST_END)
        self.assert_player_state(self.ENDED_STATE, self.MAX_REBUFFER_DELAY)


    def perform_last_second_test(self):
        """Test to check if the last second is played.

        """
        NEAR_END = 2.0
        self.assert_player_state(self.PLAYING_STATE, self.NO_DELAY)
        self.seek_to_almost_end(NEAR_END)
        self.assert_player_state(
                self.ENDED_STATE, self.MAX_REBUFFER_DELAY + NEAR_END)
        self.verify_last_second_playback()


    @helper_logger.video_log_wrapper
    def run_once(self, subtest_name):
        """Main runner for the test.

        @param subtest_name: The name of the test to run, given below.

        """
        extension_paths = []
        if self.DISABLE_COOKIES:
            # To stop the system from erasing the previous profile, enable:
            #  options.dont_override_profile = True
            extension_path = os.path.join(
                    os.path.dirname(__file__),
                    'files/cookie-disabler')
            extension_paths.append(extension_path)


        with chrome.Chrome(
                extra_browser_args=helper_logger.chrome_vmodule_flag(),
                extension_paths=extension_paths) as cr:
            self.initialize_test(cr, self.TEST_PAGE)

            if subtest_name is 'playing':
                self.perform_playing_test()
            elif subtest_name is 'pausing':
                self.perform_pausing_test()
            elif subtest_name is 'resuming':
                self.perform_resuming_test()
            elif subtest_name is 'seeking':
                self.perform_seeking_test()
            elif subtest_name is 'frame_drop':
                self.perform_frame_drop_test()
            elif subtest_name is 'ending':
                self.perform_ending_test()
            elif subtest_name is 'last_second':
                self.perform_last_second_test()