#!/usr/bin/env python3

# File: block.py 
# Description: This file contains the declaration of basic tetris block class.
# Author: Pavel Benáček <pavel.benacek@gmail.com>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import pdb

import constants
import pygame
import math
import copy
import sys

class Block(object):
    """
    Class for handling of tetris block
    """    

    def __init__(self,shape,x,y,screen,color,rotate_en, type):
        """
        Initialize the tetris block class

        Parameters:
            - shape - list of block data. The list contains [X,Y] coordinates of
                      building blocks.
            - x - X coordinate of first tetris shape block
            - y - Y coordinate of first tetris shape block
            - screen  - screen to draw on
            - color - the color of each shape block in RGB notation
            - rotate_en - enable or disable the rotation
        """
        # The initial shape (convert all to Rect objects)
        self.shape = []
        for sh in shape:
            bx = sh[0]*constants.BWIDTH + x
            by = sh[1]*constants.BHEIGHT + y
            block = pygame.Rect(bx,by,constants.BWIDTH,constants.BHEIGHT)
            self.shape.append(block)     
        # Setup the rotation attribute
        self.rotate_en = rotate_en
        # Setup the rest of variables
        self.x = x
        self.y = y
        # Movement in the X,Y coordinates
        self.diffx = 0
        self.diffy = 0
        # Screen to drawn on
        self.screen = screen
        self.color = color
        # Rotation of the screen
        self.diff_rotation = 0
        self.type = type

    def draw(self):
        """
        Draw the block from shape blocks. Each shape block
        is filled with a color and black border.
        """
        for bl in self.shape:
            pygame.draw.rect(self.screen,self.color,bl)
            pygame.draw.rect(self.screen,constants.BLACK,bl,constants.MESH_WIDTH)
        
    def get_rotated(self,x,y):
        """
        Compute the new coordinates based on the rotation angle.
        
        Parameters:
            - x - the X coordinate to transfer
            - y - the Y coordinate to transfer

        Returns the tuple with new (X,Y) coordinates.
        """
        # Use the classic transformation matrix:
        # https://www.siggraph.org/education/materials/HyperGraph/modeling/mod_tran/2drota.htm
        rads = self.diff_rotation * (math.pi / 180.0)
        newx = x*math.cos(rads) - y*math.sin(rads)
        newy = y*math.cos(rads) + x*math.sin(rads)
        return (newx,newy)        

    def move(self,x,y):
        """
        Move all elements of the block using the given offset.
        
        Parameters:
            - x - movement in the X coordinate
            - y - movement in the Y coordinate 
        """
        # Accumulate X,Y coordinates and call the update function       
        self.diffx += x
        self.diffy += y  
        self._update()

    def remove_blocks(self,y):
        """
        Remove blocks on the Y coordinate. All blocks
        above the Y are moved one step down. 

        Parameters:
            - y - Y coordinate to work with.
        """
        new_shape = []
        for shape_i in range(len(self.shape)):
            tmp_shape = self.shape[shape_i]
            if tmp_shape.y < y:
                # Block is above the y, move down and add it to the list of active shape
                # blocks.
                new_shape.append(tmp_shape)  
                tmp_shape.move_ip(0,constants.BHEIGHT)
            elif tmp_shape.y > y:
                # Block is below the y, add it to the list. The block doesn't need to be moved because
                # the removed line is above it.
                new_shape.append(tmp_shape)
        # Setup the new list of block shapes.
        self.shape = new_shape

    def has_blocks(self):
        """
        Returns true if the block has some shape blocks in the shape list.
        """    
        return True if len(self.shape) > 0 else False

    def rotate(self):
        """
        Setup the rotation value to 90 degrees.
        """
        # Setup the rotation and update coordinates of all shape blocks.
        # The block is rotated iff the rotation is enabled
        if self.rotate_en:
            self.diff_rotation = 90
            self._update()

    def rotate_by(self, deg):
        """
        Setup the rotation value to deg degrees.
        """
        # Setup the rotation and update coordinates of all shape blocks.
        # The block is rotated iff the rotation is enabled
        if self.rotate_en:
            self.diff_rotation = deg
            self._update()

    def rotate_clockwise(self):
        """
        Setup the rotation value to 90 degrees.
        """
        # Setup the rotation and update coordinates of all shape blocks.
        # The block is rotated iff the rotation is enabled
        if self.rotate_en:
            self.diff_rotation = 270
            self._update()

    def rotate_counter_clockwise(self):
        """
        Setup the rotation value to 90 degrees.
        """
        # Setup the rotation and update coordinates of all shape blocks.
        # The block is rotated iff the rotation is enabled
        if self.rotate_en:
            self.diff_rotation = 90
            self._update()

    def _update(self):
        """
        Update the position of all shape boxes.
        """
        for bl in self.shape:
            # Get old coordinates and compute new x,y coordinates. 
            # All rotation calculates are done in the original coordinates.
            origX = (bl.x - self.x)/constants.BWIDTH
            origY = (bl.y - self.y)/constants.BHEIGHT
            rx,ry = self.get_rotated(origX,origY)
            newX = rx*constants.BWIDTH  + self.x + self.diffx
            newY = ry*constants.BHEIGHT + self.y + self.diffy
            # Compute the relative move
            newPosX = newX - bl.x
            newPosY = newY - bl.y
            bl.move_ip(newPosX,newPosY)
        # Everyhting was moved. Setup new x,y, coordinates and reset all disable the move
        # variables.
        self.x += self.diffx
        self.y += self.diffy
        self.diffx = 0
        self.diffy = 0
        self.diff_rotation = 0

    def backup(self):
        """
        Backup the current configuration of shape blocks.
        """
        # Make the deep copy of the shape list. Also, remember
        # the current configuration.
        self.shape_copy = copy.deepcopy(self.shape)
        self.x_copy = self.x
        self.y_copy = self.y
        self.rotation_copy = self.diff_rotation

    def backup_config(self):
        shape_backup = copy.deepcopy(self.shape)
        x_backup = self.x
        y_backup = self.y
        rotation_backup = self.diff_rotation
        return shape_backup, x_backup, y_backup, rotation_backup

    def restore(self):
        """
        Restore the previous configuraiton.
        """
        self.shape = self.shape_copy
        self.x = self.x_copy
        self.y = self.y_copy
        self.diff_rotation = self.rotation_copy

    def restore_config(self, shape_backup, x_backup, y_backup, rotation_backup):
        self.shape = shape_backup
        self.x = x_backup
        self.y = y_backup
        self.diff_rotation = rotation_backup

    def check_collision(self,rect_list):
        """
        The function checks if the block colides with any other block
        in the shape list. 

        Parameters:
            - rect_list - the function accepts the list of Rect object which
                         are used for the collistion detection. 
        """
        for blk in rect_list:
            collist = blk.collidelistall(self.shape)
            if len(collist):
                return True
        return False

