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

import logging
import py_utils

from page_sets.desktop_ui.custom_metric_utils import SetMetricNames
from page_sets.desktop_ui.js_utils import MEASURE_JS_MEMORY
from page_sets.desktop_ui.multitab_story import MultiTabStory
from page_sets.desktop_ui.ui_devtools_utils import (
  PressKey,
  PLATFORM_ACCELERATOR,
  SHIFT_DOWN,
)
from page_sets.desktop_ui.url_list import TOP_URL
from page_sets.desktop_ui.webui_utils import Inspect
from page_sets.desktop_ui import story_tags

TAB_SEARCH_BENCHMARK_UMA = [
  'Tabs.TabSearch.CloseAction',
  'Tabs.TabSearch.Mojo.SwitchToTab',
  'Tabs.TabSearch.Mojo.SwitchToTab.IsOverlap',
  'Tabs.TabSearch.Mojo.TabUpdated',
  'Tabs.TabSearch.Mojo.TabUpdated.IsOverlap',
  'Tabs.TabSearch.NumTabsClosedPerInstance',
  'Tabs.TabSearch.NumTabsOnOpen',
  'Tabs.TabSearch.NumWindowsOnOpen',
  'Tabs.TabSearch.OpenAction',
  'Tabs.TabSearch.PageHandlerConstructionDelay',
  'Tabs.TabSearch.WebUI.InitialTabsRenderTime',
  'Tabs.TabSearch.WebUI.LoadCompletedTime',
  'Tabs.TabSearch.WebUI.LoadDocumentTime',
  'Tabs.TabSearch.WebUI.SearchAlgorithmDuration',
  'Tabs.TabSearch.WebUI.TabListDataReceived',
  'Tabs.TabSearch.WebUI.TabListDataReceived2',
  'Tabs.TabSearch.WebUI.TabListDataReceived2.IsOverlap',
  'Tabs.TabSearch.WebUI.TabSwitchAction',
  'Tabs.TabSearch.WindowDisplayedDuration2',
  'Tabs.TabSearch.WindowTimeToShowCachedWebView',
  'Tabs.TabSearch.WindowTimeToShowUncachedWebView',
]

TAB_SEARCH_CUSTOM_METRIC_NAMES = [
  'TabSearchPageHandler:GetProfileTabs',
  'TabSearchPageHandler:TabChangedAt',
]

TAB_SEARCH_URL = 'chrome://tab-search.top-chrome/'


class TabSearchStory(MultiTabStory):
  """Base class for tab search stories"""

  def RunPageInteractions(self, action_runner):
    SetMetricNames(action_runner, TAB_SEARCH_CUSTOM_METRIC_NAMES)
    self.OpenTabSearch()
    action_runner = Inspect(action_runner.tab.browser, TAB_SEARCH_URL)
    action_runner.ExecuteJavaScript(
      MEASURE_JS_MEMORY % 'tab_search:used_js_heap_size_begin'
    )
    self.InteractWithPage(action_runner)
    action_runner.ExecuteJavaScript(
      MEASURE_JS_MEMORY % 'tab_search:used_js_heap_size_end'
    )

  def OpenTabSearch(self, index=0):
    # Use keyboard shortcut to open tab search.
    # index is used to select which window to send the shortcut to.
    window_node_id = self._devtools.QueryNodes('<Window>')[index]
    PressKey(
      self._devtools, window_node_id, 'A', PLATFORM_ACCELERATOR | SHIFT_DOWN
    )

  def CloseTabSearch(self, index=0):
    # Use escape key to close tab search.
    # index is used to select which window to send the key to.
    window_node_id = self._devtools.QueryNodes('<Window>')[index]
    PressKey(self._devtools, window_node_id, 'Esc')

  def InteractWithPage(self, action_runner):
    self.ScrollTabs(action_runner)
    self.SearchTabs(action_runner)
    self.CloseTab(action_runner)

  def ScrollUp(self, action_runner):
    action_runner.Wait(1)
    # Scroll to the bottom of the list.
    action_runner.PressKey('ArrowUp')
    action_runner.Wait(1)

  def SearchTabs(self, action_runner):
    action_runner.Wait(1)
    action_runner.EnterText('o')
    action_runner.Wait(2)
    action_runner.PressKey('Backspace')
    action_runner.Wait(1)

  def CloseTab(self, action_runner):
    action_runner.Wait(1)
    # Tab to the close button of the 2nd tab.
    action_runner.PressKey('Tab', repeat_count=4, repeat_delay_ms=500)
    action_runner.PressKey(' ')
    action_runner.Wait(1)

  def ScrollTabs(self, action_runner):
    action_runner.Wait(1)
    self.StartMeasuringFrameTime(
      action_runner, 'tab_search:frame_time_on_scroll'
    )
    action_runner.ScrollElement(element_function=SCROLL_ELEMENT_FUNCTION)
    self.StopMeasuringFrameTime(action_runner)
    action_runner.Wait(1)

  def CloseAndOpen(self, action_runner):
    action_runner.Wait(1)
    self.CloseTabSearch()
    action_runner.Wait(1)
    self.OpenTabSearch()
    action_runner.Wait(5)

  def CloseAndOpenLoading(self, action_runner):
    action_runner.Wait(1)
    self.CloseTabSearch()
    action_runner.Wait(1)
    tabs = action_runner.tab.browser.tabs
    i = 0
    for url in self.URL_LIST2:
      tabs[i].Navigate(url)
      i = i + 1
    self.OpenTabSearch()
    action_runner.Wait(5)

  def ScrollUpAndDown(self, action_runner):
    action_runner.Wait(1)
    self.StartMeasuringFrameTime(
      action_runner, 'tab_search:frame_time_on_first_scroll_down'
    )
    action_runner.ScrollElement(element_function=SCROLL_ELEMENT_FUNCTION)
    self.StartMeasuringFrameTime(
      action_runner, 'tab_search:frame_time_on_first_scroll_up'
    )
    action_runner.ScrollElement(
      element_function=SCROLL_ELEMENT_FUNCTION, direction='up'
    )
    self.StartMeasuringFrameTime(
      action_runner, 'tab_search:frame_time_on_second_scroll_down'
    )
    action_runner.ScrollElement(element_function=SCROLL_ELEMENT_FUNCTION)
    self.StopMeasuringFrameTime(action_runner)
    action_runner.Wait(1)

  def WillStartTracing(self, chrome_trace_config):
    super(TabSearchStory, self).WillStartTracing(chrome_trace_config)
    chrome_trace_config.EnableUMAHistograms(*TAB_SEARCH_BENCHMARK_UMA)


class TabSearchRecentlyClosedStory(TabSearchStory):
  """Base class for tab search recently closed stories"""

  def RunNavigateSteps(self, action_runner):
    url_list = self.URL_LIST
    tabs = action_runner.tab.browser.tabs
    if len(url_list) > 0:
      tabs[0].Navigate(url_list[0])
    for url in url_list[1:]:
      new_tab = tabs.New()
      new_tab.Navigate(url)
      if self.WAIT_FOR_NETWORK_QUIESCENCE:
        try:
          new_tab.action_runner.WaitForNetworkQuiescence()
        except py_utils.TimeoutException:
          logging.warning('WaitForNetworkQuiescence() timeout: %s' % (url))
      new_tab.Close()
    self._devtools = action_runner.tab.browser.GetUIDevtools()

  def InteractWithPage(self, action_runner):
    self.SearchTabs(action_runner)
    self.ScrollTabs(action_runner)

  def SearchTabs(self, action_runner):
    action_runner.Wait(1)
    action_runner.EnterText('o')
    action_runner.Wait(2)


class TabSearchStoryRecentlyClosed10(TabSearchRecentlyClosedStory):
  NAME = 'tab_search:10_recently_closed'
  URL_LIST = ['chrome://version?q={}'.format(i) for i in range(10)]
  URL = URL_LIST[0]
  WAIT_FOR_NETWORK_QUIESCENCE = True


class TabSearchStoryRecentlyClosed50(TabSearchRecentlyClosedStory):
  NAME = 'tab_search:50_recently_closed'
  URL_LIST = ['chrome://version?q={}'.format(i) for i in range(50)]
  URL = URL_LIST[0]
  WAIT_FOR_NETWORK_QUIESCENCE = True


class TabSearchStoryRecentlyClosed100(TabSearchRecentlyClosedStory):
  NAME = 'tab_search:100_recently_closed'
  URL_LIST = ['chrome://version?q={}'.format(i) for i in range(100)]
  URL = URL_LIST[0]
  WAIT_FOR_NETWORK_QUIESCENCE = True


class TabSearchStoryTop10(TabSearchStory):
  NAME = 'tab_search:top10:2020'
  URL_LIST = TOP_URL[:10]
  URL = URL_LIST[0]
  WAIT_FOR_NETWORK_QUIESCENCE = True


class TabSearchStoryTop50(TabSearchStory):
  NAME = 'tab_search:top50:2020'
  URL_LIST = TOP_URL[:50]
  URL = URL_LIST[0]
  WAIT_FOR_NETWORK_QUIESCENCE = True


class TabSearchStoryTop10Loading(TabSearchStory):
  NAME = 'tab_search:top10:loading:2020'
  URL_LIST = TOP_URL[:10]
  URL = URL_LIST[0]
  WAIT_FOR_NETWORK_QUIESCENCE = False


class TabSearchStoryTop50Loading(TabSearchStory):
  NAME = 'tab_search:top50:loading:2020'
  URL_LIST = TOP_URL[:50]
  URL = URL_LIST[0]
  WAIT_FOR_NETWORK_QUIESCENCE = False


class TabSearchStoryCloseAndOpen(TabSearchStory):
  NAME = 'tab_search:close_and_open:2020'
  URL_LIST = TOP_URL[:10]
  URL = URL_LIST[0]
  WAIT_FOR_NETWORK_QUIESCENCE = True

  def InteractWithPage(self, action_runner):
    self.CloseAndOpen(action_runner)


class TabSearchStoryCloseAndOpenLoading(TabSearchStory):
  NAME = 'tab_search:close_and_open:loading:2020'
  URL_LIST = TOP_URL[:10]
  URL_LIST2 = TOP_URL[10:20]
  URL = URL_LIST[0]
  WAIT_FOR_NETWORK_QUIESCENCE = False

  def InteractWithPage(self, action_runner):
    self.CloseAndOpenLoading(action_runner)


class TabSearchStoryScrollUpAndDown(TabSearchStory):
  NAME = 'tab_search:scroll_up_and_down:2020'
  URL_LIST = TOP_URL[:50]
  URL = URL_LIST[0]
  WAIT_FOR_NETWORK_QUIESCENCE = False

  def InteractWithPage(self, action_runner):
    self.ScrollUpAndDown(action_runner)


class TabSearchStoryCleanSlate(TabSearchStory):
  NAME = 'tab_search:clean_slate'
  URL_LIST = []
  URL = 'about:blank'
  WAIT_FOR_NETWORK_QUIESCENCE = False
  TAGS = [story_tags.SMOKE_TEST]

  def InteractWithPage(self, action_runner):
    action_runner.Wait(1)


class TabSearchStoryMeasureMemory(TabSearchStory):
  URL_LIST = []
  URL = 'about:blank'
  WAIT_FOR_NETWORK_QUIESCENCE = False

  def WillStartTracing(self, chrome_trace_config):
    super(TabSearchStoryMeasureMemory, self).WillStartTracing(
      chrome_trace_config
    )
    chrome_trace_config.category_filter.AddExcludedCategory('*')
    chrome_trace_config.category_filter.AddIncludedCategory('blink.console')
    chrome_trace_config.category_filter.AddDisabledByDefault(
      'disabled-by-default-memory-infra'
    )

  def GetExtraTracingMetrics(self):
    return super(TabSearchStoryMeasureMemory, self).GetExtraTracingMetrics() + [
      'memoryMetric'
    ]


class TabSearchStoryMeasureMemoryBefore(TabSearchStoryMeasureMemory):
  NAME = 'tab_search:measure_memory:before'

  def RunNavigateSteps(self, action_runner):
    super(TabSearchStoryMeasureMemoryBefore, self).RunNavigateSteps(
      action_runner
    )
    action_runner.MeasureMemory(deterministic_mode=True)

  def InteractWithPage(self, action_runner):
    action_runner.Wait(1)


class TabSearchStoryMeasureMemoryAfter(TabSearchStoryMeasureMemory):
  NAME = 'tab_search:measure_memory:after'

  def InteractWithPage(self, action_runner):
    action_runner.MeasureMemory(deterministic_mode=True)


class TabSearchStoryMeasureMemoryMultiwindow(TabSearchStoryMeasureMemory):
  NAME = 'tab_search:measure_memory:multiwindow'

  def InteractWithPage(self, action_runner):
    action_runner.Wait(2)
    tabs = action_runner.tab.browser.tabs
    tabs.New(in_new_window=True)
    self.OpenTabSearch(index=1)  # Open the tab search bubble in the 2nd window.
    action_runner.Wait(2)
    action_runner.MeasureMemory(deterministic_mode=True)


class TabSearchStoryMeasureMemory2TabSearch(TabSearchStoryMeasureMemory):
  NAME = 'tab_search:measure_memory:2tab_search'

  def RunNavigateSteps(self, action_runner):
    super(TabSearchStoryMeasureMemory2TabSearch, self).RunNavigateSteps(
      action_runner
    )
    tabs = action_runner.tab.browser.tabs
    new_tab = tabs.New()
    new_tab.Navigate(TAB_SEARCH_URL)
    new_tab.WaitForDocumentReadyStateToBeComplete()
    new_tab.action_runner.ExecuteJavaScript(
      MEASURE_JS_MEMORY % 'tab_search:used_js_heap_size2'
    )

  def InteractWithPage(self, action_runner):
    action_runner.MeasureMemory(deterministic_mode=True)


class TabSearchStoryMeasureMemory3TabSearch(TabSearchStoryMeasureMemory):
  NAME = 'tab_search:measure_memory:3tab_search'

  def RunNavigateSteps(self, action_runner):
    super(TabSearchStoryMeasureMemory3TabSearch, self).RunNavigateSteps(
      action_runner
    )
    tabs = action_runner.tab.browser.tabs
    for i in range(2):
      new_tab = tabs.New()
      new_tab.Navigate(TAB_SEARCH_URL)
      new_tab.WaitForDocumentReadyStateToBeComplete()
      new_tab.action_runner.ExecuteJavaScript(
        MEASURE_JS_MEMORY % ('tab_search:used_js_heap_size' + str(i + 2))
      )

  def InteractWithPage(self, action_runner):
    action_runner.MeasureMemory(deterministic_mode=True)


SCROLL_ELEMENT_FUNCTION = '''
document.querySelector('tab-search-app').shadowRoot
        .querySelector('tab-search-page').shadowRoot
        .getElementById('tabsList')
'''
