#!python
#
# This file is part of aafigure. https://github.com/aafigure/aafigure
# (C) 2006 Chris Liechti <cliechti@gmx.net>
#
# SPDX-License-Identifier:    BSD-3-Clause
"""\
SVG renderer for the aafigure package.
"""

import sys
import codecs
from xml.sax.saxutils import escape


class SVGOutputVisitor:
    """Render a list of shapes as SVG image."""

    def __init__(self, options):
        self.options = options
        self.file_like = codecs.getwriter('utf-8')(options['file_like'])
        self.scale = options['scale'] * 7
        self.line_width = options['line_width']
        self.foreground = options['foreground']
        self.background = options['background']
        self.fillcolor = options['fill']
        self.border = 3
        self.indent = ''
        # if front is given explicit, use it instead of textual/proportional flags
        if 'font' in options:
            self.font = options['font']
        else:
            if options['proportional']:
                self.font = u'sans-serif'
            else:
                self.font = u'monospace'

    def _num(self, number):
        """helper to scale numbers for svg output"""
        return number * self.scale

    def _coordinate(self, number):
        """helper to scale numbers for svg output"""
        return self._num(number) + self.border

    def get_size_attrs(self):
        """get image size as svg text"""
        # this function is here beacuse of a hack. the rst2html converter
        # has to know the size of the figure it inserts
        return u'width="{}" height="{}"'.format(
            self._num(self.width) + 2 * self.border,
            self._num(self.height) + 2 * self.border)

    def visit_image(self, aa_image, xml_header=True):
        """\
        Process the given ASCIIArtFigure and output the shapes in
        the SVG file
        """
        self.aa_image = aa_image        # save for later XXX not optimal to do it here
        self.width = aa_image.width * aa_image.nominal_size * aa_image.aspect_ratio
        self.height = aa_image.height * aa_image.nominal_size
        if xml_header:
            self.file_like.write(
                u'<?xml version="1.0" standalone="no"?>\n'
                '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" '
                '"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n\n'
                '<svg width="{w}" height="{h}" viewBox="0 0 {w} {h}" version="1.1" xmlns="http://www.w3.org/2000/svg" '
                'xmlns:xlink="http://www.w3.org/1999/xlink">\n'
                '<!-- automatically generated by aafigure -->'.format(
                    w=self._num(self.width) + 2 * self.border,
                    h=self._num(self.height) + 2 * self.border))
        else:
            self.file_like.write(
                u'<svg width="{w}" height="{h}" viewBox="0 0 {w} {h}" version="1.1" '
                'xmlns="http://www.w3.org/2000/svg">\n'.format(
                    w=self._num(self.width) + 2 * self.border,
                    h=self._num(self.height) + 2 * self.border))
        self.visit_shapes(aa_image.shapes)
        self.file_like.write(u'</svg>\n')

    def visit_shapes(self, shapes):
        for shape in shapes:
            shape_name = shape.__class__.__name__.lower()
            visitor_name = 'visit_{}'.format(shape_name)
            if hasattr(self, visitor_name):
                getattr(self, visitor_name)(shape)
            else:
                sys.stderr.write(u"WARNING: don't know how to handle shape {!r}\n".format(shape))

    # - - - - - - SVG drawing helpers - - - - - - -
    def _line(self, x1, y1, x2, y2, thick):
        """Draw a line, coordinates given as four decimal numbers"""
        self.file_like.write(
            u'{}<line x1="{}" y1="{}" x2="{}" y2="{}" stroke="{}" '
            'stroke-width="{}" />\n'.format(
                self.indent,
                self._coordinate(x1),
                self._coordinate(y1),
                self._coordinate(x2),
                self._coordinate(y2),
                self.foreground,
                self.line_width * (1 + bool(thick))))

    def _rectangle(self, x1, y1, x2, y2, style=''):
        """\
        Draw a rectangle, coordinates given as four decimal numbers.
        ``style`` is inserted in the SVG. It could be e.g. "fill:yellow"
        """
        if x1 > x2:
            x1, x2 = x2, x1
        if y1 > y2:
            y1, y2 = y2, y1
        self.file_like.write(
            u'{}<rect x="{}" y="{}" width="{}" height="{}" stroke="{}" '
            'fill="{}" stroke-width="{}" style="{}" />'.format(
                self.indent,
                self._coordinate(x1), self._coordinate(y1),
                self._num(x2 - x1), self._num(y2 - y1),
                self.fillcolor,  # stroke:%s;
                self.fillcolor,
                self.line_width,
                style))

    # - - - - - - visitor function for the different shape types - - - - - - -

    def visit_point(self, point):
        self.file_like.write(
            u'{}<circle cx="{}" cy="{}" r="{}" fill="{}" stroke="{}" '
            'stroke-width="{}" />'.format(
                self.indent,
                self._coordinate(point.x), self._coordinate(point.y),
                self._num(0.2),
                self.foreground, self.foreground,
                self.line_width))

    def visit_line(self, line):
        x1, x2 = line.start.x, line.end.x
        y1, y2 = line.start.y, line.end.y
        self._line(x1, y1, x2, y2, line.thick)

    def visit_rectangle(self, rectangle):
        self._rectangle(
            rectangle.p1.x, rectangle.p1.y,
            rectangle.p2.x, rectangle.p2.y)

    def visit_circle(self, circle):
        self.file_like.write(
            u'{}<circle cx="{}" cy="{}" r="{}" stroke="{}" stroke-width="{}" '
            'fill="{}" />'.format(
                self.indent,
                self._coordinate(circle.center.x), self._coordinate(circle.center.y),
                self._num(circle.radius),
                self.foreground,
                self.line_width,
                self.fillcolor))

    def visit_label(self, label):
        #  font-weight="bold"   style="stroke:%s"
        self.file_like.write(
            u'{}<text x="{}" y="{}" font-family="{}" font-size="{}" '
            'fill="{}" >\n  {}\n{}</text>\n'.format(
                self.indent,
                self._coordinate(label.position.x), self._coordinate(label.position.y - 0.3),  # XXX static offset not good in all situations
                self.font,
                self._num(self.aa_image.nominal_size),
                self.foreground,
                escape(label.text),
                self.indent))

    def visit_group(self, group):
        self.file_like.write(u'<g>\n')
        old_indent = self.indent
        self.indent += u'    '
        self.visit_shapes(group.shapes)
        self.indent = old_indent
        self.file_like.write(u'</g>\n')

    def visit_arc(self, arc):
        p1, p2 = arc.start, arc.end
        c1 = arc.start_control_point()
        c2 = arc.end_control_point()
        self.file_like.write(
            u'{}<path d="M{},{} C{},{} {},{} {},{}" fill="none" '
            'stroke="{}" stroke-width="{}" />'.format(
                self.indent,
                self._coordinate(p1.x), self._coordinate(p1.y),
                self._coordinate(c1.x), self._coordinate(c1.y),
                self._coordinate(c2.x), self._coordinate(c2.y),
                self._coordinate(p2.x), self._coordinate(p2.y),
                self.foreground,
                self.line_width))
