import tkinter as tk
import math

class Punkt(object):
    __slots__=("x","y")
    def __init__(self,x,y=0):
        if isinstance(x,Punkt): self.x=x.x;self.y=x.y
        else: self.x=float(x);self.y=float(y)
    def __mul__(self,other):
        if isinstance(other,Punkt): return Punkt(self.x*other.x,self.y*other.y)
        else: return Punkt(self.x*float(other),self.y*float(other))
    def __truediv__(self,other):
        if isinstance(other,Punkt): return Punkt(self.x/other.x,self.y/other.y)
        else: return Punkt(self.x/float(other),self.y/float(other))
    def __add__(self,other):
        if isinstance(other,Punkt): return Punkt(self.x+other.x,self.y+other.y)
        else: return Punkt(self.x+float(other),self.y+float(other))
    def __sub__(self,other):
        if isinstance(other,Punkt): return Punkt(self.x-other.x,self.y-other.y)
        else: return Punkt(self.x-float(other),self.y-float(other))
    def __abs__(self): return math.hypot(self.x,self.y)
    def __str__(self): return "[%.2f,%.2f]"%(self.x,self.y)
    def __call__(self): return [self.x,self.y]
    def __complex__(self): return complex(self.x,self.y)
    def angle_rad(self): return math.atan2(self.y,self.x)
    def angle(self): return math.degrees(self.angle_rad())
    def round(self): return Punkt(round(self.x),round(self.y))
    def __lshift__(self,other):
        o=Punkt(self)
        if o.x>other.x: o.x=other.x
        if o.y>other.y: o.y=other.y
        return o
    def __rshift__(self,other):
        o=Punkt(self)
        if o.x<other.x: o.x=other.x
        if o.y<other.y: o.y=other.y
        return o

class linear(object):
    def __init__(self,Before,P):
        self.before=Before; self.P=P
        self.min=Punkt(self.P)
        if self.before:
            self.max=Punkt(self.before.P)
            if self.before.P.x < self.P.x:
                self.min.x=self.before.P.x;self.max.x=self.P.x
            if self.before.P.y < self.P.y:
                self.min.y=self.before.P.y;self.max.y=self.P.y
        else:
            self.max=self.min
    def scale(self,scale,shift):
        self.S_P=(self.P*scale+shift).round()
    def frame(self):
        return self.min,self.max
    def __str__(self):
        return "LINE to "+str(self.S_P)
    def draw(self,canvas,color="black",tag=""):
        p1=self.before.S_P;p2=self.S_P
        canvas.create_line(p1.x,int(canvas.cget("height"))-p1.y,p2.x,
            int(canvas.cget("height"))-p2.y,fill=color,tag=tag,width=2)
        self.pfeil(canvas)
    def pfeil(self,canvas,color="red",tag="pfeil"):
        s=self.S_P-self.before.S_P
        m=(self.S_P+self.before.S_P)/2
        a=abs(s)
        p1=s/a*5+m;p2=s/-a*4+m
        canvas.create_line(p1.x,int(canvas.cget("height"))-p1.y,p2.x,
            int(canvas.cget("height"))-p2.y,fill="red",width=2,
            arrow=tk.FIRST,arrowshape=(8,10,4),tag=tag)
    def geotext(self):
        if self.before: g=1
        else: g=0
        return "G%i X%9.4f Y%9.4f"%(g,self.P.x,self.P.y)

class arc(object):
    def __init__(self,Before,P,M):
        self.before=Before; self.P=P; self.MP=M
        self.SW=math.degrees(math.atan2(Before.P.y-M.y,Before.P.x-M.x))
        self.EW=math.degrees(math.atan2(P.y-M.y,P.x-M.x))
        self.min=Punkt(self.P); self.max=Punkt(self.before.P)
        if self.before.P.x<self.P.x: 
            self.min.x=self.before.P.x
            self.max.x=self.P.x
        if self.before.P.y<self.P.y:
            self.min.y=self.before.P.y
            self.max.y=self.P.y
        self.Q={0:False,90:False,180:False,270:False}

        Q={"0":False,"90":False,"180":False,"270":False}

        if isinstance(self,arcR):
            S=self.EW;E=self.SW
        else:
            S=self.SW;E=self.EW

        if S > E: S-=360

        if S <= 0 <= E or S<= 360 <=E:
            self.max.x = self.MP.x+self.R
            self.Q[0]=True
        if S <= 180 <= E or S <= -180 <= E:
            self.min.x = self.MP.x-self.R
            self.Q[180]=True
        if S <= 270 <= E or S <= -90 <= E:
            self.min.y = self.MP.y-self.R
            self.Q[270]=True
        if S <= 90 <= E or S <= -270 <= E:
            self.max.y = self.MP.y+self.R
            self.Q[90]=True

    def scale(self,scale,shift):
        self.S_P=(self.P*scale+shift).round()
        self.S_MP=(self.MP*scale+shift).round()
        self.S_min=(self.min*scale+shift).round()
        self.S_max=(self.max*scale+shift).round()
        r1=round(abs(self.before.S_P-self.S_MP))
        r2=round(abs(self.S_P-self.S_MP))
        if r1>r2: self.S_R=r1
        else: self.S_R=r2
    def frame(self):
        return self.min,self.max
    def draw(self,canvas,color="black",tag=""):
        h=int(canvas.cget("height"))
        p1=Punkt(self.S_MP)
        p2=Punkt(self.S_MP)

        if self.Q[0]:
            p2.x=self.S_max.x
            canvas.create_oval(p2.x-4,h-self.S_MP.y-4,p2.x+4,h-self.S_MP.y+4,fill="yellow",tag="quadrant")
        else: p2.x+=self.S_R

        if self.Q[180]:
            p1.x=self.S_min.x
            canvas.create_oval(p1.x-4,h-self.S_MP.y-4,p1.x+4,h-self.S_MP.y+4,fill="yellow",tag="quadrant")
        else: p1.x-=self.S_R

        if self.Q[90]:
            p2.y=self.S_max.y
            canvas.create_oval(self.S_MP.x-4,h-p2.y-4,self.S_MP.x+4,h-p2.y+4,fill="yellow",tag="quadrant")
        else: p2.y+=self.S_R

        if self.Q[270]:
            p1.y=self.S_min.y
            canvas.create_oval(self.S_MP.x-4,h-p1.y-4,self.S_MP.x+4,h-p1.y+4,fill="yellow",tag="quadrant")
        else: p1.y-=self.S_R

        ex=self.EW-self.SW
        if self.SW>self.EW: ex+=360
        canvas.create_arc(p1.x,h-p1.y,p2.x,h-p2.y,start=self.SW,extent=ex,
            outline=color,tag=tag,style=tk.ARC,width=1.5)
        self.pfeil(canvas)
    def pfeil(self,canvas,color="red",tag="pfeil"):
        if self.SW>self.EW: hw=(self.SW+self.EW+360)/2
        else: hw=(self.SW+self.EW)/2
        hw=math.radians(hw)
        m=Punkt(math.cos(hw)*self.S_R+self.S_MP.x,
                 math.sin(hw)*self.S_R+self.S_MP.y)
        s=self.S_P-self.before.S_P
        a=abs(s)*2;p1=s/a+m;p2=s/-a+m
        canvas.create_line(p1.x,int(canvas.cget("height"))-p1.y,p2.x,
            int(canvas.cget("height"))-p2.y,fill="red",width=2,
            arrow=tk.FIRST,arrowshape=(8,10,4),tag=tag)
    def geotext(self):
        if isinstance(self,arcR): g=2
        else: g=3
        return "G%i X%9.4f Y%9.4f I%9.4f J%9.4f"%(g,self.P.x,self.P.y,
            self.before.P.x-self.P.x,self.before.P.y-self.P.y)


class arcR(arc):
    def __init__(self,Before,P,M):
        self.R=abs(P-M)
        if abs(self.R - abs(Before.P-M))>0.001: raise AttributeError("Radientoleranz"+ str(self.R-abs(Before.P-M)))
        arc.__init__(self,Before,P,M)
        self.EW,self.SW=self.SW,self.EW
    def __str__(self):
        return u"ARC-CW to "+str(self.S_P)+" M="+str(self.S_MP)+ \
            " R=%6.2f from %6.2f to %6.2f"%(self.S_R,self.EW,self.SW)

class arcL(arc):
    def __init__(self,Before,P,M):
        self.R=abs(P-M)
        if abs(self.R - abs(Before.P-M))>0.001: raise AttributeError("Radientoleranz"+ str(self.R-abs(Before.P-M)))
        arc.__init__(self,Before,P,M)
    def __str__(self):
        return "ARC-CCW to "+str(self.S_P)+" M="+str(self.S_MP)+ \
            " R=%6.2f from %6.2f to %6.2f"%(self.S_R,self.EW,self.SW)

class geoList(object):
    def __init__(self,x,y):
        self.__Liste=[linear(None,Punkt(x,y))]
        self.min=None
        self.max=None
    def scale(self,scala,shift):
        for i in self.__Liste: i.scale(scala,shift)
    def draw(self,da,**g):
        for i in self.__Liste[1:]: i.draw(da,**g)
    def frame(self):
        return self.min,self.max
    def append(self,t,x,y,mx=0,my=0):
        n=self.__Liste[len(self.__Liste)-1]
        if t<2: nobj=linear(n,Punkt(x,y))
        elif t==2: nobj=arcR(n,Punkt(x,y),Punkt(mx,my))
        elif t==3: nobj=arcL(n,Punkt(x,y),Punkt(mx,my))
        else: raise TypeError()
        r1,r2=nobj.frame()
        if not self.min: self.min,self.max=r1,r2
        else: self.min,self.max=r1<<self.min,r2>>self.max
        self.__Liste.append(nobj)
    def __iter__(self): 
        self.N = -1 
        return self 
    def next(self):
        self.N += 1
        if self.N < len(self.__Liste): return self.__Liste[self.N]
        else: raise StopIteration()

if __name__=="__main__":
    import string
    #from geo import *

    mainframe=tk.Tk()
    da=tk.Canvas(mainframe,width=1240,height=800,background="white")
    da.pack(fill=tk.BOTH,side="bottom")
    tx=tk.Label(mainframe)
    tx.pack(fill=tk.X,side="top")

    def boing(a,b,c):
        global da,geo
        if c==2:
            da.after(1000,boing(a,b,0))
            return
        if c: newcolor="green"
        else: newcolor="black"
        for k in da.find_withtag(b):
            n=da.type(k) 
            if n=="arc": da.itemconfig(k,outline=newcolor,width=2*c+2)
            elif n=="line": da.itemconfig(k,fill=newcolor,width=2*c+2)
        if c==1:
            da.itemconfig("pfeil",fill=newcolor)
            tx["text"]=b
            for t in geom:
                print(t.geotext())
        else: da.itemconfig("pfeil",fill="red")

    geom=geoList(0,0)
    geom.append(1,5,0,0,0)
    geom.append(3,10,5,5,5)
    geom.append(2,15,10,15,5)
    geom.append(1,15,5,15,10)
    geom.append(3,20,0,20,5)
    geom.append(2,25,-5,20,-5)
    geom.append(1,20,-5,25,-5)
    geom.append(3,15,-10,20,-10)
    geom.append(2,10,-15,10,-10)
    geom.append(1,5,-15,10,-15)
    geom.append(3,0,-10,0,-15)
    geom.append(2,-5,-5,0,-5)
    geom.append(3,-12.07106781,-5,-8.53553391,-8.53553391)
    geom.append(2,-19.14213562,-5,-15.60660172,-1.46446609)
    geom.append(3,-19.14213562,2.07106781,-22.67766953,-1.46446609)
    geom.append(2,-19.14213562,9.14213562,-15.60660172,5.60660172)
    geom.append(1,-19.14213562,14.14213562,-19.14213562,9.14213562)
    geom.append(3,-12.07106781,14.14213562,-15.60660172,17.67766953)
    geom.append(2,-5,14.14213562,-8.53553391,10.60660172)
    geom.append(3,-5,7.07106781,-1.46446609,10.60660172)
    geom.append(2,-5,0,-8.53553391,3.53553391)

    min,max=geom.frame()

    scrn=Punkt(da.cget("width"),da.cget("height"))
    part=max-min

    if (part.y/part.x)>(scrn.y/scrn.x):
        scala=int((scrn.y-20)/part.y)
    else:
        scala=int((scrn.x-20)/part.x)

    part*=scala
    shift=(scrn+part)/2-(max*scala)
    scala=Punkt(scala,scala)

    geom.scale(scala,shift)
    geom.draw(da,tag="n")
    da.tag_bind("n","<Button-1>",lambda event,x="n": boing(event,x,1))
    da.tag_bind("n","<ButtonRelease-1>",lambda event,x="n": boing(event,x,2))

    da.create_text((float(da.cget("width"))-10)/2,10,text="Scala:"+str(scala))
    mainframe.mainloop()
