import math
from Command import *


class Program:

    def __init__(self, commands=None):
        self.commands = commands if commands is not None else []
        self.active = True
        self.visible = False
        self.filename = ''
        if self.commands:
            self.xmin, self.ymin, self.xmax, self.ymax = self.__simulate()
        else:
            self.xmin = self.ymin = self.xmax = self.ymax = 0

    def __simulate(self):
        (x, y) = (0, 0)
        xy = []
        for command in self.commands:
            if not command.args:
                continue
            elif command.name in command.abscoms:
                x, y = command.x, command.y
                xy.append((x, y))
            elif command.name in command.relcoms:
                x, y = x + command.x, y + command.y
                xy.append((x, y))
            elif command.name in command.arccoms:
                xy += [(x + command.x, y + command.x), (x - command.x, y - command.x)]
        if not xy:
            return [0, 0, 0, 0]
        return [m([v[i] for v in xy]) for m, i in ((min, 0), (min, 1), (max, 0), (max, 1))]

    @staticmethod
    def parsefile(filename):
        with open(filename) as file:
            return Program.parse(file.read())

    @staticmethod
    def parse(code):
        commands = []
        for command in code.strip().split(";")[:-1]:
            name, args = command[:2], command[2:]
            args = [float(arg) for arg in args.split(",")] if args else []
            if name in Command.movecoms and len(args) > 2:
                for i in range(0, len(args), 2):
                    commands.append(Command(name, args[i], args[i + 1]))
            else:
                commands.append(Command(name, *args))
        return Program(commands)

    def show(self, w=None):
        import tkinter as tk
        p = self.flip()
        p = p.rotate(270)
        p = p.fitin((0, 0, 1024, 600), (0, 0))
        win = tk.Tk()
        win.title('HPGLPLOTTER')
        canvas = tk.Canvas(win, width=p.winsize[0], height=p.winsize[1])
        canvas.grid(row=0, column=0)
        x = y = 0
        for command in p.commands:
            if command.name == 'PU':
                x, y = command.x, command.y
            elif command.name == 'PD':
                canvas.create_line(x, y, command.x, command.y)
                x, y = command.x, command.y
            elif command.name == 'CI':
                r = command.args[0]
                canvas.create_oval(x - r, y - r, x + r, y + r)
        win.mainloop()

    def __str__(self):
        return "".join(str(command) for command in self.commands)

    def __mul__(self, arg):
        return Program([command * arg for command in self.commands])

    def __add__(self, arg):
        if type(arg) == type(self):
            return Program(self.commands + arg.commands)
        return Program([command + arg for command in self.commands])

    def __sub__(self, arg):
        return Program([command - arg for command in self.commands])

    def __len__(self):
        return len(str(self))

    def rotate(self, angl):
        return Program([command.rotate(angl) for command in self.commands])

    def flip(self):
        return Program([command.flip() for command in self.commands if command])

    def scaleto(self, ab):
        a, b = ab
        w, h = self.winsize
        if w == 0 or h == 0:
            return self
        return self * min(a / w, b / h)

    def moveto(self, ab):
        a, b = ab
        return self - (self.xmin - a, self.ymin - b)

    def fitin(self, xys, fxy):
        (x1, y1, x2, y2) = xys
        (fx, fy) = fxy
        p = self.scaleto((x2 - x1, y2 - y1))
        return p.moveto((x1 + fx * (x2 - x1 - p.winsize[0]), y1 + fy * (y2 - y1 - p.winsize[1])))

    def multi(self, w, h):
        dist = 40
        out = self
        p = self.winsize
        for i in range(w):
            for j in range(h):
                if w != 0 and h != 0:
                    out = out + (self + (i * p[0] + i * dist, j * p[1] + j * dist))
        return out

    @property
    def center(self):
        return ((self.xmin + self.xmax) / 2), ((self.ymin + self.ymax) / 2)

    @property
    def winsize(self):
        return self.xmax - self.xmin, self.ymax - self.ymin
