"""
This program demonstrates a linked list by depicting it graphically as a train.

Author: Zachary Palmer
Date: 2015-11-30
"""

from graphics import *

class TrainCar(object):
    """
    This class defines the concept of a train car.  It does NOT handle the
    drawing of the car; it just represents the idea.
    """
    def __init__(self, value):
        self.next_car = None
        self.value = value
    def get_next_car(self):
        return self.next_car
    def get_value(self):
        return self.value
    def set_next_car(self, next_car):
        self.next_car = next_car
    def set_value(self, value):
        self.value = value

class Train(object):
    """
    This class defines the metaphorical train.  As with the above TrainCar
    class, this class does NOT handle drawing the train.  It only represents
    the idea of the train itself.  (In some sense, this class can be thought to
    represent the train's engine.)
    """
    def __init__(self):
        self.first_car = None
    def add_car_to_front(self, value):
        new_car = TrainCar(value)
        new_car.set_next_car(self.first_car)
        self.first_car = new_car
    def get_car(self, index):
        car = self.first_car
        for i in range(index):
            car = car.get_next_car()
        return car
    def get(self, index):
        car = self.get_car(index)
        return car.get_value()
    def add_car_after_front(self, index, value):
        new_car = TrainCar(value)
        car = self.get_car(index-1)
        new_car.set_next_car(car.get_next_car())
        car.set_next_car(new_car)
    def add(self, index, value):
        if index == 0:
            self.add_car_to_front(value)
        else:
            self.add_car_after_front(index, value)
    def remove_car_from_front(self):
        car = self.first_car.get_next_car()
        self.first_car = car
    def remove_car_after_front(self, index):
        car = self.get_car(index-1)
        next_car = car.get_next_car().get_next_car()
        car.set_next_car(next_car)
    def remove(self, index):
        if index == 0:
            self.remove_car_from_front()
        else:
            self.remove_car_after_front(index)
    def size(self):
        number = 0
        car = self.first_car
        while car != None:
            number += 1
            car = car.get_next_car()
        return number

class GraphicsTrain(object):
    """
    This class handles the drawing of the train engine in the rendering.
    """
    def __init__(self, bottom_right):
        self.objects = []
        p1 = bottom_right.clone()
        p1.move(-120,0)
        p2 = bottom_right.clone()
        p2.move(0,-90)
        self._take_object(Rectangle(p1,p2))
        p3 = p1.clone()
        p3.move(-30,0)
        p4 = p1.clone()
        p4.move(0,-30)
        self._take_object(Polygon(p1,p3,p4))
        p4.move(0,-30)
        self._take_object(Circle(p4, 30))
        p4.move(30,0)
        p5 = p4.clone()
        p5.move(-30,-60)
        p6 = p4.clone()
        p6.move(30,-60)
        self._take_object(Polygon(p4,p5,p6))
        p7 = p2.clone()
        p7.move(-30,-30)
        self._take_object(Rectangle(p2,p7))
        
    def _take_object(self, obj):
        obj.setFill("black")
        obj.setOutline("black")
        self.objects.append(obj)

    def draw(self, win):
        for obj in self.objects:
            obj.draw(win)

class GraphicsCar(object):
    """
    This class handles the drawing of train cars in the rendering.  Each object
    represents a single train car.
    """
    def __init__(self, center, value):
        self.objects = []
        self.center = center
        p1 = Point(self.center.getX()-75,self.center.getY()-45)
        p2 = Point(self.center.getX()+75,self.center.getY()+45)
        box = Rectangle(p1,p2)
        box.setOutline("orange")
        box.setWidth(5)
        box.setFill("white")
        p3 = Point(self.center.getX()-45,self.center.getY()+45)
        wheel1 = Circle(p3, 30)
        wheel1.setOutline("black")
        wheel1.setFill("black")
        p3.move(90,0)
        wheel2 = Circle(p3, 30)
        wheel2.setOutline("black")
        wheel2.setFill("black")
        text = Text(center, str(value))
        text.setSize(30)
        self.objects = [wheel1, wheel2, box, text]

    def draw(self, win):
        for obj in self.objects:
            obj.draw(win)

    def undraw(self):
        for obj in self.objects:
            obj.undraw()

def update_train(win, values, cars):
    """
    This function updates the rendering of the train.  The arguments are the
    window in which to draw, the train itself (as a linked list of values), and
    a list of the graphical objects representing the train cars.  This function
    will update the list by replacing all of the cars with cars matching the
    contents of the train.
    """
    start = Point(250,325)
    win.autoflush = False
    for car in cars:
        car.undraw()
    while len(cars) > 0:
        cars.pop()
    for i in range(values.size()):
        value = values.get(i)
        car = GraphicsCar(start, value)
        start.move(175,0)
        cars.append(car)
    for car in cars:
        car.draw(win)
    win.autoflush = True
    win.flush()
    
def main():
    win = GraphWin("Train", 1000, 600)
    win.setBackground("teal")
    ground = Rectangle(Point(0,win.getHeight()), Point(win.getWidth(),400))
    ground.setFill("brown")
    ground.draw(win)
    train = GraphicsTrain(Point(160,400))
    train.draw(win)
    
    values = Train()
    graphical_cars = []
    command = None
    while command != "q":
        command = raw_input("Do you want to [a]dd, [r]emove, or [q]uit? ")
        if command == "a":
            try:
                index = int(raw_input("Where? "))
            except ValueError:
                index = None
                print "Invalid index!"
            if index != None:
                if index > values.size():
                    print "That is not a valid location!"
                else:
                    value = raw_input("What value? ")
                    values.add(index, value)
                    update_train(win, values, graphical_cars)
        elif command == "r":
            try:
                index = int(raw_input("Where? "))
            except ValueError:
                index = None
                print "Invalid index!"
            if index != None:
                if index >= values.size():
                    print "That is not a valid location!"
                else:
                    values.remove(index)
                    update_train(win, values, graphical_cars)
        elif command == "q":
            print "Goodbye!"
        else:
            print "I did not understand."
    
main()
