from math import sin, cos, pi

from browser import document
from visualife.core import HtmlViewport

class Turtle:
    def __init__(self):
        """Default turtle constructor"""
        self.x, self.y, self.a, self.pen, self.speed = 0, 0, 0, True, 1

    def __str__(self): return "%d %d" % (self.x, self.y)


class TurtleCommand:
    """Base class for a turtle command"""

    def __str__(self):
        raise NotImplemented

    def __call__(self, turtle, drawing):
        raise NotImplemented


class R(TurtleCommand):

    def __init__(self, angle):
        """Turn right by a given angle"""
        self.angle = angle

    def __call__(self, turtle, drawing):
        turtle.a -= self.angle

    def __str__(self):
        return "R " + str(self.angle)


class L(TurtleCommand):

    def __init__(self, angle):
        """Turn left by a given angle"""
        self.angle = angle

    def __call__(self, turtle, drawing):
        turtle.a += self.angle

    def __str__(self):
        return "L " + str(self.angle)


class A(TurtleCommand):

    def __init__(self, angle):
        """Set a given angle (in degrees)"""
        self.angle = angle

    def __call__(self, turtle, drawing):
        turtle.a = self.angle

    def __str__(self):
        return "A " + str(self.angle)

class U(TurtleCommand):

    def __call__(self, turtle, drawing):
        turtle.pen = False

    def __str__(self):
        return "U"


class D(TurtleCommand):

    def __call__(self, turtle, drawing):
        turtle.pen = True

    def __str__(self):
        return "D"


class F(TurtleCommand):

    def __init__(self, n_steps=1):
        """Go forward command"""
        self.n_steps = n_steps

    def __call__(self, turtle, drawing):
        turtle.y += self.n_steps * sin(turtle.a / 180.0 * pi) * turtle.speed
        turtle.x += self.n_steps * cos(turtle.a / 180.0 * pi) * turtle.speed
        if turtle.pen:
            drawing.line_to(turtle.x, turtle.y)
        else:
            drawing.move_to(turtle.x, turtle.y)

    def __str__(self):
        return "F " + str(self.n_steps) + "" if self.n_steps != 1 else "F"


class J(TurtleCommand):
    def __init__(self, params):
        """Turtle jumps to an arbitrary position given in absolute coordinates"""
        self.xy = [float(token) for token in params.split()]

    def __call__(self, turtle, drawing):
        turtle.x, turtle.y = self.xy[0], self.xy[1]
        drawing.move_to(turtle.x, turtle.y)

    def __str__(self):
        return "J " + str(self.xy[0]) + " " + str(self.xy[1])


class Faster(TurtleCommand):

    def __init__(self, f):
        """Increase turtle speed by a given value"""
        self.f = f

    def __call__(self, turtle, drawing):
        turtle.speed += self.f

    def __str__(self):
        return "FASTER " + str(self.f)


class Slower(TurtleCommand):

    def __init__(self, f):
        """Decrease turtle speed by a given value"""
        self.f = f

    def __call__(self, turtle, drawing):
        turtle.speed += self.f

    def __str__(self):
        return "SLOWER " + str(self.f)


class Procedure(TurtleCommand):

    def __init__(self, name):
        """Procedure is a group of commands"""
        self.__commands = []
        self.__name = name

    def do_next(self, next_command):
        self.__commands.append(next_command)
        return self

    def __call__(self, turtle, drawing):
        for cmd in self.__commands:
            cmd(turtle, drawing)

    def __str__(self):
        out = self.__name+" := ["
        for cmd in self.__commands:
            out += str(cmd)+" "
        return out + "]"


class Repeat(TurtleCommand):

    def __init__(self, n, cmd):
        """Repeats a command multiple times"""
        self.__command = cmd
        self.__n = n

    @property
    def n(self):
        return self.__n

    def __call__(self, turtle, drawing):
        for i in range(self.__n):
            self.__command(turtle, drawing)

    def __str__(self):
        return "REPEAT " + str(self.__n) + " " + str(self.__command)


class GraphicDevice:
    def line_to(self, x, y):
        raise NotImplemented

    def move_to(self, x, y):
        raise NotImplemented


class EchoDevice(GraphicDevice):
    def line_to(self, x, y):
        print("line to %.1f %.1f" %( x, y))

    def move_to(self, x, y):
        print("move to %.1f %.1f" %( x, y))


class Logo:

    def __init__(self, device=EchoDevice()):
        self.__makers = {}
        self.__turtle = Turtle()
        self.__device = device
        self.add_command("R", lambda angle: R(float(angle)))
        self.add_command("L", lambda angle: L(float(angle)))
        self.add_command("A", lambda angle: A(float(angle)))
        self.add_command("F", lambda steps: F(float(steps)))
        self.add_command("FASTER", lambda value: Faster(float(value)))
        self.add_command("SLOWER", lambda value: Slower(float(value)))
        self.add_command("U", U())
        self.add_command("D", D())
        self.add_command("J", lambda value: J(value))
        self.add_command("JUMP", lambda value: J(value))
        self.add_command("DEF", self.make_procedure)
        self.add_command("REPEAT", self.make_repeat)
        self.__loop_cnt = 0

    @property
    def turtle(self): return self.__turtle

    def draw(self, *cmds):
        for ci in cmds:
            if isinstance(ci, str):                     # ---------- If it's a string, use factory to produce a command
                cmd = self.produce_command(ci)
                if not ci.startswith("DEF") and cmd:    # ---------- Execute, if it's not a definition
                    cmd(self.__turtle, self.__device)
            elif ci:                                    # ---------- Execute a command
                if ci: ci(self.__turtle, self.__device)

    def add_command(self, name, cmd_maker):
        self.__makers[name] = cmd_maker

    def make_procedure(self, procedure_string):
        name = procedure_string.split(maxsplit=1)[0]
        p = Procedure(name)
        cmnds = procedure_string[procedure_string.find('[')+1:procedure_string.find(']')].split(";")
        for cmd in cmnds:
            p.do_next(self.produce_command(cmd))

        self.__makers[name] = p             # ---------- here we register the new procedure in this factory
        return p

    def make_repeat(self, repeat_string):
        tokens = repeat_string.split(maxsplit=1)
        if tokens[1].find('[') >= 0:
            tokens[1] = "DEF loop_" + str(self.__loop_cnt) + " := " + tokens[1]
            self.__loop_cnt += 1
        return Repeat(int(tokens[0]), self.produce_command(tokens[1]))

    def produce_command(self, a_string):
        if a_string.strip() == "" or a_string[0] == '#': return None
        tokens = a_string.split(maxsplit=1)
        if isinstance(self.__makers[tokens[0]], TurtleCommand):
            return self.__makers[tokens[0].strip()]
        else:
            return self.__makers[tokens[0].strip()](*tokens[1:])


class VisualifeBridge(GraphicDevice):
    def __init__(self, viewport):
        self.__viewport = viewport
        self.__x, self.__y = 0, 0
        self.__n_lines = 0

    def line_to(self, x, y):
        self.__viewport.line("line-%d" % self.__n_lines, self.__x, self.__y, x, y)
        self.__n_lines += 1
        self.__x, self.__y = x, y

    def move_to(self, x, y):
        self.__x, self.__y = x, y


def run(evt=None):
    drawing.clear()
    logo = Logo(VisualifeBridge(drawing))
    logo.turtle.speed = 2
    commands = document["logo1"].value.splitlines()
    logo.draw(*commands)
    drawing.close()

def load(evt):
    name = evt.target.id
    document["logo1"].value = programs[name]
    run()

programs = {}
programs["dandelion"] = """
JUMP 1 1
REPEAT 4 [F 199; R -90]
JUMP 160 160
DEF one_side := [F 10; R 90]
DEF square := [REPEAT 4 one_side]
REPEAT 36 [R 10; square]
A 80
F 100
A -75
F 110
SLOWER 0.1
REPEAT 36 [R 10; square]
JUMP 10 360
FASTER 0.2
DEF diamond := [F 3; L 20; F 3; L 160; F 3; L 20; F 3]
REPEAT 20 [A -130; diamond; A -80; diamond; A 0; F 7]
"""

programs["spiral"] = """
JUMP 0 0
REPEAT 4 [F 194; R -90]
JUMP 200 200
DEF one_side := [F 8.5; R 91]
DEF square := [REPEAT 4 one_side]
REPEAT 200 [R 5; square; FASTER 0.09]
"""

programs["stars"] = """
JUMP 0 0
REPEAT 4 [F 194; R -90]
JUMP 200 200
REPEAT 126 [F 50; R 154]
JUMP 44 100
REPEAT 120 [F 50; R 134]
JUMP 125 240
REPEAT 120 [F 50; R 124; FASTER 0.007]
"""

drawing = HtmlViewport(document['svg'], 400, 400)
drawing.style = "stroke:black; stroke-width: 0.25;"
document["dandelion"].bind("click", load)
document["spiral"].bind("click", load)
document["stars"].bind("click", load)
document["redraw"].bind("click", run)

document["logo1"].value = programs["dandelion"]
run()
