# Path: navigation/ObjectFollower.py
# class ObjectFollower:
#     def __init__(self, object_detector: ObjectDetector, robot_commands: RobotSerialCommandsConverter,
#                  object_size_threshold, speed_percent_limits: tuple) -> None:
#         self.__object_detector = object_detector
#         self.__robot_commands = robot_commands
#         self.__object_size_threshold = object_size_threshold
#         self.__speed_percent_limits = speed_percent_limits
#         self.radius = 0
#         self.center = (0, 0)
#         self.__image = None
# 
#     def process(self, image):
#         self.__object_detector.process(image)
#         self.__image = image
#         self.center = self.radius = 0
#         if self.__object_detector.detected:
#             self.center, self.radius = self.__object_detector.circle_coordonates
#         return self
# 
#     def has_command(self) -> bool:
#         print(self.radius, self.__is_detection_in_range())
#         if self.radius == 0 or not self.__is_detection_in_range():
#             return False
#         return True
# 
#     def get_command(self) -> str:
#         if not self.has_command():
#             return None
#         return self.__robot_commands.get_steer_command(
#                     self.__get_angle(self.center, self.__image),
#                     self.__get_speed_percent(self.radius, self.__image),
#                     True) + \
#                RobotSerialCommandsConverter.MESSAGE_TERMINATOR
# 
#     def __is_detection_in_range(self):
#         height, width, channels = self.__image.shape
#         minimum_object_size, maximum_object_size = self.__get_object_bounded_sizes(width)
#         if 2 * self.radius >= minimum_object_size and self.radius * 2 <= maximum_object_size:
#             return True
#         return False
# 
#     def __get_object_bounded_sizes(self, actual_object_width):
#         return int(self.__object_size_threshold[0] * actual_object_width / 100), \
#                int(self.__object_size_threshold[1] * actual_object_width / 100)
# 
#     def __get_angle(self, center: tuple, image):
#         height, width, channels = image.shape
#         x_coordonate = center[0]
#         return MathUtils.remap(x_coordonate, 0, width, RobotSerialCommandsConverter.MIN_ANGLE,
#                                RobotSerialCommandsConverter.MAX_ANGLE)
# 
#     def __get_speed_percent(self, radius, image):
#         height, width, channels = image.shape
#         minimum_object_size, maximum_object_size = self.__get_object_bounded_sizes(width)
#         return MathUtils.remap(radius * 2, maximum_object_size, minimum_object_size,
#                                self.__speed_percent_limits[0], self.__speed_percent_limits[1])
# 
# Path: navigation/RobotSerialCommandsConverter.py
# class RobotSerialCommandsConverter:
#     MAX_ANGLE = 180
#     MIN_ANGLE = 0
#     MOTOR_COMMAND = 'M:{0}:{1}'
#     LIGHT_COMMAND = 'L:{0}'
#     MESSAGE_TERMINATOR = ';'
#     POWER_LIMITS = {'forward': {'max': 50, 'min': 0}, 'backward': {'max': -50, 'min': 0}}
#     DIRECTION_LIMITS = {'right': 50, 'left': -50}
# 
#     def get_steer_command(self, angle: int, percent_power: int, forward: bool) -> str:
#         direction = self.__get_converted_direction(angle)
#         power = self.__get_converted_power(percent_power, forward)
#         return self.MOTOR_COMMAND.format(direction, power)
# 
#     def get_lights_command(self, state: bool) -> str:
#         literal_state = {True: '1', False: '0'}
#         return self.LIGHT_COMMAND.format(literal_state[state])
# 
#     def __get_converted_power(self, percent_power: int, forward: bool) -> str:
#         power_limits_key = {True: 'forward', False: 'backward'}[forward]
#         return str(MathUtils.remap(
#                 percent_power, 0, 100,
#                 self.POWER_LIMITS[power_limits_key]['min'], self.POWER_LIMITS[power_limits_key]['max']))
# 
#     def __get_converted_direction(self, angle: int) -> int:
#         return MathUtils.remap(angle, self.MIN_ANGLE, self.MAX_ANGLE,
#                                self.DIRECTION_LIMITS['left'], self.DIRECTION_LIMITS['right'])
# 
# Path: navigation/ColoredObjectDetector.py
# class ColoredObjectDetector(ObjectDetector):
#     def __init__(self, hsv_bounds):
#         self.__hsv_bounds = hsv_bounds
#         super().__init__()
# 
#     def process(self, image):
#         self.detected = False
#         hsv_frame = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
#         mask = cv2.inRange(hsv_frame, self.__hsv_bounds[0], self.__hsv_bounds[1])
#         mask = cv2.erode(mask, None, iterations=2)
#         mask = cv2.dilate(mask, None, iterations=2)
#         contours = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
#         if len(contours) == 0:
#             return
#         largest_contour = max(contours, key=cv2.contourArea)
#         ((x, y), radius) = cv2.minEnclosingCircle(largest_contour)
#         M = cv2.moments(largest_contour)
#         center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
#         self.circle_coordonates = (center, int(radius))
#         self.detected = True


import unittest
from unittest.mock import MagicMock
from unittest.mock import PropertyMock
from navigation.ObjectFollower import ObjectFollower
from navigation.RobotSerialCommandsConverter import RobotSerialCommandsConverter
from navigation.ColoredObjectDetector import ColoredObjectDetector



class TestObjectFollower(unittest.TestCase):
    def test_has_command_and_get_command_move_left_fast(self):
        object_detector, robot_commands, follower = self.__get_objects_for_for_left_steer(20)

# Path: navigation/ObjectFollower.py
# class ObjectFollower:
#     def __init__(self, object_detector: ObjectDetector, robot_commands: RobotSerialCommandsConverter,
#                  object_size_threshold, speed_percent_limits: tuple) -> None:
#         self.__object_detector = object_detector
#         self.__robot_commands = robot_commands
#         self.__object_size_threshold = object_size_threshold
#         self.__speed_percent_limits = speed_percent_limits
#         self.radius = 0
#         self.center = (0, 0)
#         self.__image = None
# 
#     def process(self, image):
#         self.__object_detector.process(image)
#         self.__image = image
#         self.center = self.radius = 0
#         if self.__object_detector.detected:
#             self.center, self.radius = self.__object_detector.circle_coordonates
#         return self
# 
#     def has_command(self) -> bool:
#         print(self.radius, self.__is_detection_in_range())
#         if self.radius == 0 or not self.__is_detection_in_range():
#             return False
#         return True
# 
#     def get_command(self) -> str:
#         if not self.has_command():
#             return None
#         return self.__robot_commands.get_steer_command(
#                     self.__get_angle(self.center, self.__image),
#                     self.__get_speed_percent(self.radius, self.__image),
#                     True) + \
#                RobotSerialCommandsConverter.MESSAGE_TERMINATOR
# 
#     def __is_detection_in_range(self):
#         height, width, channels = self.__image.shape
#         minimum_object_size, maximum_object_size = self.__get_object_bounded_sizes(width)
#         if 2 * self.radius >= minimum_object_size and self.radius * 2 <= maximum_object_size:
#             return True
#         return False
# 
#     def __get_object_bounded_sizes(self, actual_object_width):
#         return int(self.__object_size_threshold[0] * actual_object_width / 100), \
#                int(self.__object_size_threshold[1] * actual_object_width / 100)
# 
#     def __get_angle(self, center: tuple, image):
#         height, width, channels = image.shape
#         x_coordonate = center[0]
#         return MathUtils.remap(x_coordonate, 0, width, RobotSerialCommandsConverter.MIN_ANGLE,
#                                RobotSerialCommandsConverter.MAX_ANGLE)
# 
#     def __get_speed_percent(self, radius, image):
#         height, width, channels = image.shape
#         minimum_object_size, maximum_object_size = self.__get_object_bounded_sizes(width)
#         return MathUtils.remap(radius * 2, maximum_object_size, minimum_object_size,
#                                self.__speed_percent_limits[0], self.__speed_percent_limits[1])
# 
# Path: navigation/RobotSerialCommandsConverter.py
# class RobotSerialCommandsConverter:
#     MAX_ANGLE = 180
#     MIN_ANGLE = 0
#     MOTOR_COMMAND = 'M:{0}:{1}'
#     LIGHT_COMMAND = 'L:{0}'
#     MESSAGE_TERMINATOR = ';'
#     POWER_LIMITS = {'forward': {'max': 50, 'min': 0}, 'backward': {'max': -50, 'min': 0}}
#     DIRECTION_LIMITS = {'right': 50, 'left': -50}
# 
#     def get_steer_command(self, angle: int, percent_power: int, forward: bool) -> str:
#         direction = self.__get_converted_direction(angle)
#         power = self.__get_converted_power(percent_power, forward)
#         return self.MOTOR_COMMAND.format(direction, power)
# 
#     def get_lights_command(self, state: bool) -> str:
#         literal_state = {True: '1', False: '0'}
#         return self.LIGHT_COMMAND.format(literal_state[state])
# 
#     def __get_converted_power(self, percent_power: int, forward: bool) -> str:
#         power_limits_key = {True: 'forward', False: 'backward'}[forward]
#         return str(MathUtils.remap(
#                 percent_power, 0, 100,
#                 self.POWER_LIMITS[power_limits_key]['min'], self.POWER_LIMITS[power_limits_key]['max']))
# 
#     def __get_converted_direction(self, angle: int) -> int:
#         return MathUtils.remap(angle, self.MIN_ANGLE, self.MAX_ANGLE,
#                                self.DIRECTION_LIMITS['left'], self.DIRECTION_LIMITS['right'])
# 
# Path: navigation/ColoredObjectDetector.py
# class ColoredObjectDetector(ObjectDetector):
#     def __init__(self, hsv_bounds):
#         self.__hsv_bounds = hsv_bounds
#         super().__init__()
# 
#     def process(self, image):
#         self.detected = False
#         hsv_frame = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
#         mask = cv2.inRange(hsv_frame, self.__hsv_bounds[0], self.__hsv_bounds[1])
#         mask = cv2.erode(mask, None, iterations=2)
#         mask = cv2.dilate(mask, None, iterations=2)
#         contours = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
#         if len(contours) == 0:
#             return
#         largest_contour = max(contours, key=cv2.contourArea)
#         ((x, y), radius) = cv2.minEnclosingCircle(largest_contour)
#         M = cv2.moments(largest_contour)
#         center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
#         self.circle_coordonates = (center, int(radius))
#         self.detected = True


# Path: test/navigation/TestObjectFollower.py
import unittest
from unittest.mock import MagicMock
from unittest.mock import PropertyMock
from navigation.ObjectFollower import ObjectFollower
from navigation.RobotSerialCommandsConverter import RobotSerialCommandsConverter
from navigation.ColoredObjectDetector import ColoredObjectDetector




class TestObjectFollower(unittest.TestCase):
    def test_has_command_and_get_command_move_left_fast(self):
        object_detector, robot_commands, follower = self.__get_objects_for_for_left_steer(20)

        self.assertFalse(follower.has_command())