import pandas as pd

from plots.packing_algo import ConvexPolygon, pack_polygons, truncate, RectangularContainer, ConvexContainer
from plots.polygon_creator import voronoi_polygons_wrapper, rectangle_cutter


def build_dataset_with_rectangle_cutter(rect_width: float, rect_height, repetition, cut_list=[], angle_steps=90,
                                        cut_min=1,
                                        cut_max=1, cut_steps=1, intervals=[0, 0.01, 0.05, 1], weights=[0, 0, 0.5, 1],
                                        save_container=True, create_plots=True):
    cut_numbers = []
    data_dict_list = []
    if cut_list:
        cut_numbers = cut_list
    else:
        if cut_min > cut_max:
            cut_max = cut_min
        for cut_number in range(cut_min, cut_max + 1, cut_steps):
            cut_numbers.append(cut_number)
    for cut_count in cut_numbers:
        cutted_polygons_lists = []
        for n in range(0, repetition):
            cutted_polygons = rectangle_cutter(rect_width, rect_height, cut_count, intervals=intervals, weights=weights)
            cutted_polygons_lists.append(cutted_polygons)
        dict_data = collect_rect_containers_data(rect_width, rect_height, cutted_polygons_lists, angle_steps,
                                                 save_container, create_plots=True)
        data_dict_list.append(dict_data)
    return data_dict_list


def build_dataset_with_voronoi(rect_width, rect_height, repetition, cut_list=[], cut_min=5, cut_max=5, cut_steps=1,
                               angle_steps=90, save_container=True, create_plots=True):
    cut_numbers = []
    data_dict_list = []
    if cut_list:
        cut_numbers = cut_list
    else:
        if cut_min > cut_max:
            cut_max = cut_min
        for cut_number in range(cut_min, cut_max + 1, cut_steps):
            cut_numbers.append(cut_number)
    for cut_count in cut_numbers:
        cutted_polygons_lists = []
        for n in range(0, repetition):
            cutted_polygons = voronoi_polygons_wrapper(rect_width, rect_height, cut_count)
            cutted_polygons_lists.append(cutted_polygons)
        dict_data = collect_rect_containers_data(rect_width, rect_height, cutted_polygons_lists, angle_steps,
                                                 save_container, create_plots=True)
        data_dict_list.append(dict_data)
    return data_dict_list


def collect_rect_containers_data(rect_width: float, rect_height: float, cutted_polygons_lists: [[ConvexPolygon]],
                                 angle_steps=90, save_container=True, create_plots=True) -> {str, list}:
    opt_area = rect_width * rect_height
    opt_area_list = []
    area_list = []
    area_div_list = []
    angle_0_area_div_list = []
    angle_0_not_clipped_area_div_list = []
    not_clipped_area_list = []
    not_clipped_area_div_list = []
    polygon_count_list = []
    end_container_list_plots = []
    for polygons in cutted_polygons_lists:
        cc = ConvexContainer(polygons, steps=angle_steps, build_plots=create_plots)
        end_container = cc.smallest_rectangular_container
        end_container_angle_0 = cc.angle_0_rectangular_container

        c_area = end_container.container_area
        c_not_opt_area = end_container.container_not_clipped_area
        c_not_r_area = end_container_angle_0.container_area
        c_not_r_not_opt_area = end_container_angle_0.container_not_clipped_area
        area_list.append(c_area)
        area_div_list.append(c_area / opt_area)
        not_clipped_area_list.append(c_not_opt_area)
        not_clipped_area_div_list.append(c_not_opt_area / opt_area)

        angle_0_area_div_list.append(c_not_r_area / opt_area)
        angle_0_not_clipped_area_div_list.append(c_not_r_not_opt_area / opt_area)
        opt_area_list.append(opt_area)
        polygon_count_list.append(len(polygons))
        if save_container:
            end_container_list_plots.append(cc.plot_container(render=False))
    rect_containers_data_dict = {'area': area_list, 'area/opt_area': area_div_list,
                                 'not_clipped_area': not_clipped_area_list,
                                 'not_clipped_area/opt_area': not_clipped_area_div_list,
                                 'angle_0_area/opt_area': angle_0_area_div_list,
                                 'angle_0_not_clipped_area/opt_area': angle_0_not_clipped_area_div_list,
                                 'opt-area': opt_area_list,
                                 'polygon count': polygon_count_list}
    if end_container_list_plots:
        rect_containers_data_dict['End-Container'] = end_container_list_plots
    return rect_containers_data_dict


# # need to update like rectangle_cutter with deviation
# def find_repition_factor_rectangle_voronoi(cut, rep_high, rep_low=1, rep_steps=1, display_flag=True,
#                                            intervals=[0, 0.01, 0.05, 1], weights=[0, 0, 0.5, 1]):
#     average_list = []
#     if rep_low < 1 or rep_high < 1:
#         raise ValueError("the value of rep_high and rep_low need to bigger then 0")
#     for rep in range(rep_low, rep_high + rep_steps, rep_steps):
#         data = build_dataset_with_voronoi(1000, 1000, rep, cut_min=cut, cut_max=cut, cut_steps=1)
#         df = pd.DataFrame(data[0])
#         if display_flag:
#             display(df.sort_values(by="area", ascending=False))
#         list_optimal_areas = df["area/opt_area"].tolist()
#         average = sum(list_optimal_areas) / len(list_optimal_areas)
#         average_list.append((rep, average))
#     return average_list


# # deviation=0.05, accept=10
# def find_repition_factor_rectangle_cutter(cut, rep_high, deviation=0.05, accept_number=10, rep_low=1, rep_steps=1,
#                                           display_flag=True, intervals=[0, 0.01, 0.05, 1], weights=[0, 0, 0.5, 1]):
#     average_list = []
#     if rep_low < 1 or rep_high < 1:
#         raise ValueError("the value of rep_high and rep_low need to bigger then 0")
#     average_canidate = 0
#     accept_counter = accept_number
#     average_top_bound = average_canidate + average_canidate * deviation
#     average_bot_bound = average_canidate - average_canidate * deviation
#     for rep in range(rep_low, rep_high + rep_steps, rep_steps):

#         data = build_dataset_with_rectangle_cutter(1000, 1000, rep, cut_min=cut, cut_max=cut, cut_steps=1,
#                                                    intervals=[0, 0.01, 0.05, 1], weights=[0, 0, 0.5, 1])
#         df = pd.DataFrame(data[0])
#         if display_flag:
#             display(df.sort_values(by="area", ascending=False))

#         list_optimal_areas = df["area/opt_area"].tolist()
#         average = sum(list_optimal_areas) / len(list_optimal_areas)

#         if average_bot_bound <= average <= average_top_bound:
#             accept_counter -= 1
#         else:
#             average_canidate = average
#             accept_counter = accept_number
#             average_top_bound = average_canidate + average_canidate * deviation
#             average_bot_bound = average_canidate - average_canidate * deviation
#         if accept_counter <= 0:
#             print(rep)
#             return average_list
#         average_list.append((rep, average))
#     return average_list


def dict_list_to_ordered_panda_list(dictionary_list: [dict], ordered_by_area=True) -> [pd.DataFrame]:
    data_frame_list = []
    for dic in dictionary_list:
        df = pd.DataFrame(dic)
        if ordered_by_area:
            data_frame_list.append(df.sort_values(by="area", ascending=False))
        else:
            data_frame_list.append(df.sort_values(by="not_clipped_area", ascending=False))
    return data_frame_list


# def display_panda_df_list(df_list: [pd.DataFrame]) -> None:
#     for df in df_list:
#         display(df)


def build_aprox_values(panda_data):
    mean_opt = 0
    mean_not_clipped = 0
    mean_angle_0 = 0
    mean_angle_0_not_clipped = 0
    counter = 0
    mean_best = 0
    mean_worst = 0
    for data in panda_data:
        mean_opt += data["area/opt_area"].mean(axis=0)
        mean_best += data["area/opt_area"].iloc[-1]
        mean_worst += data["area/opt_area"].iloc[0]
        mean_not_clipped += data["not_clipped_area/opt_area"].mean(axis=0)
        mean_angle_0 += data["angle_0_area/opt_area"].mean(axis=0)
        mean_angle_0_not_clipped += data["angle_0_not_clipped_area/opt_area"].mean(axis=0)
        counter += 1
    mean_opt = mean_opt / counter
    mean_best = mean_best / counter
    mean_worst = mean_worst / counter
    mean_not_clipped = mean_not_clipped / counter
    mean_angle_0 = mean_angle_0 / counter
    mean_angle_0_not_clipped = mean_angle_0_not_clipped / counter
    aprox_dict = dict(aprox=mean_opt, aprox_best=mean_best, aprox_worst=mean_worst, aprox_not_clipped=mean_not_clipped,
                      aprox_angle_0=mean_angle_0, aprox_angle_0_not_clipped=mean_angle_0_not_clipped)
    return aprox_dict