"""
An Invaders-like Controller module for lecture demos.

You should refer to this file to see how to had and remove laser bolts.

Authors: Walker White (wmw2), Lillian Lee (ljl2), and Steve Marschner (srm2)
Date:    November 10, 2017 (Python 3 Version)
"""
import cornell
import random
import math
from game2d import *

############# CONSTANTS #############
# Window Size
WINDOW_WIDTH  = 512
WINDOW_HEIGHT = 512

# Gravity of surface
GRAVITY = -0.2
# Upward velocity
ROCKET_VY = 1.2
# For the explosion
PARTICLE_DIAMETER = 5
MAX_INIT_VEL = 5
PARTICLES_PER_SHELL = 50


############# CONTROLLER CLASS #############
class Pyro(GameApp):
    """
    This class is a controller for lecture demos about interaction and animation.

    This class extends GameApp and implements the various methods necessary for 
    an interactive application.

    * Method start() is called once at the beginning.

    * Method update() is called repeatedly to do animation.

    * Attribute _state keeps track of the current game state.
    
    * The on_touch methods handle mouse (or finger) input.
        
    INSTANCE ATTRIBUTES (Not hiding any):
        view :    the view (inherited from Game)    [GView]
        input :   the input (inherited from Game)   [GInput]
        _rockets: the list of rockets to animate    [list of Rocket]
        _sparks:  the list of sparks to animate     [list of Spark]
        _last:    the last position clicked [GPoint, None if mouse not down last frame]
    """
    
    # THREE MAIN METHODS
    
    def start(self):
        """
        Initializes the program state.
        """
        self._rockets = []
        self._sparks  = []
        self._last = None # Have not clicked mouse yet.
    
    def update(self,dt):
        """
        Animates a single frame. Called every 1/60 of a second.
        
        Parameter dt: The time since the last animation frame.
        Precondition: dt is a float.
        """
        # helper method to process the mouse click
        self._checkClick() 
        # Helper method to move rockets
        self._moveRockets()
    
    def draw(self):
        """
        Draws all particles in the view.
        """
        for rocket in self._rockets:
            rocket.draw(self.view)
        
        for spark in self._sparks:
            spark.draw(self.view)
    
    
    # HELPER METHODS
    def _explodeRocket(self, rocket):
        """
        Explodes the ship rocket.
        
        Parameter rocket: The rocket to explode
        Precondition: rocket must be of type Rocket.
        """ 
        color = cornell.RGB(random.randrange(256),
                            random.randrange(256),
                            random.randrange(256))
        for i in range(PARTICLES_PER_SHELL):
            spark = Spark(rocket.x, rocket.y, color)
            self._sparks.append(spark)
    
    def _checkClick(self):
        """
        Checks for a click and add a Rocket if necessary.
        
        A 'click' is the animation frame after the mouse is pressed for the first
        time in a while (so _lastclick is None).
        """
        # Input stores the touch information
        touch = self.input.touch
        
        if self._last is None and not touch is None:
            # Click happened.  Add rocket to particle list.
            rocket = Rocket(touch.x, touch.y)
            self._rockets.append(rocket)
        
        # Update lastclick
        self._last = touch

    
    def _moveRockets(self):
        """
        Moves all the rockets and explodes them when they get to high.
        """
        for rocket in self._rockets:
            rocket.move()
            # Handle a rocket if it explodes
            if rocket.isExploded():  # MUST use getter here
                self._explodeRocket(rocket)
        
        # Delete any exploded rockets
        i = 0
        while i < len(self._rockets):
            if self._rockets[i].isExploded():  # MUST use getter here
                del self._rockets[i]
            else:
                i += 1
        
        # move all the sparks
        for spark in self._sparks:
            spark.move()
        
        # Delete any sparks out of view
        i = 0
        while i < len(self._sparks):
            if self._sparks[i].y < -10:
                del self._sparks[i]
            else:
                i += 1


############# MODEL CLASSES #############
class Spark(GEllipse):
    """
    This class represents particles created in shell explosions.
    
    INSTANCE ATTRIBUTES (Beyond those in GEllipse):
        _vx: velocity in x direction    [float]
        _vy: velocity in y direction    [float]
    """
    
    def __init__(self, x, y, color=cornell.WHITE):
        """
        Initializer: Creates particle at (x,y) with random velocity and given color.
        
        Parameter x: the starting x-coordinate
        Precondition: x is a number (int or float)
        
        Parameter y: the starting y-coordinate
        Precondition: y is a number (int or float)
        
        Parameter color: the spark color
        Precondition: color is a valid color object or name (e.g. a string)
        """
        GEllipse.__init__(self, x=x, y=y, 
                          width=PARTICLE_DIAMETER, height=PARTICLE_DIAMETER,
                          fillcolor=color)        
        self._vy = random.uniform(-MAX_INIT_VEL,MAX_INIT_VEL)
        self._vx = math.sqrt(MAX_INIT_VEL**2 - self._vy**2) * math.sin(random.uniform(0,2*math.pi))
    
    def move(self):
        """
        Moves the spark by the current velocity
        """
        self.x += self._vx
        self.y += self._vy
        self._vy += GRAVITY


class Rocket(GEllipse):
    """
    This class represents rockets that will generate explosions later.
    
    INSTANCE ATTRIBUTES (Beyond those in GEllipse):
        _trigger_y: the y coordinate at which rocket will explode    [float]
        _exploded:  True if the rocket has exploded 
                    [bool, True if self.y > self.trigger_y]
    """
    
    # Must use getters to access with an object of another class
    def isExploded(self):
        """
        Returns: True if rocket has exploded, False otherwise
        """
        return self._exploded
    
    def __init__(self, x, y):
        """
        Initializer: Creates a rocket headed for an explosion at (x,y).
        
        Parameter x: the starting x-coordinate
        Precondition: x is a number (int or float)
        
        Parameter y: the y=threshold for the explosion
        Precondition: y is a number (int or float)
        """
        GEllipse.__init__(self, x=x, y=0, 
                          width=PARTICLE_DIAMETER, height=PARTICLE_DIAMETER,
                          fillcolor=cornell.GRAY)    
        self._trigger_y = y
        self._exploded = False
    
    def move(self):
        """
        Moves the rocket and sets it to explode as necessary.
        """
        self.y += ROCKET_VY
        if self.y > self._trigger_y:
            self._exploded = True


# Script Code
if __name__ == '__main__':
    Pyro(width=WINDOW_WIDTH,height=WINDOW_HEIGHT,fps=30.0).run()