require './core/data/player_core_data'
require './core/base/page_helper'
require './core/twitch/video_player/player_core_events'

class PlayerCore < PageHelper
  @video_container = '#videoContainer'
  @network_stats_params = ['bytes', 'currBufferLength', 'downloadDuration', 'firstByteLatency', 'segmentDuration', 'startTime']
  @qualities_select_css = '#qualities'

  class << self
    attr_reader :video_container
    include PlayerCoreEvents
    # Starts the stream/vod given with the following formats:
    # Stream: live://channel
    # VOD: vod://78264336
    # @param url [String] Url to visit
    def load_and_play(url)
      page.evaluate_script("player.load('#{url}')")
      play
    end

    # @return [Array] Resolution data on the specified video
    def get_qualities
      return Capybara.evaluate_script('player.getQualities();')
    end

    # @return [Array] name of all available qualities
    def get_qualities_name
      quality_names = []
      get_qualities.each { |quality| quality_names << quality['name'] }
      return quality_names
    end

    # @return [Boolean] True if player is playing(live/vod) else False
    def is_paused?
      return page.execute_script('return player.getPlayerState()') == 'Idle'
    end

    # Placeholder to remove using static sleep # ToDo
    # @param duration [Integer] Time in seconds for sleep
    def wait_period(duration)
      # Need to wait for particular duration and check if playHead changes or not
      sleep(duration)
    end

    # @return [Hash] Quality in a Hash
    def current_quality
      return Capybara.evaluate_script('player.getQuality();')
    end

    # @param volume [int] Volume level to be set from range 0 to 1
    def set_volume(volume)
      page.evaluate_script("player.setVolume(#{volume});")
    end

    # @return [Int] Current volume of player
    def get_volume
      return page.evaluate_script('player.getVolume();').round(1)
    end

    # @return [Float] Current frame rate of stream
    def get_fps
      return page.evaluate_script('player.getVideoFrameRate();')
    end

    # @return [Integer] Return broadcaster latency
    def get_broadcaster_latency
      return page.evaluate_script('player.getBroadcasterLatency();')
    end

    # @return [Integer] Return encoder latency
    def get_encoder_latency
      return page.evaluate_script('player.getTranscoderLatency();')
    end

    # @param playback_rate [Integer] Sets the playback rate of stream
    def set_playback_rate(playback_rate)
      page.evaluate_script("player.setPlaybackRate(#{playback_rate});")
    end

    # @return [Integer] Returns the playback rate of the stream
    def get_playback_rate
      return page.evaluate_script('player.getPlaybackRate();').to_i
    end

    # @return [Capybara::Result] Returns all the available options for Quality Select Box
    def available_quality_options
      quality_options = find(@qualities_select_css).all('option')
      expect(quality_options.size).to be > 0
      return quality_options
    end

    # @return [Boolean] True if player container loaded
    def has_video_container?
      page.has_css?(@video_container, wait: 5)
    end

    # @return [Float] PLayHead of the stream
    def current_playhead
      return page.execute_script('return player.getPosition()')
    end

    # @return [Float] buffer duration of the current video
    def get_buffer_duration
      return page.evaluate_script('player.getBufferDuration()')
    end

    # @return [String] current state of the video playback
    def get_player_state
      return page.evaluate_script('player.getPlayerState()')
    end

    # @return [Integer] Duration of current VOD
    def stream_duration
      return page.evaluate_script("player.getDuration();")
    end

    # @return [Boolean] True if player is currently playing video
    def stream_playing?(wait_time=20)
      timeout = Time.now + wait_time
      while Time.now < timeout
        puts "[INFO] #{Time.now} PlayerState - #{get_player_state} : Current Playhead - #{current_playhead}"
        return true if get_player_state.eql? 'Playing' # true if video is currently playing
      end
      return false
    end

    # @param playback_duration [Integer] Duration for which stream will be validated
    # @return [Boolean] True, if smooth playback for whole stream
    def smooth_playhead?(playback_duration=nil)
      playback_duration ||= stream_duration
      wait_time = 2
      smooth_play_js = "
      var callback = arguments[arguments.length - 1];
      var currTime = 0;
      var newTime = 0;
      var totalTime = 0;
      var permissibleVariation = 1.25;
      const wait = (ms) => {
        return new Promise((resolve) => {
          setTimeout(resolve, ms);
        });
      }
      async function check_playback() {
        totalTime = player.getPosition() + #{playback_duration};
        while (totalTime > player.getPosition()) {
          currTime = player.getPosition();
          await wait(#{wait_time*1000});
          newTime = player.getPosition();
          if ((newTime - currTime) >= (#{wait_time*1000} + permissibleVariation)) {
            callback({
              'success': false,
              'message': 'Expected Playhead within ' + currTime +  '. Got Playhead - ' + newTime
            });
          }
        }
        callback({'success': true, 'message': ''});
      }
      check_playback();"

      response = TwitchUtils.execute_event_listener_script(smooth_play_js, playback_duration + 5)
      if response.is_a? Hash
        puts "[ERROR] #{response['message']}" unless response['success']
        return response['success']
      end
      return response
    end

    # @param fps [Integer] Expected FPS of stream
    # @param playback_duration [Integer] Duration for which stream will be validated
    # @return [Boolean] True, if smooth playback for whole stream
    def smooth_fps?(fps, playback_duration=nil)
      playback_duration ||= stream_duration
      permissible_variation = 2
      lower_limit, upper_limit = fps - permissible_variation, fps + permissible_variation
      wait_time = 2
      smooth_fps_js = "
      var callback = arguments[arguments.length - 1];
      var currFps = 0;
      var totalTime = 0;
      const wait = (ms) => {
        return new Promise((resolve) => {
          setTimeout(resolve, ms);
        });
      }
      async function check_fps() {
        totalTime = player.getPosition() + #{playback_duration};
        while (totalTime > player.getPosition()) {
          await wait(#{wait_time*1000});
          currFps = player.getVideoFrameRate();
          if (currFps > #{upper_limit} || currFps < #{lower_limit}){
            callback({
              'success': false,
              'message': 'Expected FPS within #{upper_limit}-#{lower_limit}. Got FPS - ' + currFps
            });
          }
        }
        callback({'success': true, 'message': ''});
      }
      check_fps();"

      response = TwitchUtils.execute_event_listener_script(smooth_fps_js, playback_duration + 5)
      if response.is_a? Hash
        puts "[ERROR] #{response['message']}" unless response['success']
        return response['success']
      end
      return response
    end

    # @param time_in_seconds [Integer] Time in seconds to go to position
    def seek_to_position(time_in_seconds)
      page.evaluate_script("player.seekTo(#{time_in_seconds})")
    end

    # Retreives network stats values
    # @return [Array] Returns Array of Hash having network stats
    def network_stats
      return page.execute_script('return player.getNetworkProfile()')
    end

    # Compares all the stats to be greater than 0
    # @param network_stats [Array] Array of Hash having network stats
    # @return [Boolean] true if all stats are greter than 0
    def valid_stats?(network_stats, values_to_check=[])
      network_stats.each do |stat|
        stat.select! { |key, value| values_to_check.include?(key) }
        return false if stat.any? { |key, value| value < 0 }
      end
      return true
    end

    # @return [Boolean] true if all network stats are valid
    def valid_network_stats?(network_stats)
      return valid_stats?(network_stats, @network_stats_params)
    end

    # Retreive playback stats
    # @return [Array] array of playback stats value
    def playback_stats_values
        timeout = Time.now + 10
        while Time.now < timeout
          if page.execute_script('return player.getVideoFrameRate()') > 1
              return stats = [page.execute_script('return player.getVideoFrameRate()'),
                page.execute_script('return player.getPlaybackRate()'),
                page.execute_script('return player.getBufferDuration()'),
                page.execute_script('return player.getVideoWidth()'),
                page.execute_script('return player.getVideoHeight()'),
                page.execute_script('return player.getDecodedFrames()'),
                page.execute_script('return player.getVolume()')]
          end
        end
        return false
    end

    # @return [Hash] array of hashes
    def get_quality_list
      qualities = get_qualities
      quality_list = Hash.new

      if qualities.to_a.empty?
        raise "Error. Qualities array is empty."
      else
        qualities.each do |quality|
          quality_list[quality['name']] = quality['height']
        end
      end
      return quality_list
    end

    # @return [Capybara::Element] dropdown values
    def quality_dropdown
      return find(@qualities_select_css)
    end

    # enables and disables ABS checkbox
    # @param value [Boolean] enable/disable abs
    def enable_abs(value = false)
      page.execute_script("player.setAutoSwitchQuality('#{value}')")
    end

    # @return [String] returns player version
    def player_version
      return page.execute_script('return player.getVersion()')
    end

    # @return [Boolean] whether volume is muted or not
    def is_muted?
      return page.execute_script('return player.isMuted()')
    end

    # enables and disables ABS checkbox
    # @param value [Boolean] enable/disable abs
    def enable_abs(value = false)
      page.execute_script("return player.setAutoSwitchQuality(#{value})")
    end

    # @param [Integer] Time Duration to wait
    # @return [Boolean] True if stream is playing
    def playhead_changes_with_time?(wait_time)
      playhead_change_js = "
        var callback = arguments[arguments.length - 1];
        var timeArr = [];
        var permissibleVariation = 1;
        var expectedTime = 0;
        function checkPlayhead() {
          timeArr.push(parseInt(player.getPosition()));
          setTimeout(function(){
            expectedTime = timeArr[0] + #{wait_time};
            timeArr.push(parseInt(player.getPosition()));
            if ((timeArr[1] <= expectedTime + permissibleVariation) && (timeArr[1] >= expectedTime - permissibleVariation)){
              callback({
                'success': true,
                'message': 'Time Durations - ' + timeArr + '. Wait Period - #{wait_time}'
              });
            } else {
              callback({
                'success': false,
                'message': 'Time Durations - ' + timeArr + '. Wait Period - #{wait_time}'
              });
            }
          }, #{wait_time}*1000);
        }
        checkPlayhead();"
      response = TwitchUtils.execute_event_listener_script(playhead_change_js, 30)
      if response.is_a? Hash
        puts "[INFO] #{response['message']}"
        return response['success']
      end
      return response
    end

    # @param state [Boolean] Enable(true) / Disable(false) Ad Insertion
    def ad_insertion_enabled(state)
      page.execute_script("player.setAdInsertionEnabled(#{state});")
    end
  end
end
