from typing import List, Tuple
from pygame import mixer
import pygame
import copy

from entity.Entity import Entity
from util.ConfigurationManager import ConfigurationManager

''' 
    For information on the Tetris piece Tetromino go here:
    https://tetris.fandom.com/wiki/Tetromino
'''
class Piece(Entity):
    
    GRAVITY = 300 # A move down every 300 ms
    PIECE_SET = 750

    def __init__(self, shape: Tuple, position: Tuple, color: str, inner_border_color: str, border_color: str):
        super().__init__(self.__get_points(shape, position), color, border_color)
        self.inner_border_color = inner_border_color
        self.center = self.__get_center(shape, position)
        self.piece_set_sound = mixer.Channel(2)
        self.piece_set_time = 0

        self.previous_points = None
        self.previous_center = None

    def update(self, elapsed_time: int, tetris) -> None:
        super().update(elapsed_time)

        well = tetris.well
        stack = tetris.stack

        tile_size = ConfigurationManager.configuration["engine"]["tile-size"]
        if self.elapsed_time >= Piece.GRAVITY and self.piece_set_time == 0:
            self.elapsed_time -= Piece.GRAVITY

            self.move((0, tile_size))
            if well and self.collide(well) or stack and self.collide(stack):
                self.revert()
                self.piece_set_time += elapsed_time
        
        if self.piece_set_time > 0:
            self.piece_set_time += elapsed_time
            
            if self.piece_set_time >= Piece.PIECE_SET:
                self.__play_piece_set_sound()
                self.piece_set_time = 0

                stack.add_piece(self)
                tetris.current_piece = None

    def draw(self, surface: pygame.Surface) -> None:
        super().draw(surface)

        tile_size = ConfigurationManager.configuration["engine"]["tile-size"]
        for square in self.points:
            if self.inner_border_color:
                vertex_one = (square[0][0] + (tile_size // 10), square[0][1] + (tile_size // 10))
                vertex_two = (square[1][0] - (tile_size // 10), square[1][1] + (tile_size // 10))
                vertex_three = (square[2][0] - (tile_size // 10), square[2][1] - (tile_size // 10))
                vertex_four = (square[3][0] + (tile_size // 10), square[3][1] - (tile_size // 10))
                new_square = (vertex_one, vertex_two, vertex_three, vertex_four)
                pygame.draw.polygon(surface, pygame.Color(self.inner_border_color), new_square, max(tile_size // 6, 1))

    def move(self, vector: Tuple) -> None:
        # reset elapsed time if user moves down to stall gravity
        if vector[1] > 0:
            self.elapsed_time = 0

        self.previous_points = copy.deepcopy(self.points)
        self.previous_center = copy.deepcopy(self.center)

        self.center[0] += vector[0]
        self.center[1] += vector[1]

        for sub_points in self.points:
            for point in sub_points:
                point[0] += vector[0]
                point[1] += vector[1]
 
    '''
        For more information on a rotation of a piece go here:
        https://gamedev.stackexchange.com/questions/17974/how-to-rotate-blocks-in-tetris
    '''
    def rotate(self) -> None:
        self.previous_points = copy.deepcopy(self.points)
        self.previous_center = copy.deepcopy(self.center)

        new_points = []
        for square in self.points:
            for vertex in square:
                h = vertex[0] - self.center[0]
                k = vertex[1] - self.center[1]

                vertex[0] = (k * -1) + self.center[0]
                vertex[1] = h + self.center[1]
            new_points.append([square[-1]] + square[0:-1])
        self.points = new_points

    def revert(self) -> None:
        if self.previous_points and self.previous_center:
            self.points = self.previous_points
            self.center = self.previous_center

    def __play_piece_set_sound(self) -> None:
        piece_set_sound_file = ConfigurationManager.configuration["sound"]["piece-set"]
        self.piece_set_sound.play(mixer.Sound(piece_set_sound_file))

    def __get_points(self, shape: Tuple, position: Tuple) -> List:
        tile_size =  ConfigurationManager.configuration["engine"]["tile-size"]

        points = []
        for square in shape[:-1]:
            sub_points = []
            for vertex in square:
                point = [vertex[0] * tile_size + position[0], vertex[1] * tile_size + position[1]]
                sub_points.append(point)
            points.append(sub_points)

        return points

    def __get_center(self, shape: Tuple, position: Tuple) -> List:
        tile_size = ConfigurationManager.configuration["engine"]["tile-size"]
        center = shape[-1]

        # cast to int and avoid exception from pygame
        return [int(center[0] * tile_size + position[0]), int(center[1] * tile_size + position[1])]

    # shape attributes
    I_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((2, 0), (3, 0), (3, 1), (2, 1)), ((3, 0), (4, 0), (4, 1), (3, 1)), (2, 0))
    J_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((2, 0), (3, 0), (3, 1), (2, 1)), ((2, 1), (3, 1), (3, 2), (2, 2)), (1.5, 0.5))
    L_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((2, 0), (3, 0), (3, 1), (2, 1)), ((0, 1), (1, 1), (1, 2), (0, 2)), (1.5, 0.5))
    O_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((1, 1), (2, 1), (2, 2), (1, 2)), ((0, 1), (1, 1), (1, 2), (0, 2)), (1, 1))
    S_SHAPE = (((0, 1), (1, 1), (1, 2), (0, 2)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((1, 1), (2, 1), (2, 2), (1, 2)), ((2, 0), (3, 0), (3, 1), (2, 1)), (1.5, 0.5))
    T_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((1, 1), (2, 1), (2, 2), (1, 2)), ((2, 0), (3, 0), (3, 1), (2, 1)), (1.5, 0.5))
    Z_SHAPE = (((0, 0), (1, 0), (1, 1), (0, 1)), ((1, 0), (2, 0), (2, 1), (1, 1)), ((1, 1), (2, 1), (2, 2), (1, 2)), ((2, 1), (3, 1), (3, 2), (2, 2)), (1.5, 0.5))