# Copyright 2020 Google LLC
#
# 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.

"""Implementation of the switch_power_usb_default capability."""
from gazoo_device import decorators
from gazoo_device import errors
from gazoo_device import gdm_logger
from gazoo_device.capabilities.interfaces import switch_power_base

logger = gdm_logger.get_gdm_logger()

SYNC = "sync"
OFF = "off"


class SwitchPowerUsbDefault(switch_power_base.SwitchPowerBase):
    """Definition of the switch_power_usb_default capability."""
    _REQUIRED_COMMANDS = ["POWER_ON", "POWER_OFF", "GET_MODE"]
    _REQUIRED_REGEXS = ["GET_MODE_REGEX"]

    def __init__(self,
                 shell_fn,
                 regex_shell_fn,
                 command_dict,
                 regex_dict,
                 device_name,
                 serial_number,
                 total_ports):
        """Create an instance of the default hub power capability.

        Args:
            shell_fn (func): Function for calling shell on the device.
            regex_shell_fn (func): Function for calling shell_with_regex on the device.
            command_dict (dict): A dictionary containing the command used for each method.
            regex_dict (dict): A dictionary containing the response regex to use to
                               determine the return value for the property.
            device_name (str): name of the device this capability is attached to.
            serial_number (str): serial number of device this capability is attached to.
            total_ports (int): number of ports on device
        """
        super().__init__(device_name=device_name)
        self._shell_fn = shell_fn
        self._regex_shell_fn = regex_shell_fn
        self._command_dict = command_dict
        self._regex_dict = regex_dict
        self._serial_number = serial_number
        self._total_ports = total_ports

        self.validate_required_keys(self._REQUIRED_COMMANDS, command_dict, "command_dict")
        self.validate_required_keys(self._REQUIRED_REGEXS, regex_dict, "regex_dict")

    @decorators.PersistentProperty
    def supported_modes(self):
        """Get the USB power modes supported by the USB hub."""
        return [OFF, SYNC]

    def get_all_ports_mode(self):
        """Gets the USB mode for all ports on this hub.

        Returns:
          list: Returns a list of port modes with port number as index.
        """
        mode_list = ['']
        for port in range(1, self._total_ports + 1):
            mode_list.append(self.get_mode(port=port))
        return mode_list

    def get_mode(self, port):
        """Get the USB mode for specified port.

        Args:
          port (int): The port to get mode.

        Returns:
            str: USB port mode settings 'off', 'sync'

        Raises:
          GazooDeviceError: invalid port.
        """
        port = int(port)
        self._validate_port("get_mode", port)
        cmd = self._command_dict["GET_MODE"].format(self._serial_number, port)
        result = self._regex_shell_fn(command=cmd,
                                      regex=self._regex_dict["GET_MODE_REGEX"],
                                      command_name="get_mode",
                                      tries=5)
        if "ON" in result.upper():
            return SYNC
        return OFF

    @decorators.CapabilityLogDecorator(logger)
    def power_on(self, port, data_sync=True):
        """This command powers on the port specified.

        Args:
            port (int): identifying which hub port to power on
            data_sync (bool): Ignored in default implementation

        Raises:
           GazooDeviceError: if data_sync is False or if a valid port is not provided.
        """
        port = int(port)
        if not data_sync:
            raise errors.GazooDeviceError(
                "Device {} {} power_on failed. "
                "data_sync=False not supported in default implementation.".
                format(self._device_name, self._serial_number))

        self._validate_port("power_on", port)
        logger.debug("{} Powering on usb port {}".format(self._device_name, port))
        self._shell_fn(self._command_dict["POWER_ON"].format(self._serial_number, port))

    @decorators.CapabilityLogDecorator(logger)
    def power_off(self, port):
        """This command powers off the port specified.

        Args:
            port (int): identifies which hub port to power off

        Raises:
           GazooDeviceError: if operation failed or if a valid port is not provided.
        """
        port = int(port)
        self._validate_port("power_off", port)
        logger.debug("{} Powering off usb port {}".format(self._device_name, port))
        self._shell_fn(self._command_dict["POWER_OFF"].format(self._serial_number, port))

    @decorators.CapabilityLogDecorator(logger)
    def set_all_ports_mode(self, mode):
        """Sets all USB hub ports to the mode specified.

        Args:
          mode (str): USB hub mode to set. The mode must be in one of the supported_modes
                      Example, 'off', 'sync'

        Raises:
          GazooDeviceError: invalid mode.
        """
        self._validate_mode(mode)
        for port in range(1, self._total_ports + 1):
            self.set_mode(mode=mode, port=port)

    @decorators.CapabilityLogDecorator(logger)
    def set_mode(self, mode, port):
        """Sets the specified USB port to the mode specified.

        Args:
          mode (str): USB mode to set. Options: 'off', 'sync'
          port (int): The port to set.

        Raises:
          GazooDeviceError: invalid mode or port.
        """
        port = int(port)
        mode = mode.lower()
        self._validate_port("set_mode", port)
        self._validate_mode(mode)

        if mode in OFF:
            self.power_off(port)
        else:  # "sync"
            self.power_on(port)

    def _validate_mode(self, mode):
        """Verify mode given resides in the valid mode list.

        Args:
            mode (str): mode e.g. 'off', 'sync'

        Raises:
            GazooDeviceError: if mode given is not found in the valid mode list.
        """
        if mode not in list(self.supported_modes):
            raise errors.GazooDeviceError("Mode {} not found in supported modes {!r}".
                                          format(mode,
                                                 self.supported_modes))

    def _validate_port(self, method_name, port):
        """Verify port given is within the bounds defined.

        Args:
            method_name (str): name of method requesting validation.
            port (int): value provided must be >= 1 and <= _total_ports.

        Raises:
            GazooDeviceError: if port given is not within the bounds defined.
        """
        if not port or port > self._total_ports:
            raise errors.GazooDeviceError("Device {} {} failed. "
                                          "Port {} is invalid.".
                                          format(self._device_name, method_name, port))
