import numbers
from typing import List
from math import sin, cos, sqrt

from matplotlib.figure import Figure

from geogst.core.geometries.shapes.lines import Ln
from geogst.plots.parameters import *


def define_plot_structural_segment(
        structural_attitude,
        profile_length,
        vertical_exaggeration,
        segment_scale_factor=70.0
):

    ve = float(vertical_exaggeration)
    intersection_point = structural_attitude.pt_3d
    z0 = intersection_point.z

    h_dist = structural_attitude.sign_hor_dist
    slope_rad = structural_attitude.slope_rad
    intersection_downward_sense = structural_attitude.dwnwrd_sense
    length = profile_length / segment_scale_factor

    s_slope = sin(float(slope_rad))
    c_slope = cos(float(slope_rad))

    if c_slope == 0.0:
        height_corr = length / ve
        structural_segment_s = [h_dist, h_dist]
        structural_segment_z = [z0 + height_corr, z0 - height_corr]
    else:
        t_slope = s_slope / c_slope
        width = length * c_slope

        length_exag = width * sqrt(1 + ve*ve * t_slope*t_slope)

        corr_width = width * length / length_exag
        corr_height = corr_width * t_slope

        structural_segment_s = [h_dist - corr_width, h_dist + corr_width]
        structural_segment_z = [z0 + corr_height, z0 - corr_height]

        if intersection_downward_sense == "left":
            structural_segment_z = [z0 - corr_height, z0 + corr_height]

    return structural_segment_s, structural_segment_z


def plot_structural_attitude(
        plot_addit_params,
        axes,
        section_length,
        vertical_exaggeration,
        structural_attitude_list,
        color
):

    # TODO:  manage case for possible nan z values
    projected_z = [structural_attitude.pt_3d.y for structural_attitude in structural_attitude_list if
                   0.0 <= structural_attitude.sign_hor_dist <= section_length]

    # TODO:  manage case for possible nan z values
    projected_s = [structural_attitude.sign_hor_dist for structural_attitude in structural_attitude_list if
                   0.0 <= structural_attitude.sign_hor_dist <= section_length]

    projected_ids = [structural_attitude.id for structural_attitude in structural_attitude_list if
                     0.0 <= structural_attitude.sign_hor_dist <= section_length]

    axes.plot(projected_s, projected_z, 'o', color=color)

    # plot segments representing structural data
    for structural_attitude in structural_attitude_list:
        if 0.0 <= structural_attitude.sign_hor_dist <= section_length:
            structural_segment_s, structural_segment_z = define_plot_structural_segment(structural_attitude,
                                                                                        section_length,
                                                                                        vertical_exaggeration)

            axes.plot(structural_segment_s, structural_segment_z, '-', color=color)

    if plot_addit_params["add_trendplunge_label"] or plot_addit_params["add_ptid_label"]:

        src_dip_dirs = [structural_attitude.src_geol_plane.dd for structural_attitude in
                        structural_attitude_list if 0.0 <= structural_attitude.sign_hor_dist <= section_length]
        src_dip_angs = [structural_attitude.src_geol_plane.da for structural_attitude in
                        structural_attitude_list if 0.0 <= structural_attitude.sign_hor_dist <= section_length]

        for rec_id, src_dip_dir, src_dip_ang, s, z in zip(projected_ids, src_dip_dirs, src_dip_angs, projected_s,
                                                          projected_z):

            if plot_addit_params["add_trendplunge_label"] and plot_addit_params["add_ptid_label"]:
                label = "%s-%03d/%02d" % (rec_id, src_dip_dir, src_dip_ang)
            elif plot_addit_params["add_ptid_label"]:
                label = "%s" % rec_id
            elif plot_addit_params["add_trendplunge_label"]:
                label = "%03d/%02d" % (src_dip_dir, src_dip_ang)
            else:
                label = ''

            if label:
                axes.annotate(label, (s + 15, z + 15))


def plot_projected_line_set(
        axes,
        curve_set,
        labels
):

    colors = LNINT_ADDITIONAL_COLORS * (int(len(curve_set) / len(LNINT_ADDITIONAL_COLORS)) + 1)
    for multiline_2d, label, color in zip(curve_set, labels, colors):
        for line_2d in multiline_2d.lines:
            plot_line(axes, line_2d.x_array, line_2d.y_array, color, name=label)


def plot_profile_lines_intersection_points(
        axes,
        profile_lines_intersection_points
):

    for s, pt3d, intersection_id, color in profile_lines_intersection_points:
        axes.plot(s, pt3d.y, 'o', color=color)
        if str(intersection_id).upper() != "NULL" or str(intersection_id) != '':
            axes.annotate(str(intersection_id), (s + 25, pt3d.y + 25))


def plot_profile_polygon_intersection_line(
        plot_addit_params,
        axes,
        intersection_line_value
):

    classification, line3d, s_list = intersection_line_value
    z_list = [pt3d.y for pt3d in line3d.pts]

    if plot_addit_params["polygon_class_colors"] is None:
        color = "red"
    else:
        color = plot_addit_params["polygon_class_colors"][str(classification)]

    plot_line(axes, s_list, z_list, color, linewidth=3.0, name=classification)


def create_axes(
    profile_window,
    plot_x_range,
    plot_y_range,
    grid_spec,
    ndx_subplot
) -> 'gui.axes._axes.Axes.AxesSubplot':

    x_min, x_max = plot_x_range
    y_min, y_max = plot_y_range
    axes = profile_window.canvas.fig.add_subplot(grid_spec[ndx_subplot])
    axes.set_xlim(x_min, x_max)
    axes.set_ylim(y_min, y_max)

    axes.grid(
        True,
        color='lightgrey',
        linestyle='-.',
        linewidth=0.25,
        alpha=0.5)

    return axes


def plot_line(
    fig: Figure,
    line: Ln
) -> Figure:
    """
    Plot a line.

    :param fig: the figure in which to plot the line.
    :param line: the line to plot.
    :return: the input Figure instance.
    """

    fig.gca().plot(line.x_list(), line.y_list(), '-')

    return fig


def plot_lines(
    fig: Figure,
    lines: List[Ln],
    color: str = "blue",
    linestyle: str = '-',
    linewidth: numbers.Real = 0.5,
    labels: bool = True,
    **kargs
) -> Figure:

    ax = fig.gca()

    for ndx, line in enumerate(lines):

        if labels and (ndx+1) % 5 == 0:
           line_color = 'red'
           line_width = linewidth * 2
        else:
            line_color = color
            line_width = linewidth

        ax.plot(
            line.x_list(),
            line.y_list(),
            color=line_color,
            linestyle=linestyle,
            linewidth=line_width,
            **kargs)

        if labels and (ndx+1) % 5 == 0:
            end_point = line[-1].end_pt
            ax.text(end_point.x, end_point.y, f'{ndx+1}')

    return fig
