import logging
from math import atan2, exp, pi
import colorsys


import svgwrite as sw
import numpy as np


def polygon_path(vs, **kwargs):
    p = []
    p.append(["M", vs[-1][0], vs[-1][1]])
    for v in vs:
        p.append(["L", v[0], v[1]])
    p.append(["Z"])
    return sw.path.Path(d=p, **kwargs)


def polygon(svg, com, d, **options):
    """
    draw a polygon
    """
    group = svg.add( svg.g( id='Polygon') )
    path = polygon_path(com+d, **options)
    group.add(path)


sun = np.array([-1.,-1.,2.])
sun /= np.linalg.norm(sun)


def Normal(vs):
    """
    Normal vector (not normalized)
    """
    n = np.zeros(3)
    for i in range(vs.shape[0]):
        n += np.cross(vs[i-1], vs[i])
    return n



def cylinder_path(R, ratio, L, **kwargs):
    # horizontal, start from origin
    magic = 0.552284749831
    x1 = R*ratio
    x2 = x1*magic
    y1 = R
    y2 = y1*magic
    p = []
    p.append(["M", 0, -y1])
    p.append(["L", L, -y1])
    p.append(["C", L+x2,-y1, L+x1, -y2,  L+x1, 0])
    p.append(["C", L+x1, y2, L+x2,  y1,  L,   y1])
    p.append(["L", 0, y1])
    p.append(["C",-x2, y1,-x1, y2,  -x1,0])
    p.append(["C",-x1,-y2,-x2,-y1, 0,-y1])
    p.append(["Z"])
    return sw.path.Path(d=p, **kwargs)


def cylinder_new(svg, v1_, v2_, r, **options):
    """
    draw a 3D cylinder
    """
    group = svg.add( svg.g( id='Cylinder') )
    if v1_[2] > v2_[2]:
        v1, v2 = v2_, v1_
    else:
        v1, v2 = v1_, v2_
    dir = v2[:2] - v1[:2]
    angle = atan2(dir[1],dir[0])
    d   = v2 - v1
    ratio = d[2] / np.linalg.norm(d)
    L = np.linalg.norm(dir)
    path = cylinder_path(r, ratio, L, **options)
    path.translate(v1[0],v1[1])
    path.rotate(angle*180/pi, center=(0,0))
    group.add(path)
    u = sw.shapes.Ellipse(center=v2[:2], r=(ratio*r, r),
                          **options) #, fill=endfill)
    u.rotate(angle*180/pi, center=v2[:2])
    group.add(u)


def Render(prims, Rsphere, shadow=True, zoom=200, topleft=np.array([-1.,-1.]), size=(50.,50.), bgcolor=None):
    logger = logging.getLogger()
    svg = sw.Drawing(size=("{0}px".format(size[0]*zoom), "{0}px".format(size[1]*zoom)))
    if bgcolor is not None:
        svg.add(svg.rect(insert=(0, 0), size=("{0}px".format(size[0]*zoom), "{0}px".format(size[1]*zoom)), fill=bgcolor))
    TL0 = np.zeros(3)
    TL0[:2] = topleft
    shadows = []
    linedefaults = { "stroke_width": 2,
                     "stroke": "#000",
                     "stroke_linejoin": "round",
                     "stroke_linecap" : "round",
    }
    filldefaults = { "stroke_width": 1,
                     "stroke": "#000",
                     "fill": "#0ff",
                     "stroke_linejoin": "round",
                     "stroke_linecap" : "round",
                     "fill_opacity": 1.0,
    }
    shadowdefaults = { "stroke_width": 0,
                       "fill": "#888",
                       "fill_opacity": 0.08,
    }
    if shadow is not None:
        r = Rsphere
        Z = np.array([0,0,1.0])
        prims += [[prim[0] - Z*r*1.4**j, prim[1]+'S', r*1.4**j, {}]
                  for j in range(1,5) for prim in prims if prim[1] == 'C']
    for prim in sorted(prims, key=lambda x: x[0][2]):
        if not ( (-0.5 < prim[0][0]-topleft[0] < size[0]+0.5) and
                 (-0.5 < prim[0][1]-topleft[1] < size[1]+0.5) ):
            continue
        if prim[1] == "L":
            if prim[4] == 0:
                options = {**linedefaults, **prim[5]}
                svg.add(sw.shapes.Line(start=(prim[2][:2]-topleft)*zoom, end=(prim[3][:2]-topleft)*zoom, **options))
            else:
                options = {**filldefaults, **prim[5]}
                cylinder_new(svg, (prim[2]-TL0)*zoom, (prim[3]-TL0)*zoom, prim[4]*zoom, **options)
        elif prim[1] == "L2":
            if prim[3] == 0:
                options = {**linedefaults, **prim[4]}
                s = ((prim[0]+prim[2])[:2]-topleft)*zoom
                e = ((prim[0]-prim[2])[:2]-topleft)*zoom
                svg.add(sw.shapes.Line(start=s, end=e, **options))
            else:
                options = {**filldefaults, **prim[4]}
                cylinder_new(svg, (prim[0]+prim[2]-TL0)*zoom, (prim[0]-prim[2]-TL0)*zoom, prim[3]*zoom, **options)
        elif prim[1] == "P":
            options = prim[3]
            if "fillhs" in options:
                normal = Normal(prim[2])
                normal /= np.linalg.norm(normal)
                cosine = abs(np.dot(sun, normal))
                hue, sat = options["fillhs"]
                del options["fillhs"]
                bri = cosine*0.5+0.5
                if sat < 0.2:
                    bri *= 0.9
                if cosine > 0.8:
                    sat *= (1 - (cosine-0.8)*3)
                r,g,b = colorsys.hsv_to_rgb(hue/360., sat, bri)
                rgb = "#{0:x}{1:x}{2:x}".format(int(r*15.9), int(g*15.9), int(b*15.9))
                options["fill"] = rgb
            options = {**filldefaults, **options}
            polygon(svg, (prim[0]-TL0)*zoom, prim[2]*zoom, **options)
        elif prim[1] == "C":
            options = { **filldefaults, **prim[3] }
            Rsphere = prim[2]
            svg.add(sw.shapes.Circle(center=(prim[0][:2]-topleft)*zoom, r=Rsphere*zoom, **options))
        elif prim[1] == "CS":
            Rsphere = prim[2]
            options = { **shadowdefaults, **prim[3] }
            svg.add(sw.shapes.Circle(center=(prim[0][:2]-topleft)*zoom, r=Rsphere*zoom, **options))
    print(svg.tostring(), end="")
