# Copyright (c) 2018-2021, Manfred Moitzi
# License: MIT License
from typing import (
    TYPE_CHECKING,
    Tuple,
    Iterable,
    List,
    cast,
    Optional,
)
import math
from ezdxf.math import Vec3, Vec2, ConstructionRay, UCS
from ezdxf.render.arrows import ARROWS, connection_point
from ezdxf.entities.dimstyleoverride import DimStyleOverride

from .dim_base import (
    BaseDimensionRenderer,
    TextBox,
    LengthMeasurement,
    Measurement,
    compile_mtext,
    order_leader_points,
)

if TYPE_CHECKING:
    from ezdxf.eztypes import Dimension, Vertex, GenericLayoutType


class LinearDimension(BaseDimensionRenderer):
    """Linear dimension line renderer, used for horizontal, vertical, rotated
    and aligned DIMENSION entities.

    Args:
        dimension: DXF entity DIMENSION
        ucs: user defined coordinate system
        override: dimension style override management object

    """

    def __init__(
        self,
        dimension: "Dimension",
        ucs: "UCS" = None,
        override: "DimStyleOverride" = None,
    ):
        super().__init__(dimension, ucs, override)
        measurement = self.measurement
        if measurement.text_moveme
nt_rule == 0:
            # moves the dimension line with dimension text, this makes no sense
            # for ezdxf (just set `base` argument)
            measurement.text_movement_rule = 2

        self.oblique_angle
: float = self.dimension.get_dxf_attrib(
            "oblique_angle", 90
        )
        self.dim_line_angle: float = self.dimension.get_dxf_attrib("angle", 0)
        self.dim_line_angle_rad: float = math.radians(self.dim_line_angle)
        self.ext_line_angle: float = self.dim_line_angle + self.oblique_angle
        self.ext_line_angle_rad: float = math.radians(self.ext_line_angle)

        # text is aligned to dimension line
        measurement.text_rotation = self.dim_line_angle
        # text above extension line, is always aligned with extension lines
        if measurement.text_halign in (3, 4):
            measurement.text_rotation = self.ext_line_angle

        self.ext1_line_start = Vec2(self.dimension.dxf.defpoint2)
        self.ext2_line_start = Vec2(self.dimension.dxf.defpoint3)

        ext1_ray = ConstructionRay(
            self.ext1_line_start, angle=self.ext_line_angle_rad
        )
        ext2_ray = ConstructionRay(
            self.ext2_line_start, angle=self.ext_line_angle_rad
        )
        dim_line_ray = ConstructionRay(
            self.dimension.dxf.defpoint, angle=self.dim_line_angle_rad
        )

        self.dim_line_start: Vec2 = dim_line_ray.intersect(ext1_ray)
        self.dim_line_end: Vec2 = dim_line_ray.intersect(ext2_ray)
        self.dim_line_center: Vec2 = self.dim_line_start.lerp(self.dim_line_end)

        if self.dim_line_start == self.dim_line_end:
            self.dim_line_vec = Vec2.from_angle(self.dim_line_angle_rad)
        else:
            self.dim_line_vec = (
                self.dim_line_end - self.dim_line_start
            ).normalize()

        # set dimension defpoint to expected location - 3D vertex required!
        self.dimension.dxf.defpoint = Vec3(self.dim_line_start)

        raw_measurement = (self.dim_line_end - self.dim_line_start).magnitude
        measurement.update(raw_measurement)

        # only for linear dimension in multi point mode
        self.multi_point_mode = self.dim_style.pop("multi_point_mode", False)

        # 1 .. move wide text up
        # 2 .. move wide text down
        # None .. ignore
        self.move_wide_text: Optional[bool] = self.dim_style.pop(
            "move_wide_text", None
        )

        # actual text width in drawing units
        self._total_text_width: float = 0

        # arrows
        self.required_arrows_space: float = (
            2 * self.arrows.arrow_size + measurement.text_gap
        )
        self.arrows_outside: bool = (
            self.required_arrows_space > raw_measurement
        )

        # text location and rotation
        if measurement.text:
            # text width and required space
            self._total_text_width = self.total_text_width()
            if self.tol.has_limits:
                # limits show the upper and lower limit of the measurement as
                # stacked values and with the size of tolerances
                self.tol.update_limits(self.measurement.value)

            if self.multi_point_mode:
                # ezdxf has total control about vertical text position in multi
                # point mode
                measurement.text_vertical_position = 0.0

            if (
                measurement.text_valign == 0
                and abs(measurement.text_vertical_position) < 0.7
            ):
                # vertical centered text needs also space for arrows
                required_space = (
                    self._total_text_width + 2 * self.arrows.arrow_size
                )
            else:
                required_space = self._total_text_width
            measurement.is_wide_text = required_space > raw_measurement

            if not measurement.force_text_inside:
                # place text outside if wide text and not forced inside
                measurement.text_is_outside = measurement.is_wide_text
            elif measurement.is_wide_text and measurement.text_halign < 3:
                # center wide text horizontal
                measurement.text_halign = 0

            # use relative text shift to move wide text up or down in multi
            # point mode
            if (
                self.multi_point_mode
                and measurement.is_wide_text
                and self.move_wide_text
            ):
                shift_value = measurement.text_height + measurement.text_gap
                if self.move_wide_text == 1:  # move text up
                    measurement.text_shift_v = shift_value
                    if (
                        measurement.vertical_placement == -1
                    ):  # text below dimension line
                        # shift again
                        measurement.text_shift_v += shift_value
                elif self.move_wide_text == 2:  # move text down
                    measurement.text_shift_v = -shift_value
                    if (
                        measurement.vertical_placement == 1
                    ):  # text above dimension line
                        # shift again
                        measurement.text_shift_v -= shift_value

            # get final text location - no altering after this line
            measurement.text_location = self.get_text_location()

            # text rotation override
            rotation: float = measurement.text_rotation
            if measurement.user_text_rotation is not None:
                rotation = measurement.user_text_rotation
            elif (
                measurement.text_is_outside
                and measurement.text_outside_horizontal
            ):
                rotation = 0.0
            elif (
                measurement.text_is_inside
                and measurement.text_inside_horizontal
            ):
                rotation = 0.0
            measurement.text_rotation = rotation

            text_box = self.init_text_box()
            self.geometry.set_text_box(text_box)
            if measurement.has_leader:
                p1, p2, *_ = text_box.corners
                self.leader1, self.leader2 = order_leader_points(
                    self.dim_line_center, p1, p2
                )
                # not exact what BricsCAD (AutoCAD) expect, but close enough
                self.dimension.dxf.text_midpoint = self.leader1
            else:
                # write final text location into DIMENSION entity
                self.dimension.dxf.text_midpoint = measurement.text_location

    def init_measurement(self, color: int, scale: float) -> Measurement:
        return LengthMeasurement(
            self.dim_style, self.default_color, self.dim_scale
        )

    def render(self, block: "GenericLayoutType") -> None:
        """Main method to create dimension geometry of basic DXF entities in the
        associated BLOCK layout.

        Ar