#! /usr/bin/python3
#
# This generates the trump precedence chart
# It is a bit of an awful hack but it gets the job done.
# The arrows are particularly fragile: change some of the
# chart settings and they will likely break.

class Chart:
    def __init__(
            self,
            chart_width: int,
            chart_height: int,
            box_width: int,
            box_height: int,
            gutter: int,
            x: int,
            y: int,
            y_offset: int,
            radius: int
        ):
        self.chart_width  = chart_width
        self.chart_height = chart_height
        self.box_width    = box_width
        self.box_height   = box_height
        self.gutter       = gutter
        self.x            = x
        self.y            = y
        self.y_offset     = y_offset
        self.radius       = radius
        self.column_count = 0
        self.dist         = 10 # spline distance for arcs
        self.gradient = [
            "#3dda07",
            "#46c819",
            "#43b72a",
            "#56a63b",
            "#5f944d",
        ]
        self.head_css = "font-family=\"sans-serif\" font-size=\"14\" font-weight=\"bolder\" dominant-baseline=\"middle\" text-anchor=\"middle\""
        self.text_css = "font-family=\"sans-serif\" font-size=\"12\" font-weight=\"bold\" dominant-baseline=\"middle\" text-anchor=\"middle\""
        self.path_color = "#dffcd7"
        self.box_color = "#ebf9e7"

    def head_box(self, x: int, y: int, height: int, text: str, bg_color: str) -> str:
        para = text.split('/')
        box = f"<rect x=\"{x}\" y=\"{y}\" width=\"{self.box_width}\" height=\"{height}\" rx=\"3\" ry=\"3\" fill=\"{bg_color}\" stroke=\"{self.box_color}\" stroke-width=\"2\" />\n"
        x_mid = x + int(self.box_width / 2)
        y_begin = y + int(height / (len(para) + 1)) + 3
        for line in para:
            box += f"<text x=\"{x_mid}\" y=\"{y_begin}\" {self.head_css}>{line}</text>\n"
            y_begin += 18
        return box + "\n"

    def box(self, x: int, y: int, height: int, text: str, bg_color: str) -> str:
        para = text.split('/')
        box = f"<rect x=\"{x}\" y=\"{y}\" width=\"{self.box_width}\" height=\"{height}\" rx=\"3\" ry=\"3\" fill=\"{bg_color}\" stroke=\"{self.box_color}\" stroke-width=\"1\" />\n"
        x_mid = x + int(self.box_width / 2)
        y_begin = y + int(height / (len(para) + 1)) + 2
        for line in para:
            box += f"<text x=\"{x_mid}\" y=\"{y_begin}\" {self.text_css}>{line}</text>\n"
            y_begin += 16
        return box

    def line(self, x1: int, y1: int, x2: int, y2: int) -> str:
        return f"<line x1=\"{x1}\" y1=\"{y1}\" x2=\"{x2}\" y2=\"{y2}\" stroke-width=\"2\" stroke=\"{self.path_color}\" />\n"

    def arc_ne(self, x: int, y: int) -> str:
        return f"<path d=\"M {x} {y} C {x} {y-self.dist}, {x+self.dist} {y-self.radius}, {x+self.radius} {y-self.radius}\" fill=\"transparent\" stroke-width=\"2\" stroke=\"{self.path_color}\"/>\n"

    def arc_nw(self, x: int, y: int) -> str:
        return f"<path d=\"M {x} {y} C {x} {y-self.dist}, {x-self.dist} {y-self.radius}, {x-self.radius} {y-self.radius}\" fill=\"transparent\" stroke-width=\"2\" stroke=\"{self.path_color}\"/>\n"

    def arc_se(self, x: int, y: int) -> str:
        return f"<path d=\"M {x} {y} C {x} {y+self.dist}, {x+self.dist} {y+self.radius}, {x+self.radius} {y+self.radius}\" fill=\"transparent\" stroke-width=\"2\" stroke=\"{self.path_color}\"/>\n"

    def arc_sw(self, x: int, y: int) -> str:
        return f"<path d=\"M {x} {y} C {x} {y+self.dist}, {x-self.dist} {y+self.radius}, {x-self.radius} {y+self.radius}\" fill=\"transparent\" stroke-width=\"2\" stroke=\"{self.path_color}\"/>\n"

    def side_arrow_head(self, x: int, y: int, direction: int) -> str:
        return f"<polygon points=\"{x},{y} {x - 6 * direction},{y - 3} {x - 6 * direction},{y + 3}\" fill=\"{self.path_color}\" stroke-width=\"2\" stroke=\"{self.path_color}\" />\n"

    def up_arrow_head(self, x: int, y: int) -> str:
        return f"<polygon points=\"{x},{y - 8} {x - 4},{y} {x + 4},{y}\" fill=\"{self.path_color}\" stroke=\"{self.path_color}\" />\n"

    def up_arrow(self, x: int, y: int) -> str:
        x_mid = x + int(self.box_width/2)
        return f"<line x1=\"{x_mid}\" y1=\"{y}\" x2=\"{x_mid}\" y2=\"{y - 10}\" stroke-width=\"2\" stroke=\"{self.path_color}\" />\n" + self.up_arrow_head(x_mid, y - 10) + "\n"

    def column(self, name: str, dependent: list[str]):
        x = self.x + self.column_count * (self.box_width + self.gutter)
        self.column_count += 1
        y = self.box_height
        out = self.head_box(x, y - 20, self.box_height + 20, name, "#abe797")

        n = 0
        for item in dependent:
            y += self.y_offset
            out += self.box(x, y, self.box_height, item, self.gradient[n]) + self.up_arrow(x, y)
            n += 1

        return out

    def dependent_column(self, right: bool, dependent: dict) -> str:
        x = self.x + self.column_count * (self.box_width + self.gutter)
        y = self.box_height

        # curving arrow to each adjacent columns
        out = ""
        for direction in [-1, 1]:
            if direction == 1 and not right:
                continue
            x_mid = int(self.box_width / 2)
            x_begin = x + x_mid
            x_end = x_begin + (x_mid + self.gutter - 3) * direction
            y_begin = y + self.y_offset
            y_end = y_begin - self.box_height - 10
            out += f"\n<path d=\"M {x_begin} {y_begin} C {x_begin} {y_begin-self.box_height+5}, {x_end-40 * direction} {y_end}, {x_end} {y_end}\" fill=\"transparent\" stroke-width=\"2\" stroke=\"{self.path_color}\"/>\n"
            out += self.side_arrow_head(x_end, y_end, direction) + "\n"

        n = 0
        for item in dependent:
            y += self.y_offset
            out += self.box(x, y, self.box_height, item, self.gradient[n])
            if n:
                out += self.up_arrow(x, y)
            n += 1

        self.column_count += 1
        return out

    def horizontal(self, top: int, color: str, dependent: list[str]) -> str:
        x = self.gutter
        y = top
        x_offset = self.box_width + self.gutter
        out = ""
        n = 0
        for item in dependent:
            y_mid = y + int(self.box_height / 2)
            out += self.box(x, y, self.box_height, item, color)
            if n:
                out +=f"<line x1=\"{x - self.gutter}\" y1=\"{y_mid}\" x2=\"{x - 2}\" y2=\"{y_mid}\" stroke-width=\"2\" stroke=\"{self.path_color}\" />\n" + self.side_arrow_head(x - 2, y_mid, 1)
            n += 1
            x += x_offset
        return out

    def svg(self) -> str:
        out = (f"""
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="{self.chart_height}" width="{self.chart_width}">
<rect width="{self.chart_width}" height="{self.chart_height}" rx="8" ry="8" fill="#112709" stroke="#000" stroke-width="2" />
        """).strip()

        out += self.column("MP3 V0", [
            "V2",
            "any VBR",
        ])

        self.column_count += 1
        cbr = [
            "256 CBR",
            "224 CBR",
            "192 CBR",
            "&lt; 192 CBR",
        ]

        out += self.column("MP3 320", cbr)

        # draw the arrows from the CBR boxes to V0
        x = self.x + 2 * (self.box_width + self.gutter)
        x_end = x - 59
        y = 3 * (self.box_height)
        for n in range(0, len(cbr)):
            out += self.line(x, y, x_end, y)
            out += self.arc_se(x_end - self.radius, y - self.radius)
            y += self.y_offset

        y_top = self.chart_height - self.box_height - 20
        out += self.horizontal(y_top, self.gradient[4], [
            "192 AAC",
            "256 AAC",
            "320 AAC",
        ])

        # line from first horizontal box
        x = self.gutter + int(self.box_width / 2)
        out += self.arc_ne(x, y_top)
        out += self.arc_sw(x + self.box_width + self.gutter, y_top - self.radius * 2)
        x += self.radius - 1
        x_end = x + int((self.box_width + self.gutter) / 2) + 20
        y = y_top - self.radius
        out += self.line(x, y, x_end, y)

        # line from middle horizontal box
        x_end += self.radius
        y_end = self.chart_height - self.box_height - 80
        out += self.line(x_end, y_end + self.radius * 2, x_end, y_end)

        # line from third horizontal box
        out += self.arc_se(x_end, y_end)
        x = int(self.gutter) / 2 + self.box_width * 2 - 10
        x_end = x + self.box_width - self.gutter + 10
        out += self.arc_nw(x_end, y_top)

        y = y_top - self.radius
        out += self.line(x, y, x_end - self.radius, y)

        # line to top
        x = self.x + int(self.gutter + 3/2 * self.box_width)
        y_begin = self.y + int(self.box_height * 2 - 20)
        out += self.line(x, y_begin, x, y_end)
        out += self.arc_nw(x, y_begin)
        y = y_begin - self.radius
        x_end = x - int(self.box_width / 2) - self.gutter + 2
        out += self.line(x - self.radius, y, x_end, y)
        out += self.side_arrow_head(x_end, y, -1)

        out += self.column("FLAC 100% Log/+ Cue + Checksum", [
            "FLAC 100% Log + Cue/No Checksum",
            "FLAC 100% Log/No Checksum",
        ])

        out += self.dependent_column(False, [
            "FLAC &lt; 100% Log/or No log",
        ])

        return out + "</svg>"

c = Chart(
    810,
    450,
    138, # box width
    40,  # box height
    20,  # gutter
    20,  # x
    20,  # y
    60,  # y_offset, how far below the next box is placed
    30,  # radius of arcs
)

print(c.svg())
