# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

import pytest
import time
import unittest
from selenium.common.exceptions import TimeoutException
from selenium.common.exceptions import StaleElementReferenceException
from selenium.common.exceptions import WebDriverException
from selenium.common.exceptions import InvalidElementStateException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait


def not_available_on_remote(func):
    def testMethod(self):
        print(self.driver)
        if type(self.driver) == 'remote':
            return lambda x: None
        else:
            return func(self)
    return testMethod


def throwSERE(driver):
    raise StaleElementReferenceException("test")


class WebDriverWaitTest(unittest.TestCase):

    def testShouldExplicitlyWaitForASingleElement(self):
        self._loadPage("dynamic")
        add = self.driver.find_element_by_id("adder")
        add.click()
        WebDriverWait(self.driver, 3).until(EC.presence_of_element_located((By.ID, "box0")))  # All is well if this doesn't throw.

    def testShouldStillFailToFindAnElementWithExplicitWait(self):
        self._loadPage("dynamic")
        try:
            WebDriverWait(self.driver, 0.7).until(EC.presence_of_element_located((By.ID, "box0")))
            self.fail("Expected TimeoutException to have been thrown")
        except TimeoutException:
            pass
        except Exception as e:
            self.fail("Expected TimeoutException but got " + str(e))

    def testShouldExplicitlyWaituntilAtLeastOneElementIsFoundWhenSearchingForMany(self):
        self._loadPage("dynamic")
        add = self.driver.find_element_by_id("adder")

        add.click()
        add.click()

        elements = WebDriverWait(self.driver, 2).until(EC.presence_of_all_elements_located((By.CLASS_NAME, "redbox")))
        self.assertTrue(len(elements) >= 1)

    def testShouldFailToFindElementsWhenExplicitWaiting(self):
        self._loadPage("dynamic")
        with self.assertRaises(TimeoutException):
            WebDriverWait(self.driver, 0.7).until(EC.presence_of_all_elements_located((By.CLASS_NAME, "redbox")))

    def testShouldWaitUntilAtLeastOneVisibleElementsIsFoundWhenSearchingForMany(self):
        self._loadPage("hidden_partially")
        add_visible = self.driver.find_element_by_id("addVisible")
        add_hidden = self.driver.find_element_by_id("addHidden")

        add_visible.click()
        add_visible.click()
        add_hidden.click()

        elements = WebDriverWait(self.driver, 2).until(EC.visibility_of_any_elements_located((By.CLASS_NAME, "redbox")))
        self.assertTrue(len(elements) == 2)

    def testShouldFailToFindVisibleElementsWhenExplicitWaiting(self):
        self._loadPage("hidden_partially")
        with self.assertRaises(TimeoutException):
            WebDriverWait(self.driver, 0.7).until(EC.visibility_of_any_elements_located((By.CLASS_NAME, "redbox")))

    def testShouldWaitOnlyAsLongAsTimeoutSpecifiedWhenImplicitWaitsAreSet(self):
        self._loadPage("dynamic")
        self.driver.implicitly_wait(0.5)
        try:
            start = time.time()
            try:
                WebDriverWait(self.driver, 1).until(EC.presence_of_element_located((By.ID, "box0")))
                self.fail("Expected TimeoutException to have been thrown")
            except TimeoutException:
                pass
            self.assertTrue(time.time() - start < 1.5,
                            "Expected to take just over 1 second to execute, but took %f" %
                            (time.time() - start))
        finally:
            self.driver.implicitly_wait(0)

    def testShouldWaitAtLeastOnce(self):
        self._loadPage("simpleTest")
        elements = WebDriverWait(self.driver, 0).until(lambda d: d.find_elements_by_tag_name('h1'))
        self.assertTrue(len(elements) >= 1)

    def testWaitUntilNotReturnsIfEvaluatesToFalse(self):
        self.assertFalse(WebDriverWait(self.driver, 1).until_not(lambda d: False))

    def testWaitShouldStillFailIfProduceIgnoredException(self):
        ignored = (InvalidElementStateException, StaleElementReferenceException)
        try:
            WebDriverWait(self.driver, 1, 0.7, ignored_exceptions=ignored).until(throwSERE)
            self.fail("Expected TimeoutException to have been thrown")
        except TimeoutException:
            pass

    def testWaitShouldStillFailIfProduceChildOfIgnoredException(self):
        ignored = (WebDriverException)
        try:
            WebDriverWait(self.driver, 1, 0.7, ignored_exceptions=ignored).until(throwSERE)