# Openpose
# Original from CMU https://github.com/CMU-Perceptual-Computing-Lab/openpose
# 2nd Edited by https://github.com/Hzzone/pytorch-openpose
# 3rd Edited by ControlNet
# 4th Edited by ControlNet (added face and correct hands)

import os
import random

os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"

import torch
import numpy as np
from . import util
from .wholebody import Wholebody


def draw_pose(pose, H, W, draw_body=True):
    bodies = pose["bodies"]
    faces = pose["faces"]
    hands = pose["hands"]
    candidate = bodies["candidate"]
    subset = bodies["subset"]
    canvas = np.zeros(shape=(H, W, 3), dtype=np.uint8)

    if draw_body:
        canvas = util.draw_bodypose(canvas, candidate, subset)

    canvas = util.draw_handpose(canvas, hands)

    canvas = util.draw_facepose(canvas, faces)

    return canvas


def keypoint2bbox(keypoints):
    valid_keypoints = keypoints[
        keypoints[:, 0] >= 0
    ]  # Ignore keypoints with confidence 0
    if len(valid_keypoints) == 0:
        return np.zeros(4)
    x_min, y_min = np.min(valid_keypoints, axis=0)
    x_max, y_max = np.max(valid_keypoints, axis=0)

    return np.array([x_min, y_min, x_max, y_max])


def expand_bboxes(bboxes, expansion_rate=0.5, image_shape=(0, 0)):
    expanded_bboxes = []
    for bbox in bboxes:
        x_min, y_min, x_max, y_max = map(int, bbox)
        width = x_max - x_min
        height = y_max - y_min

        # 扩展宽度和高度
        new_width = width * (1 + expansion_rate)
        new_height = height * (1 + expansion_rate)

        # 计算新的边界框坐标
        x_min_new = max(0, x_min - (new_width - width) / 2)
        x_max_new = min(image_shape[1], x_max + (new_width - width) / 2)
        y_min_new = max(0, y_min - (new_height - height) / 2)
        y_max_new = min(image_shape[0], y_max + (new_height - height) / 2)

        expanded_bboxes.append([x_min_new, y_min_new, x_max_new, y_max_new])

    return expanded_bboxes


def create_mask(image_width, image_height, bboxs):
    mask = np.zeros((image_height, image_width), dtype=np.float32)
    for bbox in bboxs:
        x1, y1, x2, y2 = map(int, bbox)
        mask[y1 : y2 + 1, x1 : x2 + 1] = 1.0
    return mask


threshold = 0.4


class DWposeDetector:
    def __init__(self):

        self.pose_estimation = Wholebody()

    def __call__(
        self, oriImg, return_index=False, return_yolo=False, return_mask=False
    ):
        oriImg = oriImg.copy()
        H, W, C = oriImg.shape
        with torch.no_grad():
            candidate, subset = self.pose_estimation(oriImg)
            candidate = (
                np.zeros((1, 134, 2), dtype=np.float32)
                if candidate is None
                else candidate
            )
            subset = np.zeros((1, 134), dtype=np.float32) if subset is None else subset
            nums, keys, locs = candidate.shape
            candidate[..., 0] /= float(W)
            candidate[..., 1] /= float(H)
            # import pdb; pdb.set_trace()
            if return_yolo:
                candidate[subset < threshold] = -0.1
                subset = np.expand_dims(subset >= threshold, axis=-1)
                keypoint = np.concatenate([candidate, subset], axis=-1)

                # return pose + hand
                return np.concatenate([keypoint[:, :18], keypoint[:, 92:]], axis=1)

            body = candidate[:, :18].copy()
            body = body.reshape(nums * 18, locs)
            score = subset[:, :18]
            for i in range(len(score)):
                for j in range(len(score[i])):
                    if score[i][j] > threshold:
                        score[i][j] = int(18 * i + j)
                    else:
                        score[i][j] = -1

            un_visible = subset < threshold
            candidate[un_visible] = -1

            foot = candidate[:, 18:24]

            faces = candidate[:, 24:92]

            hands1 = candidate[:, 92:113]
            hands2 = candidate[:, 113:]
            hands = np.vstack([hands1, hands2])

            # import pdb; pdb.set_trace()
            hands_ = hands[hands.max(axis=(1, 2)) > 0]
            if len(hands_) == 0:
                bbox = np.array([0, 0, 0, 0]).astype(int)
            else:
                hand_random = random.choice(hands_)
                bbox = (keypoint2bbox(hand_random) * H).astype(int)  # [0, 1] -> [h, w]

            bodies = dict(candidate=body, subset=score)
            pose = dict(bodies=bodies, hands=hands, faces=faces)

            if return_mask:
                bbox = [(keypoint2bbox(hand) * H).astype(int) for hand in hands_]
                # bbox = expand_bboxes(bbox, expansion_rate=0.5, image_shape=(H, W))
                mask = create_mask(W, H, bbox)
                return draw_pose(pose, H, W), mask

            if return_index:
                return pose
            else:
                return draw_pose(pose, H, W), bbox
