import pygame
import random
import tkinter
from tkinter import messagebox

pygame.init()


class Cube(object):
    def __init__(self, start_pos, dir_x=1, dir_y=0, color=(0, 255, 0)):
        self.pos = start_pos
        self.dir_x = 1
        self.dir_y = 0
        self.color = color

    def move(self, dir_x, dir_y):
        self.dir_x = dir_x
        self.dir_y = dir_y
        self.pos = (self.pos[0] + self.dir_x, self.pos[1] + self.dir_y)

    def draw(self):
        dis = 500 // 20
        i = self.pos[0]  # Current row
        j = self.pos[1]  # Current column

        pygame.draw.rect(win, self.color, (i * dis + 1, j * dis + 1, dis - 2, dis - 2))  # Drawing the rectangle
        # Minus 2 just so it doesn't fill up the whole rectangle


class Snake(object):
    body = []  # List containing multiple cube objects which represent the body

    # This is an dictionary which stores the point of turn so the snake won't turn as a whole,
    # rather cube by cube

    turns = {}

    def __init__(self, color, pos):
        self.color = color
        self.pos = pos
        self.head = Cube(pos)  # The head will be the first cube object
        self.body.append(self.head)  # Adding the head to the body list

        # Initial directions
        self.dir_x = 1
        self.dir_y = 0

    def move(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()

            keys = pygame.key.get_pressed()  # Get multiple key presses

            for key in keys:
                if keys[pygame.K_LEFT]:
                    self.dir_x = -1
                    self.dir_y = 0
                    self.turns[self.head.pos[:]] = [self.dir_x, self.dir_y]  # Remembering the direction

                elif keys[pygame.K_RIGHT]:
                    self.dir_x = 1
                    self.dir_y = 0
                    self.turns[self.head.pos[:]] = [self.dir_x, self.dir_y]

                elif keys[pygame.K_UP]:
                    self.dir_x = 0
                    self.dir_y = -1
                    self.turns[self.head.pos[:]] = [self.dir_x, self.dir_y]

                elif keys[pygame.K_DOWN]:
                    self.dir_x = 0
                    self.dir_y = 1
                    self.turns[self.head.pos[:]] = [self.dir_x, self.dir_y]

        for i, c in enumerate(self.body):  # Loop through every cube in the body list
            p = c.pos[:]  # Cube position in the grid
            if p in self.turns:
                turn = self.turns[p]  # Direction for the next turn
                c.move(turn[0], turn[1])
                if i == len(self.body) - 1:  # If it's the last cube
                    self.turns.pop(p)  # It removes the turn
            else:
                # This code just makes the snake appear on the other side if it goes past the screen boundaries
                if c.dir_x == -1 and c.pos[0] <= 0:
                    c.pos = (19, c.pos[1])
                elif c.dir_x == 1 and c.pos[0] >= 19:
                    c.pos = (0, c.pos[1])
                elif c.dir_y == 1 and c.pos[1] >= 19:
                    c.pos = (c.pos[0], 0)
                elif c.dir_y == -1 and c.pos[1] <= 0:
                    c.pos = (c.pos[0], 19)
                else:
                    c.move(c.dir_x, c.dir_y)

    def reset(self, pos):
        self.head = Cube(pos)
        self.body = []
        self.body.append(self.head)
        self.dir_x = 1
        self.dir_y = 0
        self.turns = {}

    def add_cube(self):
        tail = self.body[-1]
        dx, dy = tail.dir_x, tail.dir_y

        # We need to check to which side of the snake to add the cube to
        if dx == 1 and dy == 0:
            self.body.append(Cube((tail.pos[0] - 1, tail.pos[1])))
        elif dx == -1 and dy == 0:
            self.body.append(Cube((tail.pos[0] + 1, tail.pos[1])))
        elif dx == 0 and dy == 1:
            self.body.append(Cube((tail.pos[0], tail.pos[1] - 1)))
        elif dx == 0 and dy == -1:
            self.body.append(Cube((tail.pos[0], tail.pos[1] + 1)))

        # We then set the cubes direction to the direction of the snake.
        self.body[-1].dir_x = dx
        self.body[-1].dir_y = dy

    def draw(self):
        for i, c in enumerate(self.body):
            c.draw()


# Screen
win = pygame.display.set_mode((500, 500))
pygame.display.set_caption("Snake")

# Colors
WHITE = (255, 255, 255)
GRAY = (226, 226, 226)

# Clock
clock = pygame.time.Clock()
FPS = 10

# Fonts
SCORE_FONT = pygame.font.SysFont('helvetica', 30)


def draw_grid(w, rows):
    gap = w // rows

    # Initial x and y
    x = 0
    y = 0

    for i in range(rows):
        x = x + gap
        y = y + gap

        pygame.draw.line(win, GRAY, (x, 0), (x, w))
        pygame.draw.line(win, GRAY, (0, y), (w, y))


def render():
    global s, snack
    score_text = SCORE_FONT.render("Score: {}".format(str(len(s.body))), 1, (0, 0, 0))
    win.fill(WHITE)
    s.draw()
    snack.draw()
    draw_grid(500, 20)
    win.blit(score_text, (10, 10))
    pygame.display.update()


def random_snack(rows, item):
    positions = item.body  # Get the positions of cubes in the snake

    while True:
        # Random snack position
        x = random.randrange(rows)
        y = random.randrange(rows)

        # Setting the snack so it can't be on the same position as the snake
        if len(list(filter(lambda z: z.pos == (x, y), positions))) > 0:
            continue
        else:
            break

    # Returns the position of the snack
    return x, y


def message_box(subject, content):
    root = tkinter.Tk()
    root.attributes("-topmost", True)
    root.withdraw()
    messagebox.showinfo(subject, content)
    try:
        root.destroy()
    except:
        pass


def main():
    global s, snack

    run = True

    s = Snake((255, 0, 0), (10, 10))
    snack = Cube(random_snack(20, s), color=(255, 0, 0))

    while run:
        clock.tick(FPS)
        pygame.time.delay(50)
        s.move()

        if snack.pos == s.body[0].pos:  # Checks if the head collides with the snack
            print("Gje")
            s.add_cube()  # Adds a new cube to the snake
            snack = Cube(random_snack(20, s), color=(255, 0, 0))

        for x in range(len(s.body)):
            # This will check if any of the positions in our body list overlap
            if s.body[x].pos in list(map(lambda z: z.pos, s.body[x + 1:])):
                message_box('You Lost!', 'Play again...')
                s.reset((10, 10))
                break

        render()


if __name__ == '__main__':
    main()
