import argparse
import math
import cv2

from typing import List

import numpy as np
from alibabacloud_imageseg20191230.client import Client as imageseg20191230Client
from alibabacloud_facebody20191230.client import Client as facebody20191230Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_facebody20191230 import models as facebody_20191230_models
from alibabacloud_imageseg20191230 import models as imageseg_20191230_models
from alibabacloud_tea_util import models as util_models
from alibabacloud_tea_util.client import Client as UtilClient
from viapi.fileutils import FileUtils

import urllib.request


class Sample:
    def __init__(self):
        pass

    @staticmethod
    def create_client(
            access_key_id: str,
            access_key_secret: str,
    ) -> (imageseg20191230Client, facebody20191230Client):
        """
        使用AK&SK初始化账号Client
        @param access_key_id:
        @param access_key_secret:
        @return: Client
        @throws Exception
        """
        seg_config = open_api_models.Config(
            # 您的 AccessKey ID,
            access_key_id=access_key_id,
            # 您的 AccessKey Secret,
            access_key_secret=access_key_secret
        )
        face_config = open_api_models.Config(
            # 您的 AccessKey ID,
            access_key_id=access_key_id,
            # 您的 AccessKey Secret,
            access_key_secret=access_key_secret
        )
        # 访问的域名
        seg_config.endpoint = f'imageseg.cn-shanghai.aliyuncs.com'
        face_config.endpoint = f'facebody.cn-shanghai.aliyuncs.com'
        return (imageseg20191230Client(seg_config), facebody20191230Client(face_config))

def id_pohto_demo(
        args: List[str],
) -> None:
    assert (args is not None and len(args.param) > 2), "parameters wrong, use -h for details!"
    [sc_file, color, of_file] = [i for i in args.param]

    # set id photo size
    size = [295, 413]

    # client init
    seg_client, face_client = Sample.create_client(args.access_key_id, args.access_key_secret)

    # generation source image url
    input_url = generate_url(sc_file, args.access_key_id, args.access_key_secret)

    # init segment_body_request
    segment_body_request = imageseg_20191230_models.SegmentBodyRequest(
        image_url=input_url
    )

    # init detect_face_request
    detect_face_request = facebody_20191230_models.DetectFaceRequest(
        image_url=input_url
    )
    runtime = util_models.RuntimeOptions()
    try:
        # 通过人脸SDK获取人脸105关键点信息
        face_result = face_client.detect_face_with_options(detect_face_request, runtime)
        landmarks = face_result.body.data.landmarks
        landmarks = np.array(landmarks)

        # 通过分割SDK获取分割png图
        seg_result = seg_client.segment_body_with_options(segment_body_request, runtime)
        print(seg_result.body.data.image_url)
        rqt = urllib.request.urlopen(seg_result.body.data.image_url)
        seg_img = np.asarray(bytearray(rqt.read()), dtype="uint8")
        seg_img = cv2.imdecode(seg_img, cv2.IMREAD_UNCHANGED)

        # 根据人脸关键点进行人脸对齐，返回旋转后图像、面部中心点、旋转角度、旋转后新的landmarks值
        rotated_img, eye_center, angle, landmarks = align_face(seg_img, landmarks)

        # 输入旋转后图片、预期的证件照分辨率、landmarks、背景色，返回半身像结果
        png_img = corp_halfbody(rotated_img, landmarks, size)

        # 证件照背景色替换
        colors = {'red': (0, 0, 255, 255), 'blue': (255, 0, 0, 255), 'white': (255, 255, 255, 255)}
        if type(color) is str:
            color = colors[color]
        rst_img = np.zeros((size[1], size[0], 3)) + color[0:3]
        rst_img = image_merge_background(png_img[:, :, 0:3], png_img, rst_img)

        # 保存结果到本地
        cv2.imwrite(of_file, rst_img)
    except Exception as error:
        print(error.message)
        # 如有需要，请打印 error
        UtilClient.assert_as_string(error.message)

def align_face(image_array, landmarks):
    """ align faces according to eyes position
    :param image_array: numpy array of a single image
    :param landmarks: dict of landmarks for facial parts as keys and tuple of coordinates as values
    :return:
    rotated_img:  numpy array of aligned image
    eye_center: tuple of coordinates for eye center
    angle: degrees of rotation
    """
    landmarks = np.resize(landmarks, (105, 2))

    # get list landmarks of left and right eye
    left_eye = landmarks[24:39]
    right_eye = landmarks[40:55]
    left_eye_center = np.mean(left_eye, axis=0).astype("int32")
    right_eye_center = np.mean(right_eye, axis=0).astype("int32")
    dy = right_eye_center[1] - left_eye_center[1]
    dx = right_eye_center[0] - left_eye_center[0]
    angle = math.atan2(dy, dx) * 180. / math.pi
    # calculate the center of 2 eyes
    eye_center = (int(left_eye_center[0] + right_eye_center[0]) // 2,
                  int(left_eye_center[1] + right_eye_center[1]) // 2)
    # at the eye_center, rotate the image by the angle
    rotate_matrix = cv2.getRotationMatrix2D(eye_center, angle, scale=1)
    rotated_img = cv2.warpAffine(image_array, rotate_matrix, (image_array.shape[1], image_array.shape[0]))

    rotated_landmarks = []
    for landmark in landmarks:
        rotated_landmark = rotate(origin=eye_center, point=landmark, angle=angle, row=image_array.shape[0])
        rotated_landmarks.append(rotated_landmark)
    return rotated_img, eye_center, angle, rotated_landmarks

def rotate(origin, point, angle, row):
    """ rotate coordinates in image coordinate system
    :param origin: tuple of coordinates,the rotation center
    :param point: tuple of coordinates, points to rotate
    :param angle: degrees of rotation
    :param row: row size of the image
    :return: rotated coordinates of point
    """
    x1, y1 = point
    x2, y2 = origin
    y1 = row - y1
    y2 = row - y2
    angle = math.radians(angle)
    x = x2 + math.cos(angle) * (x1 - x2) - math.sin(angle) * (y1 - y2)
    y = y2 + math.sin(angle) * (x1 - x2) + math.cos(angle) * (y1 - y2)
    y = row - y
    return int(x), int(y)

def corp_halfbody(image_array, landmarks, size):
    """ crop face according to eye,mouth and chin position
    :param image_array: numpy array of a single image
    :param size: single int value, size for w and h after crop
    :param landmarks: dict of landmarks for facial parts as keys and tuple of coordinates as values
    :return:
    cropped_img: numpy array of cropped image
    left, top: left and top coordinates of cropping
    """

    crop_size = [0, 0, size[1], size[0]]

    scal = size[1] / 4 / abs(landmarks[98][1] - landmarks[56][1])
    image = cv2.resize(image_array, np.multiply((image_array.shape[0:2])[::-1], scal).astype(int))
    landmarks = np.multiply(np.array(landmarks), scal)

    x_center = landmarks[98][0]
    crop_size[0:2] = [x_center - size[0] / 2, x_center + size[0] / 2]

    y_center = (landmarks[98][1] + landmarks[56][1]) / 2
    crop_size[2:4] = [y_center - size[1] / 2, y_center + size[1] / 2]

    left, right, top, bottom = [round(i) for i in crop_size]
    left = max(0, left)
    top = max(0, top)
    right = min(image.shape[1], right)
    bottom = min(image.shape[0], bottom)

    cropped_img = image[top:bottom, left:right]

    left, right, top, bottom = [round(i) for i in crop_size]
    bottom = size[1]
    top = size[1] - cropped_img.shape[0]
    left = -min(0, left)
    right = min(cropped_img.shape[1] + left, size[0])


    png_img = np.zeros((size[1], size[0], 4))
    png_img[top:bottom, left:right] = cropped_img[:, :]
    return png_img

def image_merge_background(sc_image, png_image, bg_image):
    """ merge foreground to background image
    :param sc_image: numpy array of the source image
    :param png_image: numpy array of the segmented result with same size of sc_image
    :param bg_image: numpy array of the background image with same size of sc_image
    :return:
    rst_image: numpy array of merged image
    """
    assert (sc_image is not None and png_image is not None and bg_image is not None), "read image input error!"
    h, w, c = sc_image.shape

    # keep sc_image, png_image and bg_image same size
    viapi_image = cv2.resize(png_image, (w, h))
    bg_image = cv2.resize(bg_image, (w, h))
    if len(viapi_image.shape) == 2:
        mask = viapi_image[:, :, np.newaxis]
    elif viapi_image.shape[2] == 4:
        mask = viapi_image[:, :, 3:4]
    elif viapi_image.shape[2] == 3:
        mask = viapi_image[:, :, 0:1]
    else:
        raise Exception("invalid image mask!")
    mask = mask / 255.0

    # merge background
    sc_image = sc_image.astype(float)
    bg_image = bg_image.astype(float)
    rst_image = (sc_image - bg_image) * mask + bg_image
    rst_image = np.clip(rst_image, 0, 255)
    return rst_image

# see https://help.aliyun.com/document_detail/155645.html for more detail
def generate_url(image_path, access_key_id, access_key_secret):
    file_utils = FileUtils(access_key_id, access_key_secret)
    oss_url = file_utils.get_oss_url(image_path, "jpeg", True)
    return oss_url

def define_options():
    parser = argparse.ArgumentParser(description='Human segmentation examples.')
    parser.add_argument('-i', '--access_key_id', default="LTAI5tE6gkXQqJBbCxFfo4hv", help="oss access key id")
    parser.add_argument('-s', '--access_key_secret', default="pziJRQfXlev3vEIY4dHuB6C7E73Uv6", help="access key secret")
    parser.add_argument('-p', '--param', nargs=3
                            , default=['./data/source.jpeg', 'blue', 'result2.jpg']
                            , type=str
                            , help=('source_image_path background_color[red blue white] result_path'))
    args = parser.parse_args()
    return args

if __name__ == '__main__':
    args = define_options()
    id_pohto_demo(args)
