import random
import pygame
import sys

# global variables
WIDTH = 24
HEIGHT = 24
SIZE = 20
SCREEN_WIDTH = WIDTH * SIZE
SCREEN_HEIGHT = HEIGHT * SIZE

DIR = {
    'u' : (0, -1), # north is -y
    'd' : (0, 1),
    'l' : (-1,0),
    'r' : (1,0)
}

class Snake(object):
    l = 1
    body = [(WIDTH // 2 + 1, HEIGHT // 2),(WIDTH // 2, HEIGHT // 2)]
    direction = 'r'
    dead = False

    def __init__(self):
        pass
    
    def get_color(self, i):
        hc = (40,50,100)
        tc = (90,130,255)
        return tuple(map(lambda x,y: (x * (self.l - i) + y * i ) / self.l, hc, tc))

    def get_head(self):
        return self.body[0]

    def turn(self, dir):
        # TODO: See section 3, "Turning the snake".

        self.direction = dir

        pass

    def collision(self, x, y):
        # TODO: See section 2, "Collisions", and section 4, "Self Collisions"
        if x >= WIDTH or x < 0 or y >= HEIGHT or y < 0:
            return True

        for i in range(1, len(self.body)):
            if self.body[0] == self.body[i]:
                return True

        return False
        pass
    
    def coyote_time(self):
        # TODO: See section 13, "coyote time".
        pass

    def move(self):
        # TODO: See section 1, "Move the snake!". You will be revisiting this section a few times.

        # Goes through every element in self.body and updates its position
        headPos = self.body[0]
        if self.collision(headPos[0], headPos[1]):
            self.kill()

        # Makes sure the snake's body length is correct and adds another element in the body whose position will be updated in the following for loop
        if self.l == len(self.body):
            self.body.append((0, 0))

        for i in range(len(self.body) - 1, 0, -1):
            # The rest of the body parts just need to follow the head so we will set every body element position equal to the position of the body element before it
            self.body[i] = self.body[i-1]
        pass

        # Calculating the new position that head needs to be at
        newHeadPos = (headPos[0] + DIR[self.direction][0], headPos[1] + DIR[self.direction][1])
        self.body[0] = newHeadPos


    def kill(self):
        # TODO: See section 11, "Try again!"
        self.dead = True

    def draw(self, surface):
        for i in range(len(self.body)):
            p = self.body[i]
            pos = (p[0] * SIZE, p[1] * SIZE)
            r = pygame.Rect(pos, (SIZE, SIZE))
            pygame.draw.rect(surface, self.get_color(i), r)

    def handle_keypress(self, k):
        if k == pygame.K_UP:
            self.turn('u')
        if k == pygame.K_DOWN:
            self.turn('d')
        if k == pygame.K_LEFT:
            self.turn('l')
        if k == pygame.K_RIGHT:
            self.turn('r')
    
    def check_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type != pygame.KEYDOWN:
                continue
            self.handle_keypress(event.key)
    
    def wait_for_key(self):
        # TODO: see section 10, "wait for user input".
        waitingForPress = True

        # Waits for user to press Right Key
        while(waitingForPress):
            for event in pygame.event.get():
                if event.type != pygame.KEYDOWN:
                    continue
                elif event.key == pygame.K_RIGHT:
                    waitingForPress = False
        pass


# returns an integer between 0 and n, inclusive.
def rand_int(n):
    return random.randint(0, n)

class Apple(object):
    position = (10,10)
    color = (233, 70, 29)
    def __init__(self):
        pass

    def place(self, snake):
        # TODO: see section 6, "moving the apple".
        # Generating random new position for the apple
        newPos = (rand_int(WIDTH - 1), rand_int(HEIGHT - 1))

        # If the random position is the same as the snake then we run the function again
        for i in range(len(snake.body)):
            if newPos == snake.body[i]:
                self.place(snake)
                return

        # If we find a valid position we update the position of the apple
        self.position = newPos
        pass

    def draw(self, surface):
        pos = (self.position[0] * SIZE, self.position[1] * SIZE)
        r = pygame.Rect(pos, (SIZE, SIZE))
        pygame.draw.rect(surface, self.color, r)

def draw_grid(surface):
    for y in range(0, HEIGHT):
        for x in range(0, WIDTH):
            r = pygame.Rect((x * SIZE, y * SIZE), (SIZE, SIZE))
            color = (169,215,81) if (x+y) % 2 == 0 else (162,208,73)
            pygame.draw.rect(surface, color, r)

def main():
    pygame.init()

    clock = pygame.time.Clock()
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), 0, 32)

    surface = pygame.Surface(screen.get_size())
    surface = surface.convert()
    draw_grid(surface)

    snake = Snake()
    apple = Apple()

    score = 0

    # Used for feature #9 -  Increasing Difficulty
    tickSpeed = 10

    # Waits for User to Begin Game
    draw_grid(surface)   
    screen.blit(surface, (0,0))
    font = pygame.font.Font('freesansbold.ttf', 32) 
    beginText = font.render('Press Right Key to Begin', True, (0, 0, 128)) 
    beginRect = beginText.get_rect()
    beginRect.center = (SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)   
    screen.blit(beginText, beginRect) 
    pygame.display.update()
    snake.wait_for_key()


    while True:
        # Implements feature #9 - Increasing Difficulty
        clock.tick(tickSpeed)
        snake.check_events()
        draw_grid(surface)   
        snake.move()

        snake.draw(surface)
        apple.draw(surface)
        # TODO: see section 5, "Eating the Apple".
        # If the snake head is the the apple's position the snake has "eaten" the apple
        if snake.get_head() == apple.position:
            # Updating the location of the apple
            apple.place(snake)
            snake.l += 1
            score += 1

            # Feature #9 - Increasing difficult of game by increasing tick time
            tickSpeed += 0.5

        screen.blit(surface, (0,0))
        # TODO: see section 8, "Display the Score"

        # Displaying the score
        font = pygame.font.Font('freesansbold.ttf', 32) 
        scoreText = font.render(str(score), True, (0, 0, 128)) 
        scoreRect = scoreText.get_rect()
        scoreRect.center = (SIZE, SIZE)   
        screen.blit(scoreText, scoreRect) 

        pygame.display.update()
        if snake.dead:
            print(f'You died. Score: {score}')
            pygame.quit()
            sys.exit(0)

if __name__ == "__main__":
    main()