import numpy as np
from typing import List

from .base_model import BaseModel
from .utils import letterbox, nms_optimized, xywh2xyxy


class RTMDet(BaseModel):
    def __init__(self,
                 model_path: str,
                 conf_threshold: float,
                 iou_threshold: float,
                 device: str = 'CUDA',
                 warmup: int = 30):
        super(RTMDet, self).__init__(model_path, device, warmup)
        self.conf_threshold = conf_threshold
        self.iou_threshold = iou_threshold
        self.dx = 0
        self.dy = 0
        self.scale = 0

    def preprocess(self, image: np.ndarray):
        th, tw = self.input_shape[1:3]
        image, self.dx, self.dy, self.scale = letterbox(
            image, (tw, th), fill_value=114
        )
        tensor = np.asarray(image).astype(self.input_type, copy=False)[..., ::-1]
        tensor = np.expand_dims(tensor, axis=0)
        return tensor

    def postprocess(self, tensor: List[np.ndarray]):
        boxes = np.squeeze(tensor[0], axis=0)
        classes = np.expand_dims(np.squeeze(tensor[1], axis=0), axis=-1)
        boxes = np.concatenate([boxes, classes], axis=-1)

        boxes = nms_optimized(boxes, self.iou_threshold, self.conf_threshold)

        if boxes.shape[0] == 0:
            return boxes

        human_class = boxes[..., -1] == 0
        boxes = boxes[human_class][..., :4]

        boxes[:, 0] -= self.dx
        boxes[:, 2] -= self.dx
        boxes[:, 1] -= self.dy
        boxes[:, 3] -= self.dy

        boxes = np.clip(boxes, a_min=0, a_max=None)
        boxes[:, :4] /= self.scale

        return boxes


class Yolov8(BaseModel):
    def __init__(self,
                 model_path: str,
                 conf_threshold: float,
                 iou_threshold: float,
                 device: str = 'CUDA',
                 warmup: int = 30):
        super(Yolov8, self).__init__(model_path, device, warmup)
        self.conf_threshold = conf_threshold
        self.iou_threshold = iou_threshold
        self.dx = 0
        self.dy = 0
        self.scale = 0

    def preprocess(self, image):
        th, tw = self.input_shape[2:]
        image, self.dx, self.dy, self.scale = letterbox(image, (tw, th))
        tensor = image / 255.
        tensor = np.expand_dims(tensor, axis=0).transpose((0, 3, 1, 2)).astype(np.float32)
        return tensor

    def postprocess(self, tensor):
        feature_map = tensor[0]
        feature_map = np.squeeze(feature_map, axis=0).transpose((1, 0))

        pred_class = feature_map[..., 4:]
        pred_conf = np.max(pred_class, axis=-1, keepdims=True)
        pred_class = np.argmax(pred_class, axis=-1, keepdims=True)
        boxes = np.concatenate([feature_map[..., :4], pred_conf, pred_class], axis=-1)

        boxes = xywh2xyxy(boxes)
        boxes = nms(boxes, self.iou_threshold, self.conf_threshold)

        if boxes.shape[0] == 0:
            return boxes

        human_class = boxes[..., -1] == 0
        boxes = boxes[human_class][..., :4]

        boxes[:, 0] -= self.dx
        boxes[:, 2] -= self.dx
        boxes[:, 1] -= self.dy
        boxes[:, 3] -= self.dy
        boxes = np.clip(boxes, a_min=0, a_max=None)
        boxes[:, :4] /= self.scale
        return boxes
