"""This module handles Misty's movement."""
from typing import Dict

from misty2py.robot import Misty


class Movement:
    """Handles Misty's movement. All angles are in degrees as integers."""

    def __init__(
        self,
        max_velocity=100,
        min_velocity=1,
        stagnant_velocity=0,
        max_angle=100,
        min_angle=1,
        stagnant_angle=0,
    ) -> None:
        """Initialises the Movement class.

        Args:
            max_velocity (int, optional): The maximal allowed velocity. Defaults to `100`.
            min_velocity (int, optional): The minimal allowed velocity that is not stagnant. Defaults to `1`.
            stagnant_velocity (int, optional): The stagnant velocity. Defaults to `0`.
            max_angle (int, optional): The maximum turning angle. Defaults to `100`.
            min_angle (int, optional): The minimum turning angle that is not equal to the straight line driving. Defaults to `1`.
            stagnant_angle (int, optional): The angle equal to the straight line driving. Defaults to `0`.
        """
        self.max_velocity = max_velocity
        self.min_velocity = min_velocity
        self.stagnant_velocity = stagnant_velocity
        self.max_angle = max_angle
        self.min_angle = min_angle
        self.stagnant_angle = stagnant_angle

    def _parse_value(
        self,
        value: int,
        max: int,
        min: int,
        negative: bool = False,
        negatives_allowed: bool = False,
    ) -> int:
        """Parses a velocity or angle value based on the minimum / maximum
        values and the stagnant values.

        Args:
            value (int): the input value.
            max (int): the maximal allowed value.
            min (int): the minimal allowed value.
            negative (bool, optional): Whether the output value should be its negative (if the input is positive) or remain negative/stagnant if the input is not positive. Defaults to `False`.
            negatives_allowed (bool, optional): Whether an input value can be negative. Defaults to `False`.

        Returns:
            int: the input value so that it is smaller or equal to the maximum value and higher or equal to the minimum value if negatives are not allowed; transformed to its negative if `negative` is `True` and higher or equal to the negative of the maximal value and lower or equal to the maximal value if negatives are allowed.
        """
        if negatives_allowed:
            if value > max:
                return max
            if value < max * (-1):
                return max * (-1)
            return value

        if negative:
            if value > max:
                # velocity over max
                return max * (-1)
            if value > min:
                # velocity over min below max
                return value * (-1)
            if value > max * (-1):
                # velocity below min, over negative max
                return value
            # velocity below negative max
            return max * (-1)

        if value < min:
            return min
        if value > max:
            return max
        return value

    def _parse_velocity(
        self, velocity: int, negative: bool = False, both: bool = False
    ) -> int:
        """Parses the velocity.

        Args:
            velocity (int): the input value.
            negative (bool, optional): Whether the input value should
            be transformed into its negative. Defaults to `False`.
            both (bool, optional): Whether an input value can be negative.
            Defaults to `False`.

        Returns:
            int: the input value so that it is smaller or equal to the maximum value and higher or equal to the minimum value if negatives are not allowed; transformed to its negative if `negative` is `True` and higher or equal to the negative of the maximal value and lower or equal to the maximal value if negatives are allowed.
        """
        return self._parse_value(
            velocity,
            self.max_velocity,
            self.min_velocity,
            negative=negative,
            negatives_allowed=both,
        )

    def _parse_angle(
        self, angle: int, negative: bool = False, both: bool = False
    ) -> int:
        """Parses the angle.

        Args:
            angle (int): the input value.
            negative (bool, optional): Whether the input value should be transformed into its negative. Defaults to `False`.
            both (bool, optional): Whether an input value can be negative. Defaults to `False`.

        Returns:
            int: the input value so that it is smaller or equal to the maximum value and higher or equal to the minimum value if negatives are not allowed; transformed to its negative if `negative` is `True` and higher or equal to the negative of the maximal value and lower or equal to the maximal value if negatives are allowed.
        """
        return self._parse_value(
            angle,
            self.max_angle,
            self.min_angle,
            negative=negative,
            negatives_allowed=both,
        )

    def drive_forward(self, misty: Misty, velocity: int) -> Dict:
        """Drives forwards with the specified velocity."""
        forw = {
            "LinearVelocity": self._parse_velocity(velocity),
            "AngularVelocity": self.stagnant_angle,
        }
        return misty.perform_action("drive", data=forw).parse_to_dict()

    def drive_backward(self, misty: Misty, velocity: int):
        """Drives backwards with the specified velocity."""
        back = {
            "LinearVelocity": self._parse_velocity(velocity, negative=True),
            "AngularVelocity": self.stagnant_angle,
        }
        return misty.perform_action("drive", data=back).parse_to_dict()

    def drive_left(self, misty: Misty, velocity: int, angle: int):
        """Drives left with the specified velocity and angle."""
        left = {
            "LinearVelocity": self._parse_velocity(velocity),
            "AngularVelocity": self._parse_angle(angle),
        }
        return misty.perform_action("drive", data=left).parse_to_dict()

    def drive_right(self, misty: Misty, velocity: int, angle: int):
        """Drives right with the specified velocity and angle."""
        velocity = self._parse_velocity(velocity)
        right = {
            "LinearVelocity": self._parse_velocity(velocity),
            "AngularVelocity": self._parse_angle(angle, negative=True),
        }
        return misty.perform_action("drive", data=right).parse_to_dict()

    def stop_driving(self, misty: Misty):
        """Stops driving."""
        return misty.perform_action("drive_stop").parse_to_dict()

"""This module handles Misty's movement."""
from typing import Dict

from misty2py.robot import Misty


class Movement:
    """Handles Misty's movement. All angles are in degrees as integers."""

    def __init__(
        self,
        max_velocity=100,
        min_velocity=1,
        stagnant_velocity=0,
        max_angle=100,
        min_angle=1,
        stagnant_angle=0,
    ) -> None:
        """Initialises the Movement class.

        Args:
            max_velocity (int, optional): The maximal allowed velocity. Defaults to `100`.
            min_velocity (int, optional): The minimal allowed velocity that is not stagnant. Defaults to `1`.
            stagnant_velocity (int, optional): The stagnant velocity. Defaults to `0`.
            max_angle (int, optional): The maximum turning angle. Defaults to `100`.
            min_angle (int, optional): The minimum turning angle that is not equal to the straight line driving. Defaults to `1`.
            stagnant_angle (int, optional): The angle equal to the straight line driving. Defaults to `0`.
        """
        self.max_velocity = max_velocity
        self.min_velocity = min_velocity
        self.stagnant_velocity = stagnant_velocity
        self.max_angle = max_angle
        self.min_angle = min_angle
        self.stagnant_angle = stagnant_angle

    def _parse_value(
        self,
        value: int,
        max: int,
        min: int,
        negative: bool = False,
        negatives_allowed: bool = False,
    ) -> int:
        """Parses a velocity or angle value based on the minimum / maximum
        values and the stagnant values.

        Args:
            value (int): the input value.
            max (int): the maximal allowed value.
            min (int): the minimal allowed value.
            negative (bool, optional): Whether the output value should be its negative (if the input is positive) or remain negative/stagnant if the input is not positive. Defaults to `False`.
            negatives_allowed (bool, optional): Whether an input value can be negative. Defaults to `False`.

        Returns:
            int: the input value so that it is smaller or equal to the maximum value and higher or equal to the minimum value if negatives are not allowed; transformed to its negative if `negative` is `True` and higher or equal to the negative of the maximal value and lower or equal to the maximal value if negatives are allowed.
        """
        if negatives_allowed:
            if value > max:
                return max
            if value < max * (-1):
                return max * (-1)
            return value

        if negative:
            if value > max:
                # velocity over max
                return max * (-1)
            if value > min:
                # velocity over min below max
                return value * (-1)
            if value > max * (-1):
                # velocity below min, over negative max
                return value
            # velocity below negative max
            return max * (-1)

        if value < min:
            return min
        if value > max:
            return max
        return value

    def _parse_velocity(
        self, velocity: int, negative: bool = False, both: bool = False
    ) -> int:
        """Parses the velocity.

        Args:
            velocity (int): the input value.
            negative (bool, optional): Whether the input value should
            be transformed into its negative. Defaults to `False`.
            both (bool, optional): Whether an input value can be negative.
            Defaults to `False`.

        Returns:
            int: the input value so that it is smaller or equal to the maximum value and higher or equal to the minimum value if negatives are not allowed; transformed to its negative if `negative` is `True` and higher or equal to the negative of the maximal value and lower or equal to the maximal value if negatives are allowed.
        """
        return self._parse_value(
            velocity,
            self.max_velocity,
            self.min_velocity,
            negative=negative,
            negatives_allowed=both,
        )

    def _parse_angle(
        self, angle: int, negative: bool = False, both: bool = False
    ) -> int:
        """Parses the angle.

        Args:
            angle (int): the input value.
            negative (bool, optional): Whether the input value should be transformed into its negative. Defaults to `False`.
            both (bool, optional): Whether an input value can be negative. Defaults to `False`.

        Returns:
            int: the input value so that it is smaller or equal to the maximum value and higher or equal to the minimum value if negatives are not allowed; transformed to its negative if `negative` is `True` and higher or equal to the negative of the maximal value and lower or equal to the maximal value if negatives are allowed.
        """
        return self._parse_value(
            angle,
            self.max_angle,
            self.min_angle,
            negative=negative,
            negatives_allowed=both,
        )

    def drive_forward(self, misty: Misty, velocity: int) -> Dict:
        """Drives forwards with the specified velocity."""
        forw = {
            "LinearVelocity": self._parse_velocity(velocity),
            "AngularVelocity": self.stagnant_angle,
        }
        return misty.perform_action("drive", data=forw).parse_to_dict()

    def drive_backward(self, misty: Misty, velocity: int):
        """Drives backwards with the specified velocity."""
        back = {
            "LinearVelocity": self._parse_velocity(velocity, negative=True),
            "AngularVelocity": self.stagnant_angle,
        }
        return misty.perform_action("drive", data=back).parse_to_dict()

    def drive_left(self, misty: Misty, velocity: int, angle: int):
        """Drives left with the specified velocity and angle."""
        left = {
            "LinearVelocity": self._parse_velocity(velocity),
            "AngularVelocity": self._parse_angle(angle),
        }
        return misty.perform_action("drive", data=left).parse_to_dict()

    def drive_right(self, misty: Misty, velocity: int, angle: int):
        """Drives right with the specified velocity and angle."""
        velocity = self._parse_velocity(velocity)
        right = {
            "LinearVelocity": self._parse_velocity(velocity),
            "AngularVelocity": self._parse_angle(angle, negative=True),
        }
        return misty.perform_action("drive", data=right).parse_to_dict()

    def stop_driving(self, misty: Misty):
        """Stops driving."""
        return misty.perform_action("drive_stop").parse_to_dict()

This module handles Misty's movement. Handles Misty's movement. All angles are in degrees as integers. Initialises the Movement class.          Args:             max_velocity (int, optional): The maximal allowed velocity. Defaults to `100`.             min_velocity (int, optional): The minimal allowed velocity that is not stagnant. Defaults to `1`.             stagnant_velocity (int, optional): The stagnant velocity. Defaults to `0`.             max_angle (int, optional): The maximum turning angle. Defaults to `100`.             min_angle (int, optional): The minimum turning angle that is not equal to the straight line driving. Defaults to `1`.             stagnant_angle (int, optional): The angle equal to the straight line driving. Defaults to `0`. Parses a velocity or angle value based on the minimum / maximum         values and the stagnant values.          Args:             value (int): the input value.             max (int): the maximal allowed value.             min (int): the minimal allowed value.             negative (bool, optional): Whether the output value should be its negative (if the input is positive) or remain negative/stagnant if the input is not positive. Defaults to `False`.             negatives_allowed (bool, optional): Whether an input value can be negative. Defaults to `False`.          Returns:             int: the input value so that it is smaller or equal to the maximum value and higher or equal to the minimum value if negatives are not allowed; transformed to its negative if `negative` is `True` and higher or equal to the negative of the maximal value and lower or equal to the maximal value if negatives are allowed. # velocity over max # velocity over min below max # velocity below min, over negative max # velocity below negative max Parses the velocity.          Args:             velocity (int): the input value.             negative (bool, optional): Whether the input value should             be transformed into its negative. Defaults to `False`.             both (bool, optional): Whether an input value can be negative.             Defaults to `False`.          Returns:             int: the input value so that it is smaller or equal to the maximum value and higher or equal to the minimum value if negatives are not allowed; transformed to its negative if `negative` is `True` and higher or equal to the negative of the maximal value and lower or equal to the maximal value if negatives are allowed. Parses the angle.          Args:             angle (int): the input value.             negative (bool, optional): Whether the input value should be transformed into its negative. Defaults to `False`.             both (bool, optional): Whether an input value can be negative. Defaults to `False`.          Returns:             int: the input value so that it is smaller or equal to the maximum value and higher or equal to the minimum value if negatives are not allowed; transformed to its negative if `negative` is `True` and higher or equal to the negative of the maximal value and lower or equal to the maximal value if negatives are allowed. Drives forwards with the specified velocity. Drives backwards with the specified velocity. Drives left with the specified velocity and angle. Drives right with the specified velocity and angle. Stops driving.