import numpy as np
from .utils import *
from hmOCR.argument import Args


class Detector:
    def __init__(self, args: "Args"):
        self.det_algorithm = args.det_algorithm  # DB
        self.det_box_type = args.det_box_type
        pre_process_list = [{
            "DetResizeForTest": {
                "limit_side_len": args.det_limit_side_len,
                "limit_type": args.det_limit_type,
            }
        }, {
            "NormalizeImage": {
                "std": [0.229, 0.224, 0.225],
                "mean": [0.485, 0.456, 0.406],
                "scale": 1 / 255,
                "order": "hwc"
            }
        }, {
            "ToCHWImage": None
        }, {
            "KeepKeys": {
                "keep_keys": ["image", "shape"]
            }
        }]
        postprocess_params = {
            "name": "DBPostProcess", "thresh": args.det_db_thresh,
            "box_thresh": args.det_db_box_thresh, "max_candidates": 1000,
            "unclip_ratio": args.det_db_unclip_ratio, "use_dilation": args.det_use_dilation,
            "score_mode": args.det_db_score_mode, "box_type": args.det_box_type
        }

        self.pre_operator = create_operators(pre_process_list)
        self.post_operator = build_post_process(postprocess_params)
        self.predictor, self.input_tensor, self.output_tensors = create_predictor(args, "det")

    @staticmethod
    def order_points_clockwise(pts):
        rect = np.zeros((4, 2), dtype="float32")
        s = pts.sum(axis=1)
        rect[0] = pts[np.argmin(s)]
        rect[2] = pts[np.argmax(s)]
        tmp = np.delete(pts, (np.argmin(s), np.argmax(s)), axis=0)
        diff = np.diff(np.array(tmp), axis=1)
        rect[1] = tmp[np.argmin(diff)]
        rect[3] = tmp[np.argmax(diff)]
        return rect

    @staticmethod
    def clip_det_res(points, img_height, img_width):
        for pno in range(points.shape[0]):
            points[pno, 0] = int(min(max(points[pno, 0], 0), img_width - 1))
            points[pno, 1] = int(min(max(points[pno, 1], 0), img_height - 1))
        return points

    def filter_tag_det_res(self, dt_boxes, image_shape):
        img_height, img_width = image_shape[0:2]
        dt_boxes_new = []
        for box in dt_boxes:
            if type(box) is list:
                box = np.array(box)
            box = self.order_points_clockwise(box)
            box = self.clip_det_res(box, img_height, img_width)
            rect_width = int(np.linalg.norm(box[0] - box[1]))
            rect_height = int(np.linalg.norm(box[0] - box[3]))
            if rect_width <= 3 or rect_height <= 3:
                continue
            dt_boxes_new.append(box)
        dt_boxes = np.array(dt_boxes_new)
        return dt_boxes

    def filter_tag_det_res_only_clip(self, dt_boxes, image_shape):
        img_height, img_width = image_shape[0:2]
        dt_boxes_new = []
        for box in dt_boxes:
            if type(box) is list:
                box = np.array(box)
            box = self.clip_det_res(box, img_height, img_width)
            dt_boxes_new.append(box)
        dt_boxes = np.array(dt_boxes_new)
        return dt_boxes

    def __call__(self, img):
        ori_im = img.copy()
        data = {"image": img}

        data = transform(data, self.pre_operator)
        img, shape_list = data
        if img is None:
            return None
        img = np.expand_dims(img, axis=0)
        shape_list = np.expand_dims(shape_list, axis=0)
        img = img.copy()

        self.input_tensor.copy_from_cpu(img)
        self.predictor.run()

        outputs = [out.copy_to_cpu() for out in self.output_tensors]
        preds = {"maps": outputs[0]}
        post_result = self.post_operator(preds, shape_list)
        dt_boxes = post_result[0]["points"]

        if self.det_box_type == "poly":
            dt_boxes = self.filter_tag_det_res_only_clip(dt_boxes, ori_im.shape)
        else:
            dt_boxes = self.filter_tag_det_res(dt_boxes, ori_im.shape)

        return dt_boxes
