from Tkinter import *
from array import *


def quadratic(x,c):          
    return(x*x + c)

OutOfBounds = "Out of Bounds"

class Graphalyzer:

    def __init__(self, width=400, height=300,
                 x_min=0.0, x_max=1.1, c=.25, f=quadratic, orbit_size = 300):
        self.width, self.height = width, height
        self.x_min, self.x_max, self.f, self.c = x_min, x_max, f, c
        self.orbit_size = orbit_size

        self.x_value = array('f', range(self.width))
        self.y_value = array('f', range(self.width))
        self.orbit_value = []

        self.window = Tk()
        self.window.title('Graphalyzer')
        self.canvas = Canvas(self.window, width=width, height=height,cursor='plus')
        self.canvas.bind("<Motion>", self.motion)
        self.canvas.bind("<Leave>", self.leave)
        self.canvas.bind("<ButtonRelease-1>", self.mouse_up)
        self.canvas.pack()

        self.dot = None
        self.graph = None
        self.identity = None
        self.orbit_steps = []
        self.orbit_dots = []
        self.set(x_min, x_max, c, f)

    def motion(self, event):
        if self.dot :
            self.canvas.delete(self.dot)
            self.dot = None
        try:
            y =  int( (self.y_max  - self.x_value[event.x])/self.delta_y )
        except IndexError:
            return
        self.dot = self.canvas.create_oval(event.x - 2, y - 2, event.x + 2, y + 2)
        return
    
    def leave(self, event):
        if self.dot :
            self.canvas.delete(self.dot)
            self.dot = None

    def mouse_up(self, event):
        self.erase_orbit()
        self.make_orbit(event.x)
    
    def set(self, x_min=None, x_max=None, c=None, f=None):
        self.erase_orbit()
        if x_min: self.x_min = float(x_min)
        if x_max: self.x_max = float(x_max)
        if c: self.c = float(c)
        if f: self.f = f
        self.delta_x = (self.x_max - self.x_min)/self.width
        for i in range(self.width):
            self.x_value[i] = self.x_min + i*self.delta_x
        if self.graph:
            self.canvas.delete(self.graph)
        self.graph = self.make_graph()
        if self.identity:
            self.canvas.delete(self.identity)
        self.identity = self.make_identity()

    def pixels(self,x,y):
        x_pixel = int( (x - self.x_min)/self.delta_x )
        y_pixel = int( (self.y_max - y)/self.delta_y )
        return (x_pixel, y_pixel)
    
    def make_identity(self):
        points = []
        for i in range(self.width):
            points.append(i)
            points.append(int( (self.y_max  - self.x_value[i])/self.delta_y ))
        points.append({'fill':'darkgreen'})
        return self.canvas.create_line(*points)        

    def make_graph(self):
        self.y_min = self.x_min
        self.y_max = self.x_max
        for i in range(self.width):
            y = self.f(self.x_value[i], self.c)
            if y > self.y_max:
                self.y_max = y
            if y < self.y_min:
                self.y_min = y
            self.y_value[i] = y
        self.delta_y = (self.y_max - self.y_min)/self.height
        points = []
        for i in range(self.width):
            points.append(i)
            points.append( int( (self.y_max  - self.y_value[i])/self.delta_y ) )
        points.append({'fill':'blue'})
        return self.canvas.create_line(*points)

    def make_dot(self, x):
        x0, y0 = self.pixels(x,x)
        return self.canvas.create_oval(x0-2, y0-2, x0+2, y0+2, fill='red')
    
    def make_orbit(self, x_index):
        x_val = self.x_value[x_index]
        self.orbit_value.append(x_val)
        self.orbit_dots.append( self.make_dot(x_val) )
        try:
            for i in range (self.orbit_size):
                self.extend_orbit()
        except OutOfBounds:
            return

    def erase_orbit(self):
        for step in self.orbit_steps:
            self.canvas.delete(step)
        for dot in self.orbit_dots:
            self.canvas.delete(dot)
        self.orbit_steps = []
        self.orbit_dots = []
        self.orbit_value=[]
            
    def extend_orbit(self):
        x_val = self.orbit_value[-1]
        if x_val > self.x_max or x_val < self.x_min:
            raise OutOfBounds
        y_val = self.f(x_val, self.c)
        self.orbit_value.append(y_val)
        x0, y0 = self.pixels(x_val, x_val)
        x1, y1 = self.pixels(y_val, y_val)
        if abs(x1 - x0) > 10:
            self.orbit_steps.append( self.canvas.create_line(x0, y0, x0, y1, x1, y1,
                               arrow=LAST, arrowshape=(8,10,2)) )
        else:
            self.orbit_steps.append( self.canvas.create_line(x0, y0, x0, y1, x1, y1) )
        self.orbit_dots.append( self.make_dot(y_val) )
