import cv2
import numpy as np
import json


rotation_map = {
    0: None,
    90: cv2.ROTATE_90_COUNTERCLOCKWISE,
    180: cv2.ROTATE_180,
    270: cv2.ROTATE_90_CLOCKWISE
}

class NumpyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, np.ndarray):
            return obj.tolist()
        return json.JSONEncoder.default(self, obj)

def draw_bboxes(image, bounding_boxes, boxes_id, scores):
    image_with_boxes = image.copy()

    for bbox, bbox_id, score in zip(bounding_boxes, boxes_id, scores):
        x1, y1, x2, y2 = bbox
        cv2.rectangle(image_with_boxes, (x1, y1), (x2, y2), (128, 128, 0), 2)

        label = f'#{bbox_id}: {score:.2f}'

        (label_width, label_height), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
        label_x = x1
        label_y = y1 - 5 if y1 > 20 else y1 + 20

        # Draw a filled rectangle as the background for the label
        cv2.rectangle(image_with_boxes, (x1, label_y - label_height - 5),
                      (x1 + label_width, label_y + 5), (128, 128, 0), cv2.FILLED)
        cv2.putText(image_with_boxes, label, (label_x, label_y),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)

    return image_with_boxes


def pad_image(image: np.ndarray, aspect_ratio: float) -> np.ndarray:
    # Get the current aspect ratio of the image
    image_height, image_width = image.shape[:2]
    current_aspect_ratio = image_width / image_height

    left_pad = 0
    top_pad = 0
    # Determine whether to pad horizontally or vertically
    if current_aspect_ratio < aspect_ratio:
        # Pad horizontally
        target_width = int(aspect_ratio * image_height)
        pad_width = target_width - image_width
        left_pad = pad_width // 2
        right_pad = pad_width - left_pad

        padded_image = np.pad(image,
                              pad_width=((0, 0), (left_pad, right_pad), (0, 0)),
                              mode='constant')
    else:
        # Pad vertically
        target_height = int(image_width / aspect_ratio)
        pad_height = target_height - image_height
        top_pad = pad_height // 2
        bottom_pad = pad_height - top_pad

        padded_image = np.pad(image,
                              pad_width=((top_pad, bottom_pad), (0, 0), (0, 0)),
                              mode='constant')

    return padded_image, (left_pad, top_pad)


class VideoReader(object):
    def __init__(self, file_name, rotate=0):
        self.file_name = file_name
        self.rotate = rotation_map[rotate]
        try:  # OpenCV needs int to read from webcam
            self.file_name = int(file_name)
        except ValueError:
            pass

    def __iter__(self):
        self.cap = cv2.VideoCapture(self.file_name)
        if not self.cap.isOpened():
            raise IOError('Video {} cannot be opened'.format(self.file_name))
        return self

    def __next__(self):
        was_read, img = self.cap.read()
        if not was_read:
            raise StopIteration
        if self.rotate is not None:
            img = cv2.rotate(img, self.rotate)
        return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
