# _*_ coding: utf-8 _*_ #
# @Time: 2019/11/28 17:33
# @Author: XiongChao


import math
import numpy as np
from sklearn.neighbors import KDTree

from . import const

DEF_ALPHA = 0.000001


def two_point_distance(point1, point2):
    return math.sqrt((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2)


def interpolation_between_two_point(start_point, end_point, ratio):
    # 在起点与终点之间进行插值
    distance = two_point_distance(start_point, end_point)
    n = int(distance * ratio)  # 插值的个数
    dataset = []
    result = np.linspace(0, 1, n + 2)  # 保留首尾
    for k in result:
        x = k * (end_point[0] - start_point[0]) + start_point[0]
        y = k * (end_point[1] - start_point[1]) + start_point[1]
        if len(start_point) == 3:
            z = k * (end_point[2] - start_point[2]) + start_point[2]
            point = (x, y, z)
        else:
            point = (x, y)
        dataset.append(point)
    return dataset


def cos_(vector1, vector2):
    # 计算两个向量的余弦值
    cos_theta = (vector1[0] * vector2[0] + vector1[1] * vector2[1]) / \
                (math.sqrt(vector1[0] ** 2 + vector1[1] ** 2) * math.sqrt(vector2[0] ** 2 + vector2[1] ** 2))
    # 由于浮点运算的截断误差,可能导致求得的值大于1或者小于-1,因此需要进行处理
    if cos_theta >= 1:
        cos_theta = 1
    elif cos_theta <= -1:
        cos_theta = -1
    return cos_theta


def angle_of_two_vector(vector1, vector2):
    # 返回两个向量的角度,返回的值为角度,为浮点数,可以运算
    cos_theta = cos_(vector1, vector2)
    theta = math.acos(cos_theta) / math.pi * 180
    return theta


def angle_of_three_point(point1, point2, point3):
    # 123的角度
    vector1 = [point1[0] - point2[0], point1[1] - point2[1]]
    vector2 = [point3[0] - point2[0], point3[1] - point2[1]]
    angle = angle_of_two_vector(vector1, vector2)
    return angle


def vector_dot_product(vector1, vector2):
    result = vector1[0] * vector2[0] + vector1[1] * vector2[1]
    return result


def three_point_dot_product(point1, point2, point3):
    # 向量21与23的内积
    result = (point1[0] - point2[0]) * (point3[0] - point2[0]) + \
             (point1[1] - point2[1]) * (point3[1] - point2[1])
    return result


# def direction_of_data(point, dataset):
#     # 返回某一段数据的方向向量,在指定的point处
#     # 如果dataset近似直线则返回直线的方向,如果为曲线则返回曲线的切线方向
#     x_dataset = np.array([data[0] for data in dataset])
#     result = x_dataset - x_dataset[0]
#     if not result.any():
#         # 此时表示所有点的x值都相同,不能进行拟合,会出现矩阵不可逆的情形,当然可以直接得到方向(垂直于x轴)
#         return [0, 1]
#     derivative = derivative_of_polynomial_function(point, dataset)
#     direction = [1, derivative]
#     # 非直线的部分点很密集,取合适的点的个数,防止首尾弯曲超过180°
#     approximate_direction_of_dataset = [dataset[-1][0] - dataset[0][0], dataset[-1][1] - dataset[0][1]]
#     result = vector_dot_product(direction, approximate_direction_of_dataset)
#     if result >= 0:
#         return direction
#     else:
#         return [-1, -derivative]  # 取反方向


def dis_from_point_to_line(k, b, point):
    # k, b代表直线斜率,默认不与X轴垂直
    dis = np.abs(k * point[0] + b - point[1]) / np.sqrt(1 + k * k)
    return dis


# def dis_from_point_to_dataset(point, dataset):
#     # 某点到某个数据集的距离,此数据集为直线数据集
#     x_dataset = np.array([data[0] for data in dataset])
#     result = x_dataset - x_dataset[0]
#     if not result.any():
#         # 此时表示所有点的x值都相同,不能进行拟合,会出现矩阵不可逆的情形,当然可以直接得到方向(垂直于x轴)
#         return np.abs(point[0] - x_dataset[0])
#     new_dataset, x_mean, y_mean = data_normalization(dataset)
#     b, k = np.squeeze(get_parameter(new_dataset, 1))
#     b = y_mean + b - k * x_mean
#     distance = dis_from_point_to_line(k, b, point)
#     return distance


# def nearest_point_index_of_dataset(point, dataset):
#     # 在dataset中寻找距离point最近的点
#     # 返回的是dataset的下标
#     distance_list = [two_point_distance(point, data) for data in dataset]
#     return distance_list.index(min(distance_list))


def vertical_xaxis(dataset):
    # 判断数据集是否垂直于x轴
    x_data = np.array([data[0] for data in dataset])
    x_data = x_data - x_data[0]
    if x_data.any():
        return False
    return True


def two_point_slope_bias(point1, point2):
    # 根据两点确定斜率和截距
    k = (point2[1] - point1[1]) / (point2[0] - point1[0])
    b = (point1[1] * point2[0] - point2[1] * point1[0]) / (point2[0] - point1[0])
    return k, b


def vertical_point(point1, point2, point3):
    # point2, point3确定一条直线,point1到此直线的垂点
    if point2[0] != point3[0]:
        k, b = two_point_slope_bias(point2, point3)
        x = (k * point1[1] + point1[0] - k * b) / (1 + k * k)
        y = k * x + b
    else:
        x = point2[0]
        y = point1[1]
    return x, y


def one_point_to_vertical_point_dis(point1, point2, point3):
    # point2, point3确定一条直线,point1到此直线的垂点
    ver_point = vertical_point(point1, point2, point3)
    distance = two_point_distance(point1, ver_point)
    return distance


def dis_from_dataset_to_dataset(dataset1, dataset2):
    # 两个数据集之间的距离,采取一个数据集的点到另一个数据集的距离的平均
    # 数据集经过插值后,计算点到垂线的距离来度量宽度的误差会很小
    dataset1 = [[data[0], data[1]] for data in dataset1]  # 取二维
    dataset2 = [[data[0], data[1]] for data in dataset2]  # 取二维
    # 首先保证同向
    distance1 = two_point_distance(dataset1[0], dataset2[0])
    distance2 = two_point_distance(dataset1[0], dataset2[-1])
    if distance1 > distance2:
        dataset2 = dataset2[::-1]
    # 取对齐的两段
    index1 = nearest_point_index_of_dataset(dataset1[0], dataset2)
    index2 = nearest_point_index_of_dataset(dataset2[0], dataset1)
    distance1 = two_point_distance(dataset1[0], dataset2[index1])
    distance2 = two_point_distance(dataset2[0], dataset1[index2])
    if distance1 > distance2:
        dataset1 = dataset1[index2:]
    else:
        dataset2 = dataset2[index1:]

    index3 = nearest_point_index_of_dataset(dataset1[-1], dataset2)
    index4 = nearest_point_index_of_dataset(dataset2[-1], dataset1)
    distance3 = two_point_distance(dataset1[-1], dataset2[index3])
    distance4 = two_point_distance(dataset2[-1], dataset1[index4])
    if distance3 > distance4:
        dataset1 = dataset1[:index4 + 1]
    else:
        dataset2 = dataset2[:index3 + 1]

    if len(dataset2) < 2:
        # 仅有一个点,在此特殊情形下,仅作简单计算
        point = dataset2[0]
        index5 = nearest_point_index_of_dataset(point, dataset1)
        target_distance = two_point_distance(point, dataset1[index5])
    else:
        distance_list = []
        for i in range(0, len(dataset1), const.STEP_SIZE):
            point1 = dataset1[i]
            point2, point3 = nearest_two_point_of_dataset(point1, dataset2)
            distance = one_point_to_vertical_point_dis(point1, point2, point3)
            distance_list.append(distance)
        target_distance = sum(distance_list) / len(distance_list)
    return round(target_distance * 1000)


def nearest_point_index_of_dataset(point, dataset):
    # 在dataset中寻找距离point最近的点
    # 仅使用两坐标
    tree = KDTree(np.array(dataset), leaf_size=const.LEAF_SIZE)
    dist, ind = tree.query([np.array(point[:2])], k=1)
    return ind[0][0]


def nearest_two_point_of_dataset(point, dataset):
    # 在dataset中寻找距离point最近的点
    # 仅使用两坐标
    tree = KDTree(np.array(dataset), leaf_size=const.LEAF_SIZE)
    dist, ind = tree.query([np.array(point[:2])], k=2)
    return [dataset[ind[0][0]], dataset[ind[0][1]]]


def nearest_point_of_dataset(point, dataset):
    # 在dataset中寻找距离point最近的点
    # 返回的是dataset的下标
    # 仅使用两坐标
    distance_list = [two_point_distance(point[:2], data[:2]) for data in dataset]
    return dataset[distance_list.index(min(distance_list))]


def get_sorted_data(dataset, point):
    # 按照离point的距离,对dataset进行排序,从近到远
    dict_ = {two_point_distance(point, data): data for data in dataset}
    sort_distance = sorted(dict_.keys())
    sorted_data = []
    for key in sort_distance:
        sorted_data.append(dict_[key])
    return sorted_data


def clockwise_counterclockwise(point1, point2, point3):
    # 判断123是顺时针还是逆时针
    # 计算向量point1point2与point2point3的向量积
    result = (point2[0] - point1[0]) * (point3[1] - point2[1]) - (point2[1] - point1[1]) * (point3[0] - point2[0])
    if result > 0:
        # point1point2point3逆时针
        return 1
    else:
        return 0


def length_of_dataset(dataset):
    # 一个数据集的长度,以没两个点直接的距离之和计算
    dis = 0
    for i in range(len(dataset) - 1):
        dis += two_point_distance(dataset[i][:2], dataset[i + 1][:2])
    return dis


def is_same_point_2d(point1, point2, alpha=DEF_ALPHA):
    # 判断两个点是不是一个点
    flag = np.array([np.abs(point1[i] - point2[i]) < alpha for i in range(len(point1))])[:2]
    if flag.all():
        return True
    return False