# The MIT License (MIT)
#
# Copyright (c) 2011-2018 Aliaksei Chapyzhenka, BreizhGeek, Kazuki Yamamoto,
#                         Stefan Wallentowitz
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Originally translated to Python from original file:
# https://github.com/drom/wavedrom/blob/master/src/WaveDrom.js
# Now many parts have been rewritten and diverged

import sys
import math
import re
from itertools import chain
import svgwrite
from attrdict import AttrDict
from collections import deque

from six import string_types

from wavedrom.tspan import JsonMLElement
from . import waveskin, css
from .base import SVGBase


class WaveDrom(SVGBase):
    def __init__(self):
        self.font_width = 7
        self.lane = AttrDict({
            "xs": 20,    # tmpgraphlane0.width
            "ys": 20,    # tmpgraphlane0.height
            "xg": 120,   # tmpgraphlane0.x
            "yg": 0,     # head gap
            "yh0": 0,     # head gap title
            "yh1": 0,     # head gap
            "yf0": 0,     # foot gap
            "yf1": 0,     # foot gap
            "y0": 5,     # tmpgraphlane0.y
            "yo": 30,    # tmpgraphlane1.y - y0
            "tgo": -10,   # tmptextlane0.x - xg
            "ym": 15,    # tmptextlane0.y - y0
            "xlabel": 6,     # tmptextlabel.x - xg
            "xmax": 1,
            "scale": 1,
            "head": {},
            "foot": {}
        })

    @staticmethod
    def stretch_bricks(wave, stretch):
        stretcher = {
            "Pclk": "111", "Nclk": "000",
            "pclk": "111", "nclk": "000",
            "0": "000", "1": "111", "x": "xxx", "d": "ddd", "u": "uuu", "z": "zzz",
            "2": "vvv-2", "3": "vvv-3", "4": "vvv-4", "5": "vvv-5"}

        if stretch == -0.5:
            # This is the only valid non-integer value, it essentially means halfing down. Further subsampling
            # does not work I think..
            return wave[0::2]
        else:
            stretch = int(stretch)

            def getBrick(w):
                if w in stretcher:
                    return stretcher[w]
                elif w[2] in stretcher:
                    return stretcher[w[2]]
                else:
                    return stretcher[w[-1]]

            if stretch > 0:
                return list(chain.from_iterable(([w] + [getBrick(w)]*stretch for w in wave)))
            else:
                return wave

    def gen_wave_brick(self, prev=None, this=None, stretch=0, repeat=0, subcycle=False):
        sharpedge_clk = { "p": "pclk", "n": "nclk", "P": "Pclk", "N": "Nclk" }
        sharpedge_sig = { "h": "pclk", "l": "nclk", "H": "Pclk", "L": "Nclk" }
        sharpedge = sharpedge_clk.copy()
        sharpedge.update(sharpedge_sig)

        # level: logical levels of symbols at wave
        level = {"=": "v", "2": "v", "3": "v", "4": "v", "5": "v", "h": "1", "H": "1", "l": "0", "L": "0"}
        # translevel: Those are the levels at the end of a cycle (special for clocks)
        translevel = level.copy()
        translevel.update({"p": "0", "P": "0", "n": "1", "N": "1"})
        # data: Modifiers of wavebricks that add data
        data = {"=": "-2", "2": "-2", "3": "-3", "4": "-4", "5": "-5"}
        # clkinvert: The inverse brick to clock symbols
        clkinvert = {"p": "nclk", "n": "pclk", "P": "nclk", "N": "pclk"}
        # xclude: Those are actually identical levels, no transition
        xclude = {"hp": "111", "Hp": "111", "ln": "000", "Ln": "000",
                  "nh": "111", "Nh": "111", "pl": "000", "Pl": "000"}

        if this in sharpedge.keys():
            if prev is None:
                if this in sharpedge_clk.keys():
                    first = sharpedge[this]
                else:
                    first = level.get(this, this)*3
            else:
                first = xclude.get(prev+this, sharpedge[this])

            if this in sharpedge_clk.keys():
                wave = [first, clkinvert[this]] * (1 + repeat)
            else:
                wave = [first] + [level.get(this, this)*3]*(2 * repeat + 1)
        else:
            if prev is None:
                transition = level.get(this, this)*3 + data.get(this, "")
            else:
                transition = translevel.get(prev, prev) + 'm' + level.get(this, this) + data.get(prev, "") + data.get(this, "")
            value = level.get(this, this)*3 + data.get(this, "")
            wave = [transition, value] + [value, value] * repeat

        if subcycle:
            wave = wave[0:repeat+1]

        if not (stretch == -0.5 and this in sharpedge_clk.keys()):
            wave = self.stretch_bricks(wave, stretch)

        return wave

    def parse_wave_lane(self, text, stretch=0):
        R = []

        Stack = deque(text)

        This = None
        subCycle = False

        while len(Stack) > 0:
            Top = This
            This = Stack.popleft()
            repeat = 0
            if This == '|':
                This = 'x'
            if This == '<':
                subCycle = True
                This = Top
                Top = None
                if Stack[0] in ['.', '|']:
                    Stack.popleft()
                else:
                    continue
            if This == '>':
                subCycle = False
                This = Top
                Top = None
                if Stack and Stack[0] in ['.', '|']:
                    Stack.popleft()
                else:
                    continue
            while Stack and Stack[0] in ['.', '|']:
                Stack.popleft()
                repeat += 1
            R.extend(self.gen_wave_brick(Top, This, stretch, repeat, subCycle))

        for i in range(int(math.ceil(self.lane.phase))):
            R = R[1:]

        return R

    def parse_wave_lanes(self, sig=""):
        def data_extract(e):
            tmp = e.get("data")
            if tmp is not None:
                tmp = tmp.split() if isinstance(tmp, string_types) else tmp
            return tmp

        content = []
        for sigx in sig:
            self.lane.period = sigx.get("period", 1)
            self.lane.phase = sigx.get("phase", 0) * 2
            sub_content = []
            sub_content.append([sigx.get("name", " "), sigx.get("phase", 0)])
            if sigx.get("wave"):
                sub_content.append(self.parse_wave_lane(sigx["wave"], self.lane.period * self.lane.hscale - 1))
            else:
                sub_content.append(None)
            sub_content.append(data_extract(sigx))
            content.append(sub_content)

        return content

    def find_lane_markers(self, lanetext=""):

        lcount = 0
        gcount = 0
        ret = []
        for idx, val in enumerate(lanetext):
            if val in ["vvv-2", "vvv-3", "vvv-4", "vvv-5"]:
                lcount += 1
            else:
                if lcount != 0:
                    ret.append(gcount - ((lcount + 1) / 2))
                    lcount = 0

            gcount += 1

        if lcount != 0:
            ret.append(gcount - ((lcount + 1) / 2))

        return ret

    def render_lane_uses(self, val, g):
        if val[1]:
            for i in range(len(val[1])):
                b = self.container.use(href="#{}".format(val[1][i]))
                b.translate(i * self.lane.xs)
                g.add(b)

            if val[2] and len(val[2]):
                labels = self.find_lane_markers(val[1])
                if len(labels) != 0:
                    for k in range(len(labels)):
                        if val[2] and k < len(val[2]):
                            tx = int(labels[k]) * self.lane.xs + self.lane.xlabel
                            title = self.element.text("", x=[tx], y=[self.lane.ym], text_anchor="middle")
                            title.add(self.element.tspan(val[2][k]))
                            title["xml:space"] = "preserve"
                            g.add(title)

    def text_width(self, string, size=11):
        chars = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,34,47,74,74,118,89,25,44,44,52,78,37,
                 44,37,37,74,74,74,74,74,74,74,74,74,74,37,37,78,78,78,74,135,89,89,96,96,89,81,103,96,37,67,89,74,109,
                 96,103,89,103,96,89,81,96,89,127,89,87,81,37,37,37,61,74,44,74,74,67,74,74,37,74,74,30,30,67,30,112,74,
                 74,74,74,44,67,37,74,67,95,66,65,67,44,34,44,78,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                 0,0,0,0,0,0,37,43,74,74,74,74,34,74,44,98,49,74,78,0,98,73,53,73,44,44,44,77,71,37,44,44,49,74,111,111,
                 111,81,89,89,89,89,89,89,133,96,89,89,89,89,37,37,37,37,96,96,103,103,103,103,103,78,103,96,96,96,96,
                 87,89,81,74,74,74,74,74,74,118,67,74,74,74,74,36,36,36,36,74,74,74,74,74,74,74,73,81,74,74,74,74,65,74,
                 65,89,74,89,74,89,74,96,67,96,67,96,67,96,67,96,82,96,74,89,74,89,74,89,74,89,74,89,74,103,74,103,74,
                 103,74,103,74,96,74,96,74,37,36,37,36,37,36,37,30,37,36,98,59,67,30,89,67,67,74,30,74,30,74,39,74,44,
                 74,30,96,74,96,74,96,74,80,96,74,103,74,103,74,103,74,133,126,96,44,96,44,96,44,89,67,89,67,89,67,89,
                 67,81,38,81,50,81,37,96,74,96,74,96,74,96,74,96,74,96,74,127,95,87,65,87,81,67,81,67,81,67,30,84,97,91,
                 84,91,84,94,92,73,104,109,91,84,81,84,100,82,76,74,103,91,131,47,40,99,77,37,79,130,100,84,104,114,87,
                 126,101,87,84,93,84,69,84,46,52,82,52,82,114,89,102,96,100,98,91,70,88,88,77,70,85,89,77,67,84,39,65,
                 61,39,189,173,153,111,105,61,123,123,106,89,74,37,30,103,74,96,74,96,74,96,74,96,74,96,74,81,91,81,91,
                 81,130,131,102,84,103,84,87,78,104,81,104,81,88,76,37,189,173,153,103,84,148,90,100,84,89,74,133,118,
                 103,81]

        return sum([(chars[ord(c)] if ord(c) <= len(chars) else 114) for c in string])*size/100

    def render_wave_lane(self, content="", index=0):
        xmax = 0
        xgmax = 0
        glengths = []
        groups = []

        for j, val in enumerate(content):
            name = val[0][0].strip()
            if name is not None:
                dy = self.lane.y0 + j * self.lane.yo
                g = self.container.g(id="wavelane_{j}_{index}".format(j=j, index=index))
                g.translate(0, dy)
                title = self.element.text("", x=[self.lane.tgo], y=[self.lane.ym], text_anchor="end")
                title.add(self.element.tspan(name))
                title["xml:space"] = "preserve"
                title["class"] = "info"
                g.add(title)

                glengths.append(self.text_width(name))

                xoffset = val[0][1]
                xoffset = math.ceil(2 * xoffset) - 2 * xoffset if xoffset > 0 else -2 * xoffset
                gg = self.container.g(id="wavelane_draw_{j}_{index}".format(j=j, index=index))
                gg.translate(xoffset * self.lane.xs, 0)

                self.render_lane_uses(val, gg)

                if val[1] and len(val[1]) > xmax:
                    xmax = len(val[1])

                g.add(gg)
                groups.append(g)
        self.lane.xmax = xmax
        self.lane.xg = xgmax + 20
        return (glengths, groups)

    def captext(self, g, cxt, anchor, y):
        if cxt.get(anchor) and cxt[anchor].get("text"):
            tmark = self.element.text("", x=[float(cxt.xmax)*float(cxt.xs)/2], y=[y], text_anchor="middle", fill="#000")
            tmark["xml:space"] = "preserve"
            if isinstance(cxt[anchor]["text"], string_types):
                tmark.add(self.element.tspan(cxt[anchor]["text"]))
            else:
                tmark.add(JsonMLElement(cxt[anchor]["text"]))
            g.add(tmark)

    def ticktock(self, g, cxt, ref1, ref2, x, dx, y, length):
        L = []

        if cxt.get(ref1) is None or cxt[ref1].get(ref2) is None:
            return

        val = cxt[ref1][ref2]

        if isinstance(val, string_types):
            val = val.split()
        elif isinstance(val, (int, float, bool)):
            offset = int(val)
            val = []
            for i in range(length):
                val.append(i + offset)

        if type(val) is list:
            if len(val) == 0:
                return
            elif len(val) == 1:
                offset = val[0]
                if isinstance(offset, string_types):
                    L = val
                else:
                    for i in range(length):
                        L[i] = i + offset
            elif len(val) == 2:
                offset = int(val[0])
                step = int(val[1])
                tmp = val[1].split(".")
                if len(tmp) == 2:
                    dp = len(tmp[1])

                if isinstance(offset, string_types) or isinstance(step, string_types):
                    L = val
                else:
                    offset = step * offset
                    for i in range(length):
                        L[i] = "{0:.", dp, "f}".format(step * i + offset)
            else:
                L = val

        else:
            return

        for i in range(length):
            tmp = L[i]
            tmark = self.element.text(tmp, x=[i * dx + x], y=[y], text_anchor="middle")
            tmark["class"] = "muted"
            tmark["xml:space"] = "preserve"
            g.add(tmark)

    def render_marks(self, content="", index=0):
        def get_elem(e):
            if len(e) == 3:
                ret = self.element[e[0]](e[2])
                ret.attribs = e[1]
            elif len(e) == 2:
                ret = self.element[e[0]](e[1])
            else:
                ret = self.element.tspan(e)
            return ret

        mstep = 2 * int(self.lane.hscale)
        mmstep = mstep * self.lane.xs
        marks = int(self.lane.xmax / mstep)
        gy = len(content) * int(self.lane.yo)

        g = self.container.g(id="gmarks_{}".format(index))

        for i in range(marks + 1):
            gg = self.element.path(id="gmark_{i}_{index}".format(i=i, index=index),
                                   d="m {dx},0 0,{gy}".format(dx=i * mmstep, gy=gy),
                                   style="stroke:#888;stroke-width:0.5;stroke-dasharray:1,3")
            g.add(gg)

        self.captext(g, self.lane, "head", -33 if (self.lane.yh0 > 0) else -13)
        self.captext(g, self.lane, "foot", gy + (45 if (self.lane.yf0 > 0) else 25))
        self.ticktock(g, self.lane, "head", "tick",          0, mmstep,      -5, marks + 1)
        self.ticktock(g, self.lane, "head", "tock", mmstep / 2, mmstep,      -5, marks)
        self.ticktock(g, self.lane, "foot", "tick",          0, mmstep, gy + 15, marks + 1)
        self.ticktock(g, self.lane, "foot", "tock", mmstep / 2, mmstep, gy + 15, marks)

        return g

    def render_labels(self, root, source, index):
        if source:
            gg = self.container.g(id="labels_{index}".format(index=index))

            for idx, val in enumerate(source):
                self.lane.period = val.get("period", 1)
                self.lane.phase = val.get("phase", 0) * 2

                dy = self.lane.y0 + idx * self.lane.yo
                g = self.container.g(id="labels_{i}_{index}".format(i=idx, index=index))
                g.translate(0, dy)

                label = val.get("label")
                if label:
                    pos = 0
                    for l in re.findall(r"([\.\w]|(?:\{\w+\}))(?:\((\d*\.?\d+)\))?", label):
                        if l[0] == ".":
                            pos += 1
                            continue

                        text = l[0]
                        try:
                            offset = float(l[1])
                        except ValueError:
                            offset = 0

                        m = re.match(r"\{(\w+)\}", l[0])
                        if m:
                            text = m.group(1)
                        x = int(float(self.lane.xs) * (2 * (pos + offset) * self.lane.period *
                                                       self.lane.hscale - self.lane.phase) + float(self.lane.xlabel))
                        y = int(idx * self.lane.yo + self.lane.y0 + float(self.lane.ys) * 0.5) - dy

                        lwidth = len(text) * self.font_width
                        lx = float(x) - float(lwidth) / 2
                        ly = int(y) - 5
                        underlabel = self.element.rect(insert=(lx, ly),
                                                       size=(lwidth, 8), style="fill:#FFF;")
                        g.add(underlabel)
                        lx = float(x)
                        ly = int(y) + 2
                        label = self.element.text(text, style="font-size:8px;", text_anchor="middle",
                                                  x=[lx], y=[ly])
                        g.add(label)
                        pos += 1
                gg.add(g)
            root.add(gg)

    def arc_shape(self, Edge, frm, to):
        dx = float(to.x) - float(frm.x)
        dy = float(to.y) - float(frm.y)
        lx = (float(frm.x) + float(to.x)) / 2
        ly = (float(frm.y) + float(to.y)) / 2

        const_style = AttrDict({
            "a": "marker-end:url(#arrowhead);stroke:#0041c4;stroke-width:1;fill:none",
            "b": "marker-end:url(#arrowhead);marker-start:url(#arrowtail);stroke:#0041c4;stroke-width:1;fill:none"
        })

        pattern = {
            "-": { },
            "~": {"d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
                                                                                  dx=(0.7 * dx), dy=0,
                                                                                  dxx=(0.3 * dx), dyy=dy,
                                                                                  dxxx=dx, dyyy=dy)},
            "-~": {"d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
                                                                                   dx=(0.7 * dx), dy=0,
                                                                                   dxx=dx, dyy=dy,
                                                                                   dxxx=dx, dyyy=dy)},
            "~-": {"d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
                                                                                   dx=0, dy=0,
                                                                                   dxx=(0.3 * dx), dyy=dy,
                                                                                   dxxx=dx, dyyy=dy)},
            "-|": {"d": "m {fx},{fy} {dx},{dy} {dxx},{dyy}".format(fx=frm.x, fy=frm.y,
                                                                   dx=dx, dy=0,
                                                                   dxx=0, dyy=dy)},
            "|-": {"d": "m {fx},{fy} {dx},{dy} {dxx},{dyy}".format(fx=frm.x, fy=frm.y,
                                                                   dx=0, dy=dy,
                                                                   dxx=dx, dyy=0)},
            "-|-": {"d": "m {fx},{fy} {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
                                                                                  dx=(dx / 2), dy=0,
                                                                                  dxx=0, dyy=dy,
                                                                                  dxxx=(dx / 2), dyyy=0)},
            "->": {"style": const_style.a},
            "~>": {"style": const_style.a,
                   "d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
                                                                                   dx=(0.7 * dx), dy=0,
                                                                                   dxx=(0.3 * dx), dyy=dy,
                                                                                   dxxx=dx, dyyy=dy)},
            "-~>": {"style": const_style.a,
                    "d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
                                                                                    dx=(0.7 * dx), dy=0,
                                                                                    dxx=dx, dyy=dy,
                                                                                    dxxx=dx, dyyy=dy)},
            "~->": {"style": const_style.a,
                    "d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
                                                                                    dx=0, dy=0,
                                                                                    dxx=(0.3 * dx), dyy=dy,
                                                                                    dxxx=dx, dyyy=dy)},
            "-|>": {"style": const_style.a,
                    "d": "m {fx},{fy} {dx},{dy} {dxx},{dyy}".format(fx=frm.x, fy=frm.y,
                                                                    dx=dx, dy=0,
                                                                    dxx=0, dyy=dy)},
            "|->": {"style": const_style.a,
                    "d": "m {fx},{fy} {dx},{dy} {dxx},{dyy}".format(fx=frm.x, fy=frm.y,
                                                                    dx=0, dy=dy,
                                                                    dxx=dx, dyy=0
                                                                    )},
            "-|->": {"style": const_style.a,
                     "d": "m {fx},{fy} {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
                                                                                   dx=(dx / 2), dy=0,
                                                                                   dxx=0, dyy=dy,
                                                                                   dxxx=(dx / 2), dyyy=0
                                                                                   )},
            "<->": {"style": const_style.b},
            "<~>": {"style": const_style.b,
                    "d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
                                                                                    dx=(0.7 * dx), dy=0,
                                                                                    dxx=(0.3 * dx), dyy=dy,
                                                                                    dxxx=dx, dyyy=dy
                                                                                    )},
            "<-~>": {"style": const_style.b,
                     "d": "M {fx},{fy} c {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
                                                                                     dx=(0.7 * dx), dy=0,
                                                                                     dxx=dx, dyy=dy,
                                                                                     dxxx=dx, dyyy=dy
                                                                                     )},
            "<-|>": {"style": const_style.b,
                     "d": "m {fx},{fy} {dx},{dy} {dxx},{dyy}".format(fx=frm.x, fy=frm.y,
                                                                     dx=dx, dy=0,
                                                                     dxx=0, dyy=dy
                                                                     )},
            "<-|->": {"style": const_style.b,
                      "d": "m {fx},{fy} {dx},{dy} {dxx},{dyy} {dxxx},{dyyy}".format(fx=frm.x, fy=frm.y,
                                                                                    dx=(dx / 2), dy=0,
                                                                                    dxx=0, dyy=dy,
                                                                                    dxxx=(dx / 2), dyyy=0,
                                                                                    )}
        }

        props = AttrDict({"lx": lx, "ly": ly, "style": "fill:none;stroke:#00F;stroke-width:1",
                           "d": "M {fx},{fy} {tx},{ty}".format(fx=frm.x, fy=frm.y, tx=to.x, ty=to.y)})

        if Edge.shape in pattern:
            props.d = pattern[Edge.shape].get("d", props.d)
            props.style = pattern[Edge.shape].get("style", props.style)

            if Edge.label:
                if Edge.shape in ["-~", "-~>", "<-~>"]:
                    props.lx = float(frm.x) + (float(to.x) - float(frm.x)) * 0.75
                elif Edge.shape in ["~-", "~->"]:
                    props.lx = float(frm.x) + (float(to.x) - float(frm.x)) * 0.25
                elif Edge.shape in ["-|", "-|>", "<-|>"]:
                    props.lx = float(to.x)
                elif Edge.shape in ["|-", "|->"]:
                    props.lx = float(frm.x)

        return props

    def render_arc(self, Edge, frm, to, shapeProps):
        return self.element.path(id="gmark_{frm}_{to}".format(frm=Edge.frm, to=Edge.to),
                                  d=shapeProps.d, style=shapeProps.style)

    def render_label(self, p, text):
        w = self.text_width(text,8) + 2
        g = self.container.g(transform = "translate({},{})".format(p.x, p.y))
        # todo: I don't think this is correct. reported:
        # https://github.com/wavedrom/wavedrom/issues/252
        rect = self.element.rect(insert=(int(0-w/2), -5), size=(w, 10), style="fill:#FFF;")
        label = self.element.text("", style="font-size:8px;", text_anchor="middle", y=[3])
        label.add(self.element.tspan(text))
        g.add(rect)
        g.add(label)
        return g

    def render_arcs(self, source, index, top):
        Edge = AttrDict({"words": [], "frm": 0, "shape": "", "to": 0, "label": ""})
        Events = AttrDict({})

        if source:
            for idx, val in enumerate(source):
                self.lane.period = val.get("period", 1)
                self.lane.phase = val.get("phase", 0) * 2
                text = val.get("node")
                if text:
                    Stack = list(text)
                    Stack.reverse()
                    pos = 0
                    step = 1
                    while len(Stack) > 0:
                        eventname = Stack.pop()
                        if eventname == "<":
                            step = 0.25
                            continue
                        elif eventname == ">":
                            step = 1
                            continue
                        x = int(float(self.lane.xs) * (2 * pos * self.lane.period *
                                                       self.lane.hscale - self.lane.phase) + float(self.lane.xlabel))
                        y = int(idx * self.lane.yo + self.lane.y0 + float(self.lane.ys) * 0.5)
                        if eventname != ".":
                            Events[eventname] = AttrDict({"x": str(x), "y": str(y)})
                        pos += step

            gg = self.container.g(id="wavearcs_{index}".format(index=index))

            if top.get("edge"):
                for i, val in enumerate(top["edge"]):
                    Edge.words = val.split()
                    Edge.label = val[len(Edge.words[0]):]
                    Edge.label = Edge.label[1:]
                    Edge.frm = Edge.words[0][0]
                    Edge.to = Edge.words[0][-1]
                    Edge.shape = Edge.words[0][1:-1]
                    frm = AttrDict(Events[Edge.frm])
                    to = AttrDict(Events[Edge.to])

                    shapeProps = self.arc_shape(Edge, frm, to)
                    gg.add(self.render_arc(Edge, frm, to, shapeProps))

                    if Edge.label:
                        gg.add(self.render_label(AttrDict({"x": shapeProps.lx, "y": shapeProps.ly}), Edge.label))


            for k in Events:
                if k.islower() or k.isdigit():
                    if int(Events[k].x) > 0:
                        gg.add(self.render_label(AttrDict({"x": Events[k].x, "y": Events[k].y}), k))

            return gg

    def parse_config(self, source={}):
        self.lane.hscale = 1
        if self.lane.get("hscale0"):
            self.lane.hscale = self.lane.hscale0

        if source and source.get("config") and source.get("config").get("hscale"):
            hscale = round(source.get("config").get("hscale"))
            if hscale > 0:
                if hscale > 100:
                    hscale = 100
                self.lane.hscale = hscale

        self.lane.xmin_cfg = 0
        self.lane.xmax_cfg = sys.maxsize
        if source and "config" in source and "hbounds" in source["config"]:
            if len(source["config"]["hbounds"]) == 2:
                source["config"]["hbounds"][0] = math.floor(source["config"]["hbounds"][0])
                source["config"]["hbounds"][1] = math.ceil(source["config"]["hbounds"][0])
                if source["config"]["hbounds"][0] < source["config"]["hbounds"][1]:
                    self.lane.xmin_cfg = 2 * source["config"]["hbounds"][0]
                    self.lane.xmax_cfg = 2 * source["config"]["hbounds"][1]

        self.lane.yh0 = 0
        self.lane.yh1 = 0
        if source and source.get("head"):
            self.lane.head = source["head"]
            if "tick" in source["head"] or "tock" in source["head"]:
                self.lane.yh0 = 20
            if "tick" in source["head"]:
                source["head"]["tick"] += self.lane.xmin_cfg/2
            if "tock" in source["head"]:
                source["head"]["tock"] += self.lane.xmin_cfg/2
            if source.get("head").get("text"):
                self.lane.yh1 = 46
                self.lane.head["text"] = source["head"]["text"]

        self.lane.yf0 = 0
        self.lane.yf1 = 0
        if source and source.get("foot"):
            self.lane.foot = source["foot"]
            if "tick" in source["foot"] or "tock" in source["foot"]:
                self.lane.yf0 = 20
            if "tick" in source["foot"]:
                source["foot"]["tick"] += self.lane.xmin_cfg/2
            if "tock" in source["foot"]:
                source["foot"]["tock"] += self.lane.xmin_cfg/2
            if source.get("foot").get("text"):
                self.lane.yf1 = 46
                self.lane.foot["text"] = source["foot"]["text"]

    def rec(self, tmp=[], state={}):
        name = None
        delta = AttrDict({"x": 10})
        if isinstance(tmp[0], str) or isinstance(tmp[0], int):
            name = str(tmp[0])
            delta.x = 25

        state.x += delta.x
        for idx, val in enumerate(tmp):
            if isinstance(val, list):
                old_y = state.y
                self.rec(val, state)
                state["groups"].append({"x": state.xx,
                                        "y": old_y,
                                        "height": state.y - old_y,
                                        "name": state.name})
            elif isinstance(val, dict):
                state["lanes"].append(val)
                state["width"].append(state.x)
                state.y += 1

        state.xx = state.x
        state.x -= delta.x
        state.name = name

    def another_template(self, index, source):
        def get_container(elem):
            ctype = elem[0]
            ret = self.container[ctype]()
            ret.attribs = elem[1]

            def gen_elem(e):
                if e[0] == "path":
                    attr = e[1]
                    elem = self.element.path(d=attr["d"])
                    elem.attribs = attr
                elif e[0] == "rect":
                    attr = e[1]
                    x = attr["x"]
                    y = attr["y"]
                    w = attr["width"]
                    h = attr["height"]
                    elem = self.element.rect(insert=(x, y), size=(w, h))
                    elem.attribs = attr

                return elem
            [ret.add(gen_elem(e)) for e in elem[2:]]

            return ret

        skinname = source.get("config", {"skin" : "default"}).get("skin", "default")
        skin = waveskin.WaveSkin.get(skinname, waveskin.WaveSkin["default"])

        template = svgwrite.Drawing(id="svgcontent_{index}".format(index=index))
        if index == 0:
            template.add(template.style(skin[2][2]))
            [template.defs.add(get_container(e)) for e in skin[3][1:]]
            self.lane.xs = int(skin[3][1][2][1]["width"])
            self.lane.ys = int(skin[3][1][2][1]["height"])
            self.lane.xlabel = int(skin[3][1][2][1]["x"])
            self.lane.ym = int(skin[3][1][2][1]["y"])

        template["class"] = "WaveDrom"
        template["overflow"] = "hidden"

        return template

    def insert_svg_template(self, index=0, parent=[], source={}):
        e = waveskin.WaveSkin["default"]

        if source.get("config") and source.get("config").get("skin"):
            if waveskin.WaveSkin.get(source.get("config").get("skin")):
                e = waveskin.WaveSkin[source.get("config").get("skin")]

        if index == 0:
            self.lane.xs = int(e[3][1][2][1]["width"])
            self.lane.ys = int(e[3][1][2][1]["height"])
            self.lane.xlabel = int(e[3][1][2][1]["x"])
            self.lane.ym = int(e[3][1][2][1]["y"])

        else:
            e = ["svg",
                 {"id": "svg",
                  "xmlns": "http://www.w3.org/2000/svg",
                  "xmlns:xlink": "http://www.w3.org/1999/xlink",
                  "height": "0"},
                 [  # e[-1]
                     "g",  # e[-1][0]
                     {"id": "waves"},  # e[-1][1]
                     [  # e[-1][2]
                         "g",  # e[-1][2][0]
                         {"id": "lanes"}  # e[-1][2][1]
                     ],
                     [  # e[-1][3]
                         "g",  # e[-1][3][0]
                         {"id": "groups"}  # e[-1][3][1]
                     ]
                 ]
                 ]

        e[-1][1]["id"] = "waves_{index}".format(index=index)
        e[-1][2][1]["id"] = "lanes_{index}".format(index=index)
        e[-1][3][1]["id"] = "groups_{index}".format(index=index)
        e[1]["id"] = "svgcontent_{index}".format(index=index)
        e[1]["height"] = 0

        parent.extend(e)

    def render_waveform(self, index=0, source={}, output=[], strict_js_features=False):
        xmax = 0

        if source.get("signal"):
            template = self.another_template(index, source)
            waves = template.g(id="waves_{index}".format(index=index))
            lanes = template.g(id="lanes_{index}".format(index=index))
            groups = template.g(id="groups_{index}".format(index=index))
            self.parse_config(source)
            ret = AttrDict({"x": 0, "y": 0, "xmax": 0, "width": [], "lanes": [], "groups": []})
            self.rec(source["signal"], ret)  # parse lanes
            content = self.parse_wave_lanes(ret.lanes)
            (glengths, lanegroups) = self.render_wave_lane(content, index)
            for i, val in enumerate(glengths):
                xmax = max(xmax, (val + ret.width[i]))
            marks = self.render_marks(content, index)
            gaps = self.render_gaps(ret.lanes, index)
            if not strict_js_features:
                self.render_labels(lanes, ret.lanes, index)
            arcs = self.render_arcs(ret.lanes, index, source)

            # Render
            lanes.add(marks)
            [lanes.add(l) for l in lanegroups]
            lanes.add(arcs)
            lanes.add(gaps)

            self.render_groups(groups, ret.groups, index)
            self.lane.xg = int(math.ceil(float(xmax - self.lane.tgo) / float(self.lane.xs))) * self.lane.xs
            width = self.lane.xg + self.lane.xs * (self.lane.xmax + 1)
            height = len(content) * self.lane.yo + self.lane.yh0 + self.lane.yh1 + self.lane.yf0 + self.lane.yf1
            template["width"] = width
            template["height"] = height
            template.viewbox(0, 0, width, height)
            dx = self.lane.xg + 0.5
            dy = float(self.lane.yh0) + float(self.lane.yh1) + 0.5
            lanes.translate(dx, dy)

            waves.add(lanes)
            waves.add(groups)
            template.add(waves)
            return template

    def render_groups(self, root=[], groups=[], index=0):
        for i, val in enumerate(groups):
            dx = groups[i].x + 0.5
            dy = groups[i].y * self.lane.yo + 3.5 + self.lane.yh0 + self.lane.yh1
            h = int(groups[i]["height"] * self.lane.yo - 16)
            group = self.element.path(id="group_{i}_{index}".format(i=i, index=index),
                                      d="m {dx},{dy} c -3,0 -5,2 -5,5 l 0,{h} c 0,3 2,5 5,5".format(dx=dx, dy=dy, h=h),
                                      style="stroke:#0041c4;stroke-width:1;fill:none")

            root.add(group)

            name = groups[i]["name"]
            x = int(groups[i]["x"] - 10)
            y = int(self.lane.yo * (groups[i]["y"] + (float(groups[i]["height"]) / 2)) +
                    self.lane.yh0 + self.lane.yh1)
            label = self.container.g()
            label.translate(x, y)
            gg = self.container.g()
            gg.rotate(270)
            t = self.element.text("", text_anchor="middle")
            t["class"] = "info"
            t["xml:space"] = "preserve"
            t.add(self.element.tspan(name))
            gg.add(t)
            label.add(gg)
            root.add(label)

    def render_gap_uses(self, wave, g):
        subCycle = False

        if wave:
            Stack = deque(wave)
            pos = 0
            while len(Stack):
                next = Stack.popleft()
                if next == '<':
                    subCycle = True
                    continue
                if next == '>':
                    subCycle = False
                    continue
                if subCycle:
                    pos += self.lane.period
                else:
                    pos += 2 * self.lane.period
                if next == "|":
                    if subCycle:
                        dx = float(self.lane.xs) * (pos * float(self.lane.hscale) - float(self.lane.phase))
                    else:
                        dx = float(self.lane.xs) * ((pos - self.lane.period) * float(self.lane.hscale) - float(self.lane.phase))
                    b = self.container.use(href="#gap")
                    b.translate(dx)
                    g.add(b)

    def render_gaps(self, source, index):
        if source:
            gg = self.container.g(id="wavegaps_{index}".format(index=index))

            for idx, val in enumerate(source):
                self.lane.period = val.get("period", 1)
                self.lane.phase = int(val.get("phase", 0) * 2) + self.lane.xmin_cfg

                dy = self.lane.y0 + idx * self.lane.yo
                g = self.container.g(id="wavegap_{i}_{index}".format(i=idx, index=index))
                g.translate(0, dy)

                if "wave" in val:
                    self.render_gap_uses(val["wave"], g)

                gg.add(g)

            return gg

    def convert_to_svg(self, root):
        svg_output = ""

        if type(root) is list:
            if len(root) >= 2 and type(root[1]) is dict:
                if len(root) == 2:
                    svg_output += "<{}{}/>\n".format(root[0], self.convert_to_svg(root[1]))
                elif len(root) >= 3:
                    svg_output += "<{}{}/>\n".format(root[0], self.convert_to_svg(root[1]))
                    if len(root) == 3:
                        svg_output += self.convert_to_svg(root[2])
                    else:
                        svg_output += self.convert_to_svg(root[2:])
                    svg_output += "</{}>\n".format(root[0])
            elif type(root[0]) is list:
                for eleml in root:
                    svg_output += self.convert_to_svg(eleml)
            else:
                svg_output += "<{}>\n".format(root[0])
                for eleml in root[1:]:
                    svg_output += self.convert_to_svg(eleml)
                svg_output += "</{}>\n".format(root[0])
        elif type(root) is dict:
            for elemd in root:
                svg_output += " {}=\"{}\"".format(elemd, root[elemd])
        else:
            svg_output += root

        return svg_output

    # Backward compatibility
    genWaveBrick = gen_wave_brick
    parseWaveLane = parse_wave_lane
    parseWaveLanes = parse_wave_lanes
    findLaneMarkers = find_lane_markers
    renderWaveLane = render_wave_lane
    renderMarks = render_marks
    renderLabels = render_labels
    renderArcs = render_arcs
    parseConfig = parse_config
    anotherTemplate = another_template
    insertSVGTemplate = insert_svg_template
    renderWaveForm = render_waveform
    renderGroups = render_groups
    renderGaps = render_gaps

