# dial.py Dial and Pointer classes for micro-gui

# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2020 Peter Hinch

import cmath
from gui.core.ugui import Widget, display
from gui.widgets.label import Label

# Line defined by polar coords; origin and line are complex
def polar(display, origin, line, color):
    xs, ys = origin.real, origin.imag
    theta = cmath.polar(line)[1]
    display.line(round(xs), round(ys), round(xs + line.real), round(ys - line.imag), color)

def conj(v):  # complex conjugate
    return v.real - v.imag * 1j

# Draw an arrow; origin and vec are complex, scalar lc defines length of chevron.
# cw and ccw are unit vectors of +-3pi/4 radians for chevrons (precompiled)
def arrow(display, origin, vec, lc, color, ccw=cmath.exp(3j * cmath.pi/4), cw=cmath.exp(-3j * cmath.pi/4)):
    length, theta = cmath.polar(vec)
    uv = cmath.rect(1, theta)  # Unit rotation vector
    start = -vec
    if length > 3 * lc:  # If line is long
        ds = cmath.rect(lc, theta)
        start += ds  # shorten to allow for length of tail chevrons
    chev = lc + 0j
    polar(display, origin, vec, color)  # Origin to tip
    polar(display, origin, start, color)  # Origin to tail
    polar(display, origin + conj(vec), chev*ccw*uv, color)  # Tip chevron
    polar(display, origin + conj(vec), chev*cw*uv, color)
    if length > lc:  # Confusing appearance of very short vectors with tail chevron
        polar(display, origin + conj(start), chev*ccw*uv, color)  # Tail chevron
        polar(display, origin + conj(start), chev*cw*uv, color)


class Pointer:
    def __init__(self, dial):
        self.dial = dial
        dial.vectors.add(self)
        self.val = 0 + 0j
        self.color = None

    def value(self, v=None, color=None):
        if color is not None:
            self.color = color
        if v is not None:
            if isinstance(v, complex):
                l = cmath.polar(v)[0]
                if l > 1:
                    self.val = v/l
                else:
                    self.val = v
            else:
                raise ValueError('Pointer value must be complex.')
        self.dial.draw = True
        return self.val

class Dial(Widget):
    CLOCK = 0
    COMPASS = 1
    def __init__(self, writer, row, col, *, height=100,
                 fgcolor=None, bgcolor=None, bdcolor=False, ticks=4,
                 label=None, style=0, pip=None):
        super().__init__(writer, row, col, height, height, fgcolor, bgcolor, bdcolor)
        self.style = style
        self.pip = self.fgcolor if pip is None else pip
        if label is not None:
            self.label = Label(writer, row + height + 3, col, label)
            # Adjust metrics
            self.mrow = self.label.mrow - 2  # Label never has border
            self.mcol = max(self.mcol, self.label.mcol - 2)
        radius = int(height / 2)
        self.radius = radius
        self.ticks = ticks
        self.xorigin = col + radius
        self.yorigin = row + radius
        self.vectors = set()

    def show(self):
        if super().show():
            # cache bound variables
            ticks = self.ticks
            radius = self.radius
            xo = self.xorigin
            yo = self.yorigin
            # vectors (complex)
            vor = xo + 1j * yo
            vtstart = 0.9 * radius + 0j  # start of tick
            vtick = 0.1 * radius + 0j  # tick
            vrot = cmath.exp(2j * cmath.pi/ticks)  # unit rotation
            for _ in range(ticks):
                polar(display, vor + conj(vtstart), vtick, self.fgcolor)
                vtick *= vrot
                vtstart *= vrot
            display.circle(xo, yo, radius, self.fgcolor)
            vshort = 1000  # Length of shortest vector
            for v in self.vectors:
                color = self.fgcolor if v.color is None else v.color
                val = v.value() * radius  # val is complex
                vshort = min(vshort, cmath.polar(val)[0])
                if self.style == Dial.CLOCK:
                    polar(display, vor, val, color)
                else:
                    arrow(display, vor, val, 5, color)
            if isinstance(self.pip, int) and vshort > 5:
                display.fillcircle(xo, yo, 2, self.pip)
