# -*- coding: utf-8 -*-
# This file is part of CairoSVG
# Copyright © 2010-2012 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with CairoSVG.  If not, see <http://www.gnu.org/licenses/>.

"""
Externally defined elements managers.

This module handles gradients and patterns.

"""

import cairo
from math import radians

from .colors import color
from .helpers import filter_fill_content, node_format, preserve_ratio, urls
from .units import size
from ..parser import Tree


def parse_def(surface, node):
    """Parse the SVG definitions."""
    if node.tag == "marker":
        surface.markers[node["id"]] = node
    if "gradient" in node.tag.lower():
        surface.gradients[node["id"]] = node
    if "pattern" in node.tag.lower():
        surface.patterns[node["id"]] = node
    if "path" in node.tag:
        surface.paths[node["id"]] = node


def gradient_or_pattern(surface, node):
    """Gradient or pattern color."""
    name = filter_fill_content(node)
    if name in surface.gradients:
        return gradient(surface, node)
    elif name in surface.patterns:
        return pattern(surface, node)


def gradient(surface, node):
    """Gradients colors."""
    gradient_node = surface.gradients[filter_fill_content(node)]

    x = float(size(node.get("x")))
    y = float(size(node.get("y")))
    height = float(size(node.get("height")))
    width = float(size(node.get("width")))
    x1 = float(gradient_node.get("x1", x))
    x2 = float(gradient_node.get("x2", x + width))
    y1 = float(gradient_node.get("y1", y))
    y2 = float(gradient_node.get("y2", y + height))

    if gradient_node.tag == "linearGradient":
        linpat = cairo.LinearGradient(x1, y1, x2, y2)
        for child in gradient_node.children:
            offset = child.get("offset")
            stop_color = color(
                child.get("stop-color"), child.get("stop-opacity", 1))
            offset = child.get("offset")
            if "%" in offset:
                offset = float(offset.strip("%")) / 100
            linpat.add_color_stop_rgba(float(offset), *stop_color)
        surface.context.set_source(linpat)
        surface.context.fill_preserve()
    elif gradient_node.tag == "radialGradient":
        r = float(gradient_node.get("r"))
        cx = float(gradient_node.get("cx"))
        cy = float(gradient_node.get("cy"))
        fx = float(gradient_node.get("fx", cx))
        fy = float(gradient_node.get("fy", cy))
        radpat = cairo.RadialGradient(fx, fy, 0, cx, cy, r)

        for child in gradient_node.children:
            offset = child.get("offset")
            if "%" in offset:
                offset = float(offset.strip("%")) / 100
            stop_color = color(
                child.get("stop-color"), child.get("stop-opacity", 1))
            radpat.add_color_stop_rgba(float(offset), *stop_color)
        surface.context.set_source(radpat)
        surface.context.fill_preserve()


def linear_gradient(surface, node):
    """Store a linear gradient definition."""
    parse_def(surface, node)


def radial_gradient(surface, node):
    """Store a radial gradient definition."""
    parse_def(surface, node)


def pattern(surface, node):
    """Draw a pattern image."""
    pattern_node = surface.patterns[filter_fill_content(node)]
    if pattern_node.tag == "pattern":
        from . import SVGSurface  # circular import
        pattern_surface = SVGSurface(tree=pattern_node, output=None)
        pattern_pattern = cairo.SurfacePattern(pattern_surface.cairo)
        pattern_pattern.set_extend(cairo.EXTEND_REPEAT)
        surface.context.set_source(pattern_pattern)
        surface.context.fill_preserve()
        pattern_surface.finish()


def draw_marker(surface, node, position="mid"):
    """Draw a marker."""
    # TODO: manage markers for other tags than path
    if position == "start":
        node.markers = {
            "start": list(urls(node.get("marker-start", ""))),
            "mid": list(urls(node.get("marker-mid", ""))),
            "end": list(urls(node.get("marker-end", "")))}
        all_markers = list(urls(node.get("marker", "")))
        for markers_list in node.markers.values():
            markers_list.extend(all_markers)
    pending_marker = (
        surface.context.get_current_point(), node.markers[position])

    if position == "start":
        node.pending_markers.append(pending_marker)
        return
    elif position == "end":
        node.pending_markers.append(pending_marker)

    while node.pending_markers:
        next_point, markers = node.pending_markers.pop(0)

        if node.tangents != []:
            angle1 = node.tangents.pop(0)
        if node.tangents != []:
            angle2 = node.tangents.pop(0)

        for active_marker in markers:
            if not active_marker.startswith("#"):
                continue
            active_marker = active_marker[1:]
            if active_marker in surface.markers:
                marker_node = surface.markers[active_marker]

                angle = marker_node.get("orient", "0")
                if angle == "auto":
                    if angle1 is None:
                        angle1 = angle2
                    angle = float(angle1 + angle2) / 2
                else:
                    angle = radians(float(angle))

                temp_path = surface.context.copy_path()
                current_x, current_y = next_point

                if node.get("markerUnits") == "userSpaceOnUse":
                    base_scale = 1
                else:
                    base_scale = size(surface.parent_node.get("stroke-width"))

                # Returns 4 values
                scale_x, scale_y, translate_x, translate_y = \
                    preserve_ratio(surface, marker_node)

                viewbox = node_format(marker_node)[-1]
                viewbox_width = viewbox[2] - viewbox[0]
                viewbox_height = viewbox[3] - viewbox[1]

                surface.context.new_path()
                for child in marker_node.children:
                    surface.context.save()
                    surface.context.translate(current_x, current_y)
                    surface.context.rotate(angle)
                    surface.context.scale(
                        base_scale / viewbox_width * float(scale_x),
                        base_scale / viewbox_height * float(scale_y))
                    surface.context.translate(translate_x, translate_y)
                    surface.draw(child)
                    surface.context.restore()
                surface.context.append_path(temp_path)

    if position == "mid":
        node.pending_markers.append(pending_marker)


def marker(surface, node):
    """Store a marker definition."""
    parse_def(surface, node)


def use(surface, node):
    """Draw the content of another SVG file."""
    surface.context.save()
    surface.context.translate(size(node.get("x")), size(node.get("y")))
    if "x" in node:
        del node["x"]
    if "y" in node:
        del node["y"]
    if "viewBox" in node:
        del node["viewBox"]
    href = node.get("{http://www.w3.org/1999/xlink}href")
    tree = Tree(url=href, parent=node)
    surface._set_context_size(*node_format(tree))
    surface.draw(tree)
    surface.context.restore()
    # Restore twice, because draw does not restore at the end of svg tags
    surface.context.restore()
