# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import firefox_puppeteer.errors as errors

from firefox_puppeteer import PuppeteerMixin
from firefox_puppeteer.ui.windows import BaseWindow
from marionette_driver import By, Wait
from marionette_driver.errors import NoSuchWindowException
from marionette_harness import MarionetteTestCase


class BaseWindowTestCase(PuppeteerMixin, MarionetteTestCase):

    def setUp(self):
        """
        These tests open and close windows pretty rapidly, which
        (since bug 1261842) can cause content processes to be
        spawned and discarded in large numbers. By default, Firefox
        has a 5 second timeout for shutting down content processes,
        but we can get into cases where the content process just
        doesn't have enough time to get itself all sorted before
        the timeout gets hit, which results in the parent killing
        the content process manually, which generates a crash report,
        which causes these tests to orange. We side-step this by
        setting dom.ipc.tabs.shutdownTimeoutSecs to 0, which disables
        the shutdown timer.
        """
        super(BaseWindowTestCase, self).setUp()

        self.marionette.set_pref('dom.ipc.tabs.shutdownTimeoutSecs', 0)

    def tearDown(self):
        try:
            self.marionette.clear_pref('dom.ipc.tabs.shutdownTimeoutSecs')
        finally:
            super(BaseWindowTestCase, self).tearDown()


class TestWindows(BaseWindowTestCase):

    def tearDown(self):
        try:
            self.puppeteer.windows.close_all([self.browser])
        finally:
            super(TestWindows, self).tearDown()

    def test_switch_to(self):
        url = self.marionette.absolute_url('layout/mozilla.html')

        # Open two more windows
        for index in range(0, 2):
            self.marionette.execute_script(""" window.open(); """)

        windows = self.puppeteer.windows.all
        self.assertEquals(len(windows), 3)

        # Switch to the 2nd window
        self.puppeteer.windows.switch_to(windows[1].handle)
        self.assertEquals(windows[1].handle, self.marionette.current_chrome_window_handle)

        # TODO: Needs updated tabs module for improved navigation
        with self.marionette.using_context('content'):
            self.marionette.navigate(url)

        # Switch to the last window and find 2nd window by URL
        self.puppeteer.windows.switch_to(windows[2].handle)

        # TODO: A window can have multiple tabs, so this may need an update
        # when the tabs module gets implemented
        def find_by_url(win):
            with win.marionette.using_context('content'):
                return win.marionette.get_url() == url

        self.puppeteer.windows.switch_to(find_by_url)
        self.assertEquals(windows[1].handle, self.marionette.current_chrome_window_handle)

        self.puppeteer.windows.switch_to(find_by_url)

        # Switching to an unknown handles has to fail
        self.assertRaises(NoSuchWindowException,
                          self.puppeteer.windows.switch_to, "humbug")
        self.assertRaises(NoSuchWindowException,
                          self.puppeteer.windows.switch_to, lambda win: False)

        self.puppeteer.windows.close_all([self.browser])
        self.browser.switch_to()

        self.assertEqual(len(self.puppeteer.windows.all), 1)

    def test_switch_to_unknown_window_type(self):
        def open_by_js(_):
            with self.marionette.using_context('chrome'):
                self.marionette.execute_script("""
                  window.open('chrome://browser/content/safeMode.xul', '_blank',
                              'chrome,centerscreen,resizable=no');
                """)

        win = self.browser.open_window(callback=open_by_js, expected_window_class=BaseWindow)
        win.close()
        self.browser.switch_to()


class TestBaseWindow(BaseWindowTestCase):

    def tearDown(self):
        try:
            self.puppeteer.windows.close_all([self.browser])
        finally:
            BaseWindowTestCase.tearDown(self)

    def test_basics(self):
        # force BaseWindow instance
        win1 = BaseWindow(self.marionette, self.browser.handle)

        self.assertEquals(win1.handle, self.marionette.current_chrome_window_handle)
        self.assertEquals(win1.window_element,
                          self.marionette.find_element(By.CSS_SELECTOR, ':root'))
        self.assertEquals(win1.window_element.get_attribute('windowtype'),
                          self.marionette.get_window_type())
        self.assertFalse(win1.closed)

        # Test invalid parameters for BaseWindow constructor
        self.assertRaises(errors.UnknownWindowError,
                          BaseWindow, self.marionette, 10)

        # Test invalid shortcuts
        self.assertRaises(KeyError,
                          win1.send_shortcut, 'l', acel=True)

    def test_open_close(self):
        # force BaseWindow instance
        win1 = BaseWindow(self.marionette, self.browser.handle)

        # Open a new window (will be focused), and check states
        win2 = win1.open_window()

        # force BaseWindow instance
        win2 = BaseWindow(self.marionette, win2.handle)

        self.assertEquals(len(self.marionette.chrome_window_handles), 2)
        self.assertNotEquals(win1.handle, win2.handle)
        self.assertEquals(win2.handle, self.marionette.current_chrome_window_handle)

        win2.close()

        self.assertTrue(win2.closed)
        self.assertEquals(len(self.marionette.chrome_window_handles), 1)
        self.assertEquals(win2.handle, self.marionette.current_chrome_window_handle)
        Wait(self.marionette).until(lambda _: win1.focused)  # catch the no focused window

        win1.focus()

        # Open and close a new window by a custom callback
        def opener(window):
            window.marionette.execute_script(""" window.open(); """)

        def closer(window):
            window.marionette.execute_script(""" window.close(); """)

        win2 = win1.open_window(callback=opener)

        # force BaseWindow instance
        win2 = BaseWindow(self.marionette, win2.handle)

        self.assertEquals(len(self.marionette.chrome_window_handles), 2)
        win2.close(callback=closer)

        win1.focus()

        # Check for an unexpected window class
        self.assertRaises(errors.UnexpectedWindowTypeError,
                          win1.open_window, expected_window_class=BaseWindow)
        self.puppeteer.windows.close_all([win1])

    def test_switch_to_and_focus(self):
        # force BaseWindow instance
        win1 = BaseWindow(self.marionette, self.browser.handle)

        # Open a new window (will be focused), and check states
        win2 = win1.open_window()

        # force BaseWindow instance
        win2 = BaseWindow(self.marionette, win2.handle)

        self.assertEquals(win2.handle, self.marionette.current_chrome_window_handle)
        self.assertEquals(win2.handle, self.puppeteer.windows.focused_chrome_window_handle)
        self.assertFalse(win1.focused)
        self.assertTrue(win2.focused)

        # Switch back to win1 without moving the focus, but focus separately
        win1.switch_to()
        self.assertEquals(win1.handle, self.marionette.current_chrome_window_handle)
        self.assertTrue(win2.focused)

        win1.focus()
        self.assertTrue(win1.focused)

        # Switch back to win2 by focusing it directly
        win2.focus()
        self.assertEquals(win2.handle, self.marionette.current_chrome_window_handle)
        self.assertEquals(win2.handle, self.puppeteer.windows.focused_chrome_window_handle)
        self.assertTrue(win2.focused)

        # Close win2, and check that it keeps active but looses focus
        win2.switch_to()
        win2.close()

        win1.switch_to()


class TestBrowserWindow(BaseWindowTestCase):

    def tearDown(self):
        try:
            self.puppeteer.windows.close_all([self.browser])
        finally:
            BaseWindowTestCase.tearDown(self)

    def test_basic(self):
        self.assertNotEqual(self.browser.dtds, [])
        self.assertNotEqual(self.browser.properties, [])

        self.assertFalse(self.browser.is_private)

        self.assertIsNotNone(self.browser.menubar)
        self.assertIsNotNone(self.browser.navbar)
        self.assertIsNotNone(self.browser.tabbar)

    def test_open_close(self):
        # open and close a new browser windows by menu
        win2 = self.browser.open_browser(trigger='menu')
        self.assertEquals(win2, self.puppeteer.windows.current)
        self.assertFalse(self.browser.is_private)
        win2.close(trigger='menu')

        # open and close a new browser window by shortcut
        win2 = self.browser.open_browser(trigger='shortcut')
        self.assertEquals(win2, self.puppeteer.windows.current)
        self.assertFalse(self.browser.is_private)
        win2.close(trigger='shortcut')

        # open and close a new private browsing window
        win2 = self.browser.open_browser(is_private=True)
        self.assertEquals(win2, self.puppeteer.windows.current)
        self.assertTrue(win2.is_private)
        win2.close()

        # open and close a new private browsing window
        win2 = self.browser.open_browser(trigger='shortcut', is_private=True)
        self.assertEquals(win2, self.puppeteer.windows.current)
        self.assertTrue(win2.is_private)
        win2.close()

        # force closing a window
        win2 = self.browser.open_browser()
        self.assertEquals(win2, self.puppeteer.windows.current)
        win2.close(force=True)