import open3d as o3d
from scipy.spatial import ConvexHull
import numpy as np
from tqdm import tqdm
import matplotlib.pyplot as plt
import scipy.stats as stats
from scipy.spatial.distance import euclidean


def bins_stat(x, y, number_of_bins):
    xmin, xmax = x.min(), x.max()
    ymin, ymax = y.min(), y.max()
    binx = np.linspace(xmin, xmax, number_of_bins)
    biny = np.linspace(ymin, ymax, number_of_bins)
    # size = euclidean(binx[0],binx[1])**2
    size = euclidean(binx[0], binx[1]) * euclidean(biny[0], biny[1])
    # print('x: ',euclidean(binx[0],binx[1]))
    # print('y: ',euclidean(biny[0], biny[1]))

    return size, stats.binned_statistic_2d(x, y, None, 'count', bins=[binx, biny]), binx, biny


def ratio_bounding_convex_hull(projected_points_list):
    convex_hulls_varimax = []
    areas_varimax = []
    volumes_varimax = []
    for i, points in enumerate(projected_points_list):
        # if all projected point lie in one dimension, add some randomness so convex hull can be
        # calculated otherwise QhullError: QH6154 Qhull precision error: will be thrown
        if np.all(points[:, 1] == 0):
            old_value_x, old_value_y = points[0]
            points[0] = [old_value_x, old_value_y + 0.0000000000001]
            # print('------------->', points[0])
        hull = ConvexHull(points)
        convex_hulls_varimax.append(hull)
        areas_varimax.append(hull.area)
        volumes_varimax.append(hull.volume)

    ratio_bounding_area_convex_hull = []
    ratio_bounding_volume_convex_hull = []

    for i, points in enumerate(projected_points_list):
        points = np.asarray(points)

        x_coordinates = np.asarray(points[:, 0])
        y_coordinates = np.asarray(points[:, 1])

        area_bounding_box = (max(x_coordinates) - min(x_coordinates)) * (
                    max(y_coordinates) - min(y_coordinates))
        perimeter_bounding_box = ((max(x_coordinates) - min(x_coordinates)) + (
                max(y_coordinates) - min(y_coordinates))) * 2

        ratio_area = perimeter_bounding_box / areas_varimax[i]
        ratio_volume = area_bounding_box / volumes_varimax[i]

        ratio_bounding_area_convex_hull.append(ratio_area)
        ratio_bounding_volume_convex_hull.append(ratio_volume)

    return ratio_bounding_area_convex_hull, ratio_bounding_volume_convex_hull


def convex_hull_parameters(points_list):
    convex_hulls = []
    areas = []
    volumes = []
    for points in points_list:
        # if all projected point lie in one dimension, add some randomness so convex hull can be
        # calculated otherwise QhullError: QH6154 Qhull precision error: will be thrown
        if np.all(points[:, 1] == 0):
            old_value_x, old_value_y = points[0]
            points[0] = [old_value_x, old_value_y + 0.0000000000001]
        hull = ConvexHull(points)
        convex_hulls.append(hull)
        areas.append(hull.area)
        volumes.append(hull.volume)
    return areas, volumes, convex_hulls


def ratio_convex_hull_calculated_mesh(points_list, alpha):
    ratios = []
    for object in tqdm(points_list):
        mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_alpha_shape(points_list, alpha)

        convex_hull, _ = mesh.compute_convex_hull()
        convex_hull_volume = convex_hull.get_volume()

        if mesh.is_watertight():
            mesh_volume = mesh.get_volume()
        else:
            print('not_watertight')
            mesh_volume = convex_hull_volume
        ratio = mesh_volume / convex_hull_volume
        ratios.append(ratio)
    return ratios


def convex_hull_vs_grid(projections, number_of_bins=30):
    areas, volumes, convex_hulls = convex_hull_parameters(projections)

    no_points_in_grid = []
    convex_hull_vs_grid = []
    fig, axs = plt.subplots(5, 2, figsize=(12, 18))

    for i, projection in enumerate(projections):
        size, bins, binx, biny = bins_stat(projection[:, 0], projection[:, 1],
                                           number_of_bins=number_of_bins)
        bins = bins.statistic
        bins = [item for sublist in bins for item in sublist]
        no_points = len([x for x in bins if x == 0])
        points = len([x for x in bins if x != 0])

        area_with_points = points * size

        convex_hull_vs_grid.append(abs(volumes[i] - area_with_points))
        no_points_in_grid.append(no_points)

        if i < 5:
            axs[i][0].scatter(projection[:, 0], projection[:, 1], marker='.', alpha=0.01, c='black')
            axs[i][0].set_aspect('equal')
            axs[i][0].hlines(biny, min(binx), max(binx), colors=(0, 0, 0, 0.1))
            axs[i][0].vlines(binx, min(biny), max(biny), colors=(0, 0, 0, 0.1))
            hull = convex_hulls[i]
            axs[i][0].plot(projection[hull.vertices, 0], projection[hull.vertices, 1], 'r--')
            # axs[i][1].set_xlabel('convex hull volume')
            # axs[i][1].set_ylabel('area with points')
            # axs[i][1].set_xlim(xmin=0, xmax=10000)
            # axs[i][1].set_ylim(ymin=0, ymax=10000)

            # axs[i][1].scatter(abs(volumes_pca[i]), area_with_points)
            axs[i][1].bar([1, 2], [abs(volumes[i]), area_with_points])
    return convex_hull_vs_grid, no_points_in_grid


#varimax_projections_2d = np.load(
#    '../generated/modelnet10/modelnet10_varimax_1st_2nd_poisson_10000.npy')

#ratio_bounding_area_convex_hull_res, ratio_bounding_volume_convex_hull_res = ratio_bounding_convex_hull(
#    varimax_projections_2d)
