# -*- coding: utf-8 -*-
#
# Copyright (c) 2020 Martin Owens <doctormo@gmail.com>
#                    Thomas Holder <thomas.holder@schrodinger.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# pylint: disable=arguments-differ
"""
Provide text based element classes interface.

Because text is not rendered at all, no information about a text's path
size or actual location can be generated yet.
"""

from __future__ import annotations

from tempfile import TemporaryDirectory

from ..interfaces.IElement import BaseElementProtocol
from ..paths import Path
from ..transforms import Transform, BoundingBox
from ..command import inkscape, write_svg
from ._base import BaseElement, ShapeElement
from ._polygons import PathElementBase


class TextBBMixin:  # pylint: disable=too-few-public-methods
    """Mixin to query the bounding box from Inkscape

    .. versionadded:: 1.2
    """

    def get_inkscape_bbox(self: BaseElementProtocol) -> BoundingBox:
        """Query the bbbox of a single object.

        This calls the Inkscape command, so it is rather slow to use in a loop.
        """
        with TemporaryDirectory(prefix="inkscape-command") as tmpdir:
            svg_file = write_svg(self.root, tmpdir, "input.svg")
            out = inkscape(svg_file, "-X", "-Y", "-W", "-H", query_id=self.get_id())
            out = list(map(self.root.viewport_to_unit, out.splitlines()))
            if len(out) != 4:
                raise ValueError("Error: Bounding box computation failed")
            return BoundingBox.new_xywh(*out)


class FlowRegion(ShapeElement):
    """SVG Flow Region (SVG 2.0)"""

    tag_name = "flowRegion"

    def get_path(self):
        # This ignores flowRegionExcludes
        return sum([child.path for child in self], Path())


class FlowRoot(ShapeElement, TextBBMixin):
    """SVG Flow Root (SVG 2.0)"""

    tag_name = "flowRoot"

    @property
    def region(self):
        """Return the first flowRegion in this flowRoot"""
        return self.findone("svg:flowRegion")

    def get_path(self):
        region = self.region
        return region.get_path() if region is not None else Path()


class FlowPara(ShapeElement):
    """SVG Flow Paragraph (SVG 2.0)"""

    tag_name = "flowPara"

    def get_path(self):
        # XXX: These empty paths mean the bbox for text elements will be nothing.
        return Path()


class FlowDiv(ShapeElement):
    """SVG Flow Div (SVG 2.0)"""

    tag_name = "flowDiv"

    def get_path(self):
        # XXX: These empty paths mean the bbox for text elements will be nothing.
        return Path()


class FlowSpan(ShapeElement):
    """SVG Flow Span (SVG 2.0)"""

    tag_name = "flowSpan"

    def get_path(self):
        # XXX: These empty paths mean the bbox for text elements will be nothing.
        return Path()


class TextElement(ShapeElement, TextBBMixin):
    """A Text element"""

    tag_name = "text"
    x = property(lambda self: self.to_dimensionless(self.get("x", 0)))
    y = property(lambda self: self.to_dimensionless(self.get("y", 0)))

    def get_path(self):
        return Path()

    def tspans(self):
        """Returns all children that are tspan elements"""
        return self.findall("svg:tspan")

    def get_text(self, sep="\n"):
        """Return the text content including tspans and their tail"""
        # Stack of node and depth
        stack = [(self, 0)]
        result = []

        def poptail():
            """Pop the tail from the tail stack and add it if necessary to results"""
            tail = tail_stack.pop()
            if tail is not None:
                result.append(tail)

        # Stack of the tail of nodes
        tail_stack = []
        previous_depth = -1
        while stack:
            # Get current node and depth
            node, depth = stack.pop()

            # Pop the previous tail if the depth do not increase
            if previous_depth >= depth:
                poptail()

            # Pop as many times as the depth is reduced
            for _ in range(previous_depth - depth):
                poptail()

            # Add a node text to the result, if any if node is text or tspan
            if node.text and node.TAG in ["text", "tspan"]:
                result.append(node.text)

            # Add child elements
            stack.extend(
                map(
                    lambda tspan: (tspan, depth + 1),
                    node.iterchildren(reversed=True),
                )
            )

            # Add the tail from node to the stack
            tail_stack.append(node.tail)

            previous_depth = depth

        # Pop remaining tail elements
        # Tail of the main text element should not be included
        while len(tail_stack) > 1:
            poptail()

        return sep.join(result)

    def shape_box(self, transform=None):
        """Returns a horrible bounding box that just contains the coord points
        of the text without width or height (which is impossible to calculate)
        """
        effective_transform = Transform(transform) @ self.transform
        x, y = effective_transform.apply_to_point((self.x, self.y))
        bbox = BoundingBox(x, y)
        for tspan in self.tspans():
            bbox += tspan.bounding_box(effective_transform)
        return bbox


class TextPath(ShapeElement, TextBBMixin):
    """A textPath element"""

    tag_name = "textPath"

    def get_path(self):
        return Path()


class Tspan(ShapeElement, TextBBMixin):
    """A tspan text element"""

    tag_name = "tspan"
    x = property(lambda self: self.to_dimensionless(self.get("x", 0)))
    y = property(lambda self: self.to_dimensionless(self.get("y", 0)))

    @classmethod
    def superscript(cls, text):
        """Adds a superscript tspan element"""
        return cls(text, style="font-size:65%;baseline-shift:super")

    def get_path(self):
        return Path()

    def shape_box(self, transform=None):
        """Returns a horrible bounding box that just contains the coord points
        of the text without width or height (which is impossible to calculate)
        """
        effective_transform = Transform(transform) @ self.transform
        x1, y1 = effective_transform.apply_to_point((self.x, self.y))
        fontsize = self.to_dimensionless(self.style.get("font-size", "12px"))
        x2 = self.x + 0  # XXX This is impossible to calculate!
        y2 = self.y + float(fontsize)
        x2, y2 = effective_transform.apply_to_point((x2, y2))
        return BoundingBox((x1, x2), (y1, y2))


class SVGfont(BaseElement):
    """An svg font element"""

    tag_name = "font"


class FontFace(BaseElement):
    """An svg font font-face element"""

    tag_name = "font-face"


class Glyph(PathElementBase):
    """An svg font glyph element"""

    tag_name = "glyph"


class MissingGlyph(BaseElement):
    """An svg font missing-glyph element"""

    tag_name = "missing-glyph"
