import math
import platform
import matplotlib

import numpy as np
import geopandas as gpd

from itertools import cycle, combinations
from shapely.geometry import MultiPoint, LineString, box
from shapely.affinity import rotate


def angle_between_points(p1, p2, p3):
    v1 = np.array([float(p1[i] - p2[i]) for i in range(2)])
    v2 = np.array([float(p3[i] - p2[i]) for i in range(2)])
    cos_value = round(v1.dot(v2) / np.linalg.norm(v1) / np.linalg.norm(v2), 6)
    return math.degrees(math.acos(cos_value))


def clean_polygon_colliner_point(points, thres=1.):
    i = 0
    while i < len(points):
        idx_pre = (i - 1) % len(points)
        idx_next = (i + 1) % len(points)
        if points[idx_pre] == points[i] or points[i] == points[idx_next] \
                or point_is_collinear(points[idx_pre], points[i], points[idx_next], thres=thres):
            del points[i]
        else:
            i += 1
    return points


def point_is_collinear(*points, thres=1.):
    angle = angle_between_points(*points)
    return abs(angle - 180) <= thres


def str2float(num):
    try:
        return float(num)
    except ValueError:
        return 0


def parse_str(s, split_by=' ') -> tuple:
    return tuple([round(str2float(x.split('=')[1]), 2) for x in s.split(split_by)])


def plot_polys(ax, polys, labels, colors=None):
    if polys is None:
        polys = []
    if len(polys) < 1:
        return
    if colors is None:
        colors = [next(cycol) for _ in range(len(polys))]
    gpdf = gpd.GeoDataFrame({'geometry': polys, 'colors': colors})
    gpdf.plot(ax=ax, color=gpdf['colors'], alpha=0.1)
    if labels is None:
        labels = [str(i) for i in range(len(polys))]
    for poly, label in zip(polys, labels):
        if (not poly.is_valid) or poly.is_empty:
            continue
        center = poly.centroid
        ax.text(center.x, center.y, label, horizontalalignment='center', verticalalignment='center')


def get_vector(wall):
    start, end = wall
    length = math.sqrt(sum([(end[i] - start[i]) ** 2 for i in range(2)]))
    if length < 1e-6:
        raise Exception('zero length line, coords: {0}, {1}'.format(start, end))
    vector = [(end[i] - start[i]) / length for i in range(2)]
    return vector


def get_directed_segment_end_point(start, direction, length, ndigits=2):
    direction_x, direction_y = direction[:2]
    direction_length = math.sqrt(direction_x ** 2 + direction_y ** 2)
    direction_x /= direction_length
    direction_y /= direction_length
    return round(start[0] + length * direction_x, ndigits), round(start[1] + length * direction_y, ndigits)


def get_poly_lines(poly):
    lines = []
    coords = poly.exterior.coords[:]
    for i in range(len(coords)-1):
        lines.append(LineString([coords[i], coords[(i+1) % len(coords)]]))
    return lines


def get_door_buff_poly(door, max_buffer_distance=100):
    lines = get_poly_lines(door)
    lines.sort(key=lambda x: x.length)
    points = []
    buffer_distance = min(lines[-1].length, max_buffer_distance)
    for i in range(2):
        coords = lines[i].coords[:]
        points.append(get_directed_segment_end_point(coords[1], get_vector(coords), buffer_distance))
        points.append(get_directed_segment_end_point(coords[0], get_vector(coords[::-1]), buffer_distance))
    return MultiPoint(points).convex_hull


def calculate_piou(polys):
    piou_li = []
    if len(polys) < 2:
        return piou_li
    for pa, pb in combinations(polys, 2):
        if pa.intersects(pb):
            current_val = pa.intersection(pb).area / pa.union(pb).area
            piou_li.append(current_val)
        else:
            piou_li.append(0)
    return piou_li


def calculate_door_iod(doors, furnitures):
    piou_li = []
    for door in doors:
        inter_furnitures = [furn for furn in furnitures if door.intersects(furn)]
        if len(inter_furnitures) == 0:
            piou_li.append(0)
        else:
            inter_area = sum([door.intersection(furn).area for furn in inter_furnitures])
            piou_li.append(inter_area / door.area)
    return piou_li



def angle2direction(angle, use_radians=False):
    if use_radians:
        rad = angle
    else:
        rad = math.radians(angle)
    return [math.cos(rad), math.sin(rad)]


def get_poly(poly_data):
    poly = box(poly_data['pos'][0] - 0.5 * poly_data['length'], poly_data['pos'][1] - 0.5 * poly_data['width'],
                poly_data['pos'][0] + 0.5 * poly_data['length'], poly_data['pos'][1] + 0.5 * poly_data['width'])
    poly = rotate(poly, poly_data['rotate'])
    return poly
