class GameEnvironment:
    """
        Class that models the environment in which the game is played.
        instance variables: screen : the pygame display
                            surface : the pygame surface
                            snake : A list of Snake objects
                            fps : The speed of the game
                            dir_change_location : A dictionary of coordinates
                             pointing to directions to be followed at those
                             coordinates
                            food : A Food object
                            status : States whether the game is running or over
                            It is 1 if game is running, 0 if game is over.
        """
    def __init__(self):
        pygame.init()
        self.status = 1
        self.screen = pygame.display
        self.surface = self.screen.set_mode((800, 600))
        self.snake = [Snake(200, 200, 'r')]
        self.fps = 100
        self.dir_change_location = {}
        self.food = Food()
        self.food.generate_food()

    def events(self):
        """
                Contains the event loop for the pygame window.Rectangle
                images for the snake and the Circle images for food are created
                in this event loop. Calls to other GameEnvironment methods are
                made from here.
                :return: None
        """
        done = False
        clock = pygame.time.Clock()
        while not done:  # start of the event loop
            for event in pygame.event.get():  # piping out the events
                if event.type == pygame.QUIT:
                    self.status = 0
                    done = True
            self.food.set_level()  # getting the food level set for the game
            self.fps = (self.food.level * 10) + 100
            # setting the speed of the game according the food level
            self.check_wall()  # checking if the snake has banged into the wall
            self.check_suicide()  # checking if the snake has turned into itself
            if self.status == 0:  # checking if the snake is supposed to dead.
                done = True
            self.check_food()  # checking if the snake has eaten food.
            pressed = pygame.key.get_pressed()
            self.update_position(pressed, self.snake[0].x_coord,
                                 self.snake[0].y_coord)
            # updating the position of the snake according to input direction
            self.surface.fill((0, 0, 0))
            for i in range(len(self.food.x)):  # loop to display all food.
                pygame.draw.circle(self.surface, (255, 255, 255),
                                   (self.food.x[i], self.food.y[i]), 10)
            for i in range(len(self.snake)):  # loop to display entire snake
                pygame.draw.rect(self.surface, (55, 255, 55),
                                 pygame.Rect(self.snake[i].get_position()[0],
                                             self.snake[i].get_position()[1],
                                             20, 20))

            self.screen.flip()
            clock.tick(self.fps)

    def update_position(self, pressed, x_coord, y_coord):
        """
        Method to make sure that all snake objects change direction at given
        point
        :param pressed: A pygame key object
        :param x_coord: The x coordinate where the change in direction happens
        :param y_coord: The y coordinate where the change in direction happens
        :return: None
        """
        dir = ''  # String to represent the input direction
        if pressed[pygame.K_LEFT]:  # Condition when left arrow key is pressed
            dir = 'l'
        elif pressed[pygame.K_UP]:  # Condition when up arrow key is pressed
            dir = 'u'
        elif pressed[pygame.K_DOWN]:  # Condition when down arrow key is pressed
            dir = 'd'
        elif pressed[pygame.K_RIGHT]:  # Condition for right arrow key press
            dir = 'r'
        if dir != '':  # when there is no input, continue in original direction
            self.dir_change_location[(x_coord, y_coord)] = dir
        for i in range(len(self.snake)):
            if (self.snake[i].x_coord, self.snake[i].y_coord) in \
                    self.dir_change_location.keys():
                # checking if a snake node has reached a point where it has to
                # change direction.
                old_x = self.snake[i].x_coord
                old_y = self.snake[i].y_coord
                self.snake[i].move(
                    self.dir_change_location[(self.snake[i].x_coord,
                                              self.snake[i].y_coord)])
                # moving that snake object in the direction it has to go in at
                # that point. The direction is given by the dictionary.
                if i == len(self.snake) - 1:
                    del self.dir_change_location[(old_x, old_y)]
                    # once the last snake node passes a point, get rid of that
                    # point in the dictionary to prevent the snake from
                    # following old instructions
            else:
                self.snake[i].move(self.snake[i].direction)


    def check_wall(self):
        """
        Method to check if the snake's head has hit the wall. Ends the game if
        this happens.
        :return: None
        """
        head = self.snake[0]
        x = head.get_position()[0]
        y = head.get_position()[1]
        if x > 800 or y > 600 or x < 0 or y < 0:
            # checking if the head of the snake collides with a wall.
            self.status = 0  # killing the snake

    def check_food(self):
        """
        Checks if the snake has passed a food object. The length of the snake
        increases if this happens. Makes appropriate changes to the food object
        too.
        :return:
        """
        for i in range(len(self.food.x)): # iterating through all food on screen
            x = self.food.x[i]
            y = self.food.y[i]
            if pygame.Rect(self.snake[i].get_position()[0],
                           self.snake[i].get_position()[1],
                           20, 20).collidepoint(x, y):
                # checking if the food snake's head collides with a food object
                self.snake.append(
                    self.snake[len(self.snake) - 1].get_next_snake())
                # making the snake longer.
                self.food.get_eaten(i)
                # recording that food was eaten for scoring purposes
                break

    def check_suicide(self):
        """
        Checks if the snake turns into itself. Ends the game if this happens
        :return: None
        """
        head = pygame.Rect(self.snake[0].get_position()[0],
                           self.snake[0].get_position()[1],
                           20, 20)
        for snake in self.snake[1:]:
            body = pygame.Rect(snake.get_position()[0],
                               snake.get_position()[1],
                               20, 20)
            if head.collidepoint(body.centerx, body.centery):
                # checking if the center of the snake's head is in the center of
                # a part of its body
                self.status = 0  # killing the snake
                break