# Copyright (c) 2010-2022, Manfred Moitzi
# License: MIT License
"""MText - MultiLine-Text-Entity, build by simple TEXT-Entities.

MTEXT was introduced in R13, so this is a replacement with multiple simple
TEXT entities. Supports valign (TOP, MIDDLE, BOTTOM), halign (LEFT, CENTER,
RIGHT), rotation for an arbitrary (!) angle and mirror.

This add-on exist only for porting 'dxfwrite' projects to 'ezdxf'.

"""
from __future__ import annotations
from typing import TYPE_CHECKING
import math
from .mixins import SubscriptAttributes
import ezdxf
from ezdxf.lldxf import const
from ezdxf.enums import MAP_FLAGS_TO_STRING_ALIGN, MAP_STRING_ALIGN_TO_FLAGS

if TYPE_CHECKING:
    from ezdxf.math import UVec
    from ezdxf.eztypes import GenericLayoutType


class MText(SubscriptAttributes):
    """MultiLine-Text buildup with simple Text-Entities.

    Caution: align point is always the insert point, I don't need a second
    alignpoint because horizontal alignment FIT, ALIGN, BASELINE_MIDDLE is not
    supported.

    linespacing -- line spacing in percent of height, 1.5 = 150% = 1+1/2 lines

    supported align values:
        'BOTTOM_LEFT', 'BOTTOM_CENTER', 'BOTTOM_RIGHT'
        'MIDDLE_LEFT', 'MIDDLE_CENTER', 'MIDDLE_RIGHT'
        'TOP_LEFT',    'TOP_CENTER',    'TOP_RIGHT'

    """

    MIRROR_X = const.MIRROR_X
    MIRROR_Y = const.MIRROR_Y
    TOP = const.TOP
    MIDDLE = const.MIDDLE
    BOTTOM = const.BOTTOM
    LEFT = const.LEFT
    CENTER = const.CENTER
    RIGHT = const.RIGHT
    VALID_ALIGN = frozenset(
        [
            "BOTTOM_LEFT",
            "BOTTOM_CENTER",
            "BOTTOM_RIGHT",
            "MIDDLE_LEFT",
            "MIDDLE_CENTER",
            "MIDDLE_RIGHT",
            "TOP_LEFT",
            "TOP_CENTER",
            "TOP_RIGHT",
        ]
    )

    def __init__(
        self, text: str, insert: UVec, linespacing: float = 1.5, **kwargs
    ):
        self.textlines = text.split("\n")
        self.insert = insert
        self.linespacing = linespacing
        if "align" in kwargs:
            self.align = kwargs.get("align", "TOP_LEFT").upper()
        else:  # support for compatibility: valign, halign
            halign = kwargs.get("halign", 0)
            valign = kwargs.get("valign", 3)
            self.align = MAP_FLAGS_TO_STRING_ALIGN.get(
                (halign, valign), "TOP_LEFT"
            )

        if self.align not in MText.VALID_ALIGN:
            raise ezdxf.DXFValueError(f"Invalid align parameter: {self.align}")

        self.height = kwargs.get("height", 1.0)
        self.style = kwargs.get("style", "STANDARD")
        self.oblique = kwargs.get("oblique", 0.0)  # in degree
        self.rotation = kwargs.get("rotation", 0.0)  # in degree
        self.xscale = kwargs.get("xscale", 1.0)
        self.mirror = kwargs.get(
            "mirror", 0
        )  # renamed to text_generation_flag in ezdxf
        self.layer = kwargs.get("layer", "0")
        self.color = kwargs.get("color", const.BYLAYER)

    @property
    def lineheight(self) -> float:
        """Absolute line spacing in drawing units."""
        return self.height * self.linespacing

    def render(self, layout: "GenericLayoutType") -> None:
        """Create the DXF-TEXT entities."""
        textlines = self.textlines
        if len(textlines) > 1:
            if self.mirror & const.MIRROR_Y:
                textlines.reverse()
            for linenum, text in enumerate(textlines):
                alignpoint = self._get_align_point(linenum)
                layout.add_text(
                    text,
                    dxfattribs=self._dxfattribs(alignpoint),
                )
        elif len(textlines) == 1:
            layout.add_text(
                textlines[0],
                dxfattribs=self._dxfattribs(self.insert),
            )

    def _get_align_point(self, linenum: int) -> UVec:
        """Calculate the align point depending on the line number."""
        x = self.insert[0]
        y = self.insert[1]
        try:
            z = self.insert[2]
        except IndexError:
            z = 0.0
        # rotation not respected

        if self.align.startswith("TOP"):
            y -= linenum * self.lineheight
        elif self.align.startswith("MIDDLE"):
            y0 = linenum * self.lineheight
            fullheight = (len(self.textlines) - 1) * self.lineheight
            y += (fullheight / 2) - y0
        else:  # BOTTOM
            y += (len(self.textlines) - 1 - linenum) * self.lineheight
        return self._rotate((x, y, z))  # consider rotation

    def _rotate(self, alignpoint: UVec) -> UVec:
        """Rotate alignpoint around insert point about rotation degrees."""
        dx = alignpoint[0] - self.insert[0]
        dy = alignpoint[1] - self.insert[1]
        beta = math.radians(self.rotation)
        x = self.insert[0] + dx * math.cos(beta) - dy * math.sin(beta)
        y = self.insert[1] + dy * math.cos(beta) + dx * math.sin(beta)
        return round(x, 6), round(y, 6), alignpoint[2]

    def _dxfattribs(self, alignpoint: UVec) -> dict:
        """Build keyword arguments for TEXT entity creation."""
        halign, valign = MAP_STRING_ALIGN_TO_FLAGS.get(self.align, (0, 3))
        return {
            "insert": alignpoint,
            "align_point": alignpoint,
            "layer": self.layer,
            "color": self.color,
            "style": self.style,
            "height": self.height,
            "width": self.xscale,
            "text_generation_flag": self.mirror,
            "rotation": self.rotation,
            "oblique": self.oblique,
            "halign": halign,
            "valign": valign,
        }
