import pygame
import random
from enum import Enum
from collections import namedtuple
import numpy as np

pygame.init()
font = pygame.font.Font('arial.ttf', 25)


# font = pygame.font.SysFont('arial', 25)

class Direction(Enum):
    RIGHT = 1
    LEFT = 2
    UP = 3
    DOWN = 4


Point = namedtuple('Point', 'x, y')

# rgb colors
WHITE = (255, 255, 255)
TUR = (72, 209, 204)
BLUE3 = (176, 196, 222)
RED = (200, 0, 0)
BLUE1 = (0, 0, 255)
BLUE2 = (0, 0, 128)
BLACK = (0, 0, 0)

BLOCK_SIZE = 20
SPEED = 40


class SnakeGameAI:

    def __init__(self, w=1000, h=1000):
        self.w = w
        self.h = h
        # init display
        self.display = pygame.display.set_mode((self.w, self.h))
        pygame.display.set_caption('SnakeAgent')
        self.clock = pygame.time.Clock()
        self.reset()

    def reset(self):
        # init game state
        self.direction = Direction.RIGHT
        self.direction2 = Direction.LEFT

        self.head = Point(self.w / 2, self.h / 2)
        self.snake = [self.head,
                      Point(self.head.x - BLOCK_SIZE, self.head.y),
                      Point(self.head.x - (2 * BLOCK_SIZE), self.head.y)]

        self.head2 = Point(self.w / 4, self.h / 2)
        self.snake2 = [self.head2,
                       Point(self.head2.x - BLOCK_SIZE, self.head2.y),
                       Point(self.head2.x - (2 * BLOCK_SIZE), self.head2.y)]

        self.score = 0
        self.food = None
        self._place_food()
        self.frame_iteration = 0

    def _place_food(self):
        x = random.randint(0, (self.w - BLOCK_SIZE) // BLOCK_SIZE) * BLOCK_SIZE
        y = random.randint(0, (self.h - BLOCK_SIZE) // BLOCK_SIZE) * BLOCK_SIZE
        self.food = Point(x, y)
        if self.food in self.snake:
            self._place_food()
            if self.food in self.snake2:
                self._place_food()


    def play_step(self, action):
        self.frame_iteration += 1
        # 1. collect user input
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

        # 2. move
        self._move(action)  # update the head2
        self.snake.insert(0, self.head)
        self.snake2.insert(0, self.head2)

        # 3. check if game over
        reward = 0
        game_over = False
        if self.is_collision() or self.frame_iteration > 100 * len(
                self.snake) or self.frame_iteration > 100 * len(self.snake2):
            game_over = True
            reward = -20
            return reward, game_over, self.score

        if self.head == self.food or self.head2 == self.food:
            self.score += 1
            reward = 50
            self._place_food()
        else:
            self.snake.pop()
            self.snake2.pop()


        # 5. update ui and clock
        self._update_ui()
        self.clock.tick(SPEED)
        # 6. return game over and score
        return reward, game_over, self.score

    def is_collision(self, pt=None, ps=None):
        if pt is None:
            pt = self.head2
        if ps is None:
            ps = self.head

        # hits boundary
        if pt.x > self.w - BLOCK_SIZE or pt.x < 0 or pt.y > self.h - BLOCK_SIZE or pt.y < 0:
            return True

        if ps.x > self.w - BLOCK_SIZE or ps.x < 0 or ps.y > self.h - BLOCK_SIZE or ps.y < 0:
            return True
        # hits itself
        if pt in self.snake2[1:]:
            return True
        if ps in self.snake[1:]:
            return True

        return False

    def _update_ui(self):
        self.display.fill(BLUE2)

        for pt in self.snake:
            pygame.draw.rect(self.display, TUR, pygame.Rect(pt.x, pt.y, BLOCK_SIZE, BLOCK_SIZE))
            pygame.draw.rect(self.display, BLUE2, pygame.Rect(pt.x + 4, pt.y + 4, 12, 12))

            for ps in self.snake2:
                pygame.draw.rect(self.display, WHITE, pygame.Rect(ps.x, ps.y, BLOCK_SIZE, BLOCK_SIZE))
                pygame.draw.rect(self.display, BLACK, pygame.Rect(ps.x + 4, ps.y + 4, 12, 12))

        pygame.draw.rect(self.display, RED, pygame.Rect(self.food.x, self.food.y, BLOCK_SIZE, BLOCK_SIZE))

        text = font.render("Score: " + str(self.score), True, WHITE)
        self.display.blit(text, [0, 0])
        pygame.display.flip()

    def _move(self, action):
        # [straight, right, left]

        global new_dir2
        clock_wise = [Direction.RIGHT, Direction.DOWN, Direction.LEFT, Direction.UP]
        rev_clock_wise = [Direction.UP, Direction.LEFT, Direction.DOWN, Direction.RIGHT]

        idx = clock_wise.index(self.direction)
        idx2 = rev_clock_wise.index(self.direction2)

        if np.array_equal(action, [1, 0, 0]):
            new_dir2 = rev_clock_wise[idx2]  # no change
        elif np.array_equal(action, [0, 1, 0]):
            next_idx2 = (idx2 + 1) % 4
            new_dir2 = rev_clock_wise[next_idx2]  # right turn r -> d -> l -> u
        else:  # [0, 0, 1]
            next_idx = (idx2 - 1) % 4
            new_dir2 = rev_clock_wise[next_idx]  # left turn r -> u -> l -> d

        if np.array_equal(action, [1, 0, 0]):
            new_dir = clock_wise[idx]  # no change
        elif np.array_equal(action, [0, 1, 0]):
            next_idx = (idx + 1) % 4
            new_dir = clock_wise[next_idx]  # right turn r -> d -> l -> u
        else:  # [0, 0, 1]
            next_idx = (idx - 1) % 4
            new_dir = clock_wise[next_idx]  # left turn r -> u -> l -> d

        self.direction = new_dir
        self.direction2 = new_dir2
        z = self.head.x
        w = self.head.y

        if self.direction2 == Direction.RIGHT:
            z += BLOCK_SIZE
        elif self.direction2 == Direction.LEFT:
            z -= BLOCK_SIZE
        elif self.direction2 == Direction.DOWN:
            w += BLOCK_SIZE
        elif self.direction2 == Direction.UP:
            w -= BLOCK_SIZE

        x = self.head2.x
        y = self.head2.y
        if self.direction == Direction.RIGHT:
            x += BLOCK_SIZE
        elif self.direction == Direction.LEFT:
            x -= BLOCK_SIZE
        elif self.direction == Direction.DOWN:
            y += BLOCK_SIZE
        elif self.direction == Direction.UP:
            y -= BLOCK_SIZE

        self.head2 = Point(x, y)
        self.head = Point(z, w)