# Copyright 2011 WebDriver committers
# Copyright 2011 Google Inc.
#
# Licensed 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.

"""The ActionChains implementation."""
from selenium.webdriver.remote.command import Command

class ActionChains(object):
    """Generate user actions.
    All actions are stored in the ActionChains object. Call perform() to fire
    stored actions."""

    def __init__(self, driver):
        """Creates a new ActionChains.
        Args:
            driver: The WebDriver instance which performs user actions.
        """
        self._driver = driver
        self._actions = []

    def perform(self):
        """Performs all stored actions."""
        for action in self._actions:
            action()

    def click(self, on_element=None):
        """Clicks an element.
        Args:
            on_element: The element to click.
                        If None, clicks on current mouse position.
        """
        if on_element: self.move_to_element(on_element)
        self._actions.append(lambda:
            self._driver.execute(Command.CLICK, {'button': 0}))
        return self

    def click_and_hold(self, on_element):
        """Holds down the left mouse button on an element.
        Args:
            on_element: The element to mouse down.
                        If None, clicks on current mouse position.
        """
        if on_element: self.move_to_element(on_element)
        self._actions.append(lambda:
            self._driver.execute(Command.MOUSE_DOWN, {}))
        return self

    def context_click(self, on_element):
        """Performs a context-click (right click) on an element.
        Args:
            on_element: The element to context-click.
                        If None, clicks on current mouse position.
        """
        if on_element: self.move_to_element(on_element)
        self._actions.append(lambda:
            self._driver.execute(Command.CLICK, {'button': 2}))
        return self

    def double_click(self, on_element):
        """Double-clicks an element.
        Args:
            on_element: The element to double-click.
                        If None, clicks on current mouse position.
        """
        if on_element: self.move_to_element(on_element)
        self._actions.append(lambda:
            self._driver.execute(Command.DOUBLE_CLICK, {}))
        return self

    def drag_and_drop(self, source, target):
        """Holds down the left mouse button on the source element,
           then moves to the target element and releases the mouse button.
        Args:
            source: The element to mouse down.
            target: The element to mouse up.
        """
        self.click_and_hold(source)
        self.release(target)
        return self

    def drag_and_drop_by_offset(self, source, xoffset, yoffset):
        """Holds down the left mouse button on the source element,
           then moves to the target element and releases the mouse button.
        Args:
            source: The element to mouse down.
            xoffset: X offset to move to.
            yoffset: Y offset to move to.
        """
        self.click_and_hold(source)
        self.move_by_offset(xoffset, yoffset)
        self.release(source)
        return self

    def key_down(self, key, element=None):
        """Sends a key press only, without releasing it.
        Should only be used with modifier keys (Control, Alt and Shift).
        Args:
            key: The modifier key to send. Values are defined in Keys class.
            target: The element to send keys.
                    If None, sends a key to current focused element.
        """
        if element: self.click(element)
        self._actions.append(lambda:
            self._driver.execute(Command.SEND_MODIFIER_KEY_TO_ACTIVE_ELEMENT, {
                "value": key,
                "isdown": True}))
        return self

    def key_up(self, key, element=None):
        """Releases a modifier key.
        Args:
            key: The modifier key to send. Values are defined in Keys class.
            target: The element to send keys.
                    If None, sends a key to current focused element.
        """
        if element: self.click(element)
        self._actions.append(lambda:
            self._driver.execute(Command.SEND_MODIFIER_KEY_TO_ACTIVE_ELEMENT, {
                "value": key,
                "isdown": False}))
        return self

    def move_by_offset(self, xoffset, yoffset):
        """Moving the mouse to an offset from current mouse position.
        Args:
            xoffset: X offset to move to.
            yoffset: Y offset to move to.
        """
        self._actions.append(lambda:
            self._driver.execute(Command.MOVE_TO, {
                'xoffset': xoffset,
                'yoffset': yoffset}))
        return self

    def move_to_element(self, to_element):
        """Moving the mouse to the middle of an element.
        Args:
            to_element: The element to move to.
        """
        self._actions.append(lambda:
            self._driver.execute(Command.MOVE_TO, {'element': to_element.id}))
        return self

    def move_to_element_with_offset(self, to_element, xoffset, yoffset):
        """Move the mouse by an offset of the specificed element.
        Offsets are relative to the top-left corner of the element.
        Args:
            to_element: The element to move to.
            xoffset: X offset to move to.
            yoffset: Y offset to move to.
        """
        self._actions.append(lambda:
            self._driver.execute(Command.MOVE_TO, {
                'element': to_element.id,
                'xoffset': xoffset,
                'yoffset': yoffset}))
        return self

    def release(self, on_element):
        """Releasing a held mouse button.
        Args:
            on_element: The element to mouse up.
        """
        if on_element: self.move_to_element(on_element)
        self._actions.append(lambda:
            self._driver.execute(Command.MOUSE_UP, {}))
        return self

    def send_keys(self, *keys_to_send):
        """Sends keys to current focused element.
        Args:
            keys_to_send: The keys to send.
        """
        self._actions.append(lambda:
            self._driver.switch_to_active_element().send_keys(*keys_to_send))
        return self

    def send_keys_to_element(self, element, *keys_to_send):
        """Sends keys to an element.
        Args:
            element: The element to send keys.
            keys_to_send: The keys to send.
        """
        self._actions.append(lambda:
            element.send_keys(*keys_to_send))
        return self
