import copy
import warnings
from minecraft_pb2 import *
from utils_coordinates import *
from utils_server_client import *

class Margin():
    
    def __init__(self, west, north, east, south) -> None:
        
        self.west = west
        self.north = north
        
        self.east = east
        self.south = south
    
    @classmethod
    def from_coordinates(cls, lower:Coordinates, upper:Coordinates):
        return cls(lower.x, lower.z, upper.x, upper.z)
    
    def zero():
        return Margin(0,0, 0,0)
    
    def get_lower_margin(self):
        return Coordinates(self.west, 0, self.north)
    
    def get_upper_margin(self):
        return Coordinates(self.east, 0, self.south)

class WorkspaceManager:
    
    def __init__(self, server_client:ServerClient) -> None:
        self.server_client = server_client
        self._shape = None
        self._coordinates_list = None
        self._delete_grid = None
        self._margin = None
    
    def create_grid_workspace(self,
            starting_point:Coordinates,
            dimensions:Coordinates,
            shape:Coordinates,
            spacing:Coordinates,
            type = AIR, create_platform=False, **kwargs
        ):
        
        c_zero = Coordinates.zero()
        if dimensions<=c_zero:
            raise ValueError("Dimensions must be positive")
        if shape<=c_zero:
            raise ValueError("Shape must be positive")
        if spacing<c_zero:
            raise ValueError("Spacing must be positive") 

        
        platform_type = kwargs.get("block_type", GRASS_BLOCK)
        self._margin:Margin = kwargs.get("margin", Margin.zero())
        
        if self._margin.get_lower_margin()+self._margin.get_upper_margin()>shape:
            warnings.warn("Margins are bigger than spacing!", category=UserWarning, stacklevel=1)
        
        offset = 0
        if create_platform:
            spacing.y +=1
            offset = 1
        
        true_shape = shape + self._margin.get_lower_margin() + self._margin.get_upper_margin() + spacing
        worspace_shape = Coordinates(true_shape.x* dimensions.x, true_shape.y* dimensions.y, true_shape.z* dimensions.z)
        
        self.server_client.place_blocks_from_loc(
            starting_point - self._margin.get_lower_margin() - offset,
            worspace_shape,
            type
        )
        
        coordinates_list = []
        for y in range(dimensions.y):
            for z in range(dimensions.z):
                for x in range(dimensions.x):
                    coordinates_list.append(
                        starting_point + 
                        Coordinates(
                            true_shape.x*x,
                            true_shape.y*y,
                            true_shape.z*z
                        )
                    )
        
        if create_platform:
            platfrom_shape = Coordinates(shape.x, 1, shape.z)
            platfrom_requests = []
            for coor in coordinates_list:
                platfrom_requests.append(
                    ServerClient.create_fill_request_from_loc(
                        coor - self._margin.get_lower_margin() + Coordinates.cardinal_down(),
                        self._margin.get_lower_margin() + platfrom_shape + self._margin.get_upper_margin(),
                        platform_type
                    )
                )
            self.server_client.place_fill_requests(platfrom_requests)
        
        self._shape = shape
        self._coordinates_list = coordinates_list
        
        self._clean_up_request = self.create_fill_shape_request()
        self._read_cubes_request = self.create_read_shape_request()
        
        self._delete_grid = lambda: self.server_client.place_blocks_from_loc(
            starting_point - self._margin.get_lower_margin() - offset,
            worspace_shape,
            AIR
        )
        
        self.server_client.send_message(f"Workspace created at {starting_point}")
    
    def get_shape(self):
        return copy.deepcopy(self._shape)
    
    def get_coordinates_list(self):
        return copy.deepcopy(self._coordinates_list)
    
    def get_end_coordinates_list(self):
        return [coor+self._shape-1 for coor in self._coordinates_list]
    
    def get_offseted_coordinates_list(self, offset:Coordinates):
        return [coor+offset for coor in self._coordinates_list]

    def get_workspace_size(self):
        return len(self._coordinates_list)
    
    def create_read_shape_request(self, shape=None, offset=None):
        if offset==None:
            offset = Coordinates.zero()
        if shape==None:
            shape = self.get_shape() - offset
        
        request = []
        for coor in  self.get_coordinates_list():
            request.append(
                ServerClient.create_cube_from_loc(coor + offset, shape)
            )
        return request
    
    def create_fill_shape_request(self, shape=None, offset=None, block=AIR):
        if offset==None:
            offset = Coordinates.zero()
        if shape==None:
            shape = self.get_shape() - offset
        
        request = []
        for coor in  self.get_coordinates_list():
            request.append(
                ServerClient.create_fill_request_from_loc(coor + offset, shape, block)
            )
        return request
    
    def create_sign_describtion_request(self, messages, sign=OAK_SIGN, return_clean_request = False):
        def _add_to_requests(_coor, _message, _request:list, _clean_up:list = None):
                _request.append(
                    ServerClient.create_sign_descriptor(
                        ServerClient.create_block(_coor, sign, NORTH),
                        _message
                    )
                )
                if _clean_up is not None:
                    _clean_up.append(
                        ServerClient.create_block(offseted_coor, AIR)
                    )
        
        offset = Coordinates(self._shape.x, 0, -self._margin.north-1)

        request = []
        clean_up = [] if return_clean_request else None
        for coor, message in zip(self.get_coordinates_list(), messages):
            offseted_coor = coor + offset
            if isinstance(message, str):
                _add_to_requests(offseted_coor, message, request, clean_up)
            elif hasattr(message, "__iter__"):
                for message_part in message:
                    if isinstance(message_part, str):
                        _add_to_requests(offseted_coor, message_part, request, clean_up)
                    offseted_coor = Coordinates.move_west(offseted_coor)

        return request if clean_up is None else request, clean_up
    
    def clean_workspace(self):
        self.server_client.place_fill_requests(self._clean_up_request)

    def read_workspace(self):
        return self.server_client.read_cubes(self._read_cubes_request)

    def delete_workspace_grid(self):
        self._delete_grid()
        
        self._shape = None
        self._coordinates_list = None
        self._delete_grid = None