import pygame, math, random, entity

def load_image(path):
    surface = pygame.image.load(path)
    surface.convert()
    pygame.surfarray.pixels3d(surface)[:,:,0:1:] = 0
    return surface

def line_line_intersect(x1, y1, x2, y2, x3, y3, x4, y4):
    # Taken from http://paulbourke.net/geometry/lineline2d/
    # Denominator for ua and ub are the same, so store this calculation
    d = float((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1))
    # n_a and n_b are calculated as seperate values for readability
    n_a = float((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3))
    n_b = float((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3))
    # Make sure there is not a division by zero - this also indicates that
    # the lines are parallel.  
    # If n_a and n_b were both equal to zero the lines would be on top of each 
    # other (coincidental).  This check is not done because it is not 
    # necessary for this implementation (the parallel check accounts for this).
    if d == 0:
        return False
    # Calculate the intermediate fractional point that the lines potentially intersect.
    ua = n_a / d
    ub = n_b / d
    # The fractional point will be between 0 and 1 inclusive if the lines
    # intersect.  If the fractional calculation is larger than 1 or smaller
    # than 0 the lines would need to be longer to intersect.
    if ua >= 0. and ua <= 1. and ub >= 0. and ub <= 1.:
        return [x1 + (ua * (x2 - x1)), y1 + (ua * (y2 - y1))]
    return False
    
class Game(object):
    def __init__(self, player_left, player_right, configuration):
        self.player_left = player_left
        self.player_right = player_right
        self.configuration = configuration
        self.background = pygame.Surface(configuration['screen_size'])
        self.sprites = pygame.sprite.OrderedUpdates()
        line = entity.Line(load_image(configuration['line_image']), self.sprites)
        line.rect.topleft = ((configuration['screen_size'][0]-line.rect.width)/2, 0)
        paddle_image = load_image(configuration['paddle_image'])
        self.paddle_left = entity.Paddle(configuration['paddle_velocity'], paddle_image, configuration['paddle_bounds'], self.sprites)
        self.paddle_right = entity.Paddle(configuration['paddle_velocity'], paddle_image, configuration['paddle_bounds'], self.sprites)
        self.paddle_left.rect.topleft = (self.configuration['paddle_left_position'], (self.configuration['screen_size'][1]-self.paddle_left.rect.height)/2)
        self.paddle_right.rect.topleft = (self.configuration['paddle_right_position'], (self.configuration['screen_size'][1]-self.paddle_left.rect.height)/2)
        digit_images = [load_image(configuration['digit_image'] % n) for n in xrange(10)]
        self.score_left = entity.Score(digit_images, self.sprites)
        self.score_left.rect.topleft = configuration['score_left_position']
        self.score_right = entity.Score(digit_images, self.sprites)
        self.score_right.rect.topleft = configuration['score_right_position']
        ball_image = load_image(configuration['ball_image'])
        self.ball = entity.Ball(self.configuration['ball_velocity'], ball_image, self.sprites)
        self.bounds = pygame.Rect(20, 0, configuration['screen_size'][0]-ball_image.get_width()-20, configuration['screen_size'][1]-ball_image.get_height())
        self.sound_missed = pygame.mixer.Sound(configuration['sound_missed'])
        self.sound_paddle = pygame.mixer.Sound(configuration['sound_paddle'])
        self.sound_wall = pygame.mixer.Sound(configuration['sound_wall'])
        self.reset_game(random.random()<0.5)
        self.running = True
        
    def play_sound(self, sound):
        if self.configuration['sound']:
            sound.play()
        
    def reset_game(self, serveLeft=True):
        y = self.configuration['screen_size'][1] - self.ball.rect.height
        self.ball.position_x = (self.configuration['screen_size'][0]-self.ball.rect.width)/2.0
        self.ball.position_y = y * random.random()
        self.ball.velocity = self.configuration['ball_velocity']
        a = random.random() * math.pi / 2. - math.pi / 4.
        self.ball.velocity_vec[0] = self.ball.velocity * math.cos(a)
        self.ball.velocity_vec[1] = self.ball.velocity * math.sin(a)
        if random.random() < 0.5:
            self.ball.velocity_vec[1] = -self.ball.velocity_vec[1]
        if serveLeft:
            self.ball.velocity_vec[0] *= -1
        
    def update(self):
        # Store previous ball position for line-line intersect test later
        ball_position_x = self.ball.position_x
        ball_position_y = self.ball.position_y
        # Update sprites and players
        self.sprites.update()
        self.player_left.update(self.paddle_left, self)
        self.player_right.update(self.paddle_right, self)
        # Paddle collision check. Could probably just do a line-line intersect but I think I prefer having the pixel-pefect result of a rect-rect intersect test as well.
        if self.ball.rect.x < self.bounds.centerx:
            # Left side bullet-through-paper check on ball and paddle
            if self.ball.velocity_vec[0] < 0:
                intersect_point = line_line_intersect(
                    self.paddle_left.rect.right, self.paddle_left.rect.top,
                    self.paddle_left.rect.right, self.paddle_left.rect.bottom,
                    ball_position_x-self.ball.rect.width/2, ball_position_y+self.ball.rect.height/2,
                    self.ball.position_x-self.ball.rect.width/2, self.ball.position_y+self.ball.rect.height/2
                )
                if intersect_point:
                    self.ball.position_y = intersect_point[1]-self.ball.rect.height/2
                if intersect_point or (self.paddle_left.rect.colliderect(self.ball.rect) and self.ball.rect.right > self.paddle_left.rect.right):
                    self.ball.position_x = self.paddle_left.rect.right
                    velocity = self.paddle_left.calculate_bounce(min(1,max(0,(self.ball.rect.centery - self.paddle_left.rect.y)/float(self.paddle_left.rect.height))))
                    self.ball.velocity = min(self.configuration['ball_velocity_max'], self.ball.velocity * self.configuration['ball_velocity_bounce_multiplier'])
                    self.ball.velocity_vec[0] = velocity[0] * self.ball.velocity
                    self.ball.velocity_vec[1] = velocity[1] * self.ball.velocity
                    self.player_left.hit()
                    self.play_sound(self.sound_paddle)
        else:
            # Right side bullet-through-paper check on ball and paddle.
            if self.ball.velocity_vec[0] > 0:
                intersect_point = line_line_intersect(
                    self.paddle_right.rect.left, self.paddle_right.rect.top,
                    self.paddle_right.rect.left, self.paddle_right.rect.bottom,
                    ball_position_x-self.ball.rect.width/2, ball_position_y+self.ball.rect.height/2,
                    self.ball.position_x-self.ball.rect.width/2, self.ball.position_y+self.ball.rect.height/2
                )
                if intersect_point:
                    self.ball.position_y = intersect_point[1]-self.ball.rect.height/2
                if intersect_point or (self.paddle_right.rect.colliderect(self.ball.rect) and self.ball.rect.x < self.paddle_right.rect.x):
                    self.ball.position_x = self.paddle_right.rect.x - self.ball.rect.width
                    velocity = self.paddle_right.calculate_bounce(min(1,max(0,(self.ball.rect.centery - self.paddle_right.rect.y)/float(self.paddle_right.rect.height))))
                    self.ball.velocity = min(self.configuration['ball_velocity_max'], self.ball.velocity * self.configuration['ball_velocity_bounce_multiplier'])
                    self.ball.velocity_vec[0] = -velocity[0] * self.ball.velocity
                    self.ball.velocity_vec[1] = velocity[1] * self.ball.velocity
                    self.player_right.hit()
                    self.play_sound(self.sound_paddle)
        # Bounds collision check
        if self.ball.rect.y < self.bounds.top:
            self.ball.position_y = float(self.bounds.top)
            self.ball.velocity_vec[1] = -self.ball.velocity_vec[1]
            self.play_sound(self.sound_wall)
        elif self.ball.rect.y > self.bounds.bottom:
            self.ball.position_y = float(self.bounds.bottom)
            self.ball.velocity_vec[1] = -self.ball.velocity_vec[1]
            self.play_sound(self.sound_wall)
        # Check the ball is still in play
        if self.ball.rect.x < self.bounds.x:
            self.player_left.lost()
            self.player_right.won()
            self.score_right.score += 1
            self.reset_game(False)
            self.play_sound(self.sound_missed)
        if self.ball.rect.x > self.bounds.right:
            self.player_left.won()
            self.player_right.lost()
            self.score_left.score += 1
            self.reset_game(True)
            self.play_sound(self.sound_missed)

    def draw(self, display_surface):
        self.sprites.clear(display_surface, self.background)
        return self.sprites.draw(display_surface)
