import grpc
import numpy as np
import minecraft_pb2_grpc
from minecraft_pb2 import *
from utils_coordinates import *
from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2

class ServerClient:
    
    _max_block_query = 100000 

    def __init__(self):
        self.channel = grpc.insecure_channel('localhost:5001')
        self.client = minecraft_pb2_grpc.MinecraftServiceStub(self.channel)

    @staticmethod
    def create_block(coor:Coordinates, type:BlockType, orient:Orientation=NONE) -> Block:
        return Block(
            position=coor.to_point(),
            type=type,
            orientation=orient
        )

    @staticmethod
    def create_sign_descriptor(block:Block, text:str) -> SignDescriptor:
        return SignDescriptor(block=block, text=text)

    @staticmethod
    def create_blocks_from_np_array(coordinate:Coordinates, np_types:np.ndarray[BlockType], orientation=NONE) -> list[Block]:
        blocks = []
        for index in np.ndindex(np.shape(np_types)):
            blocks.append(Block(
                position=(coordinate+ Coordinates.from_tuple(index)).to_point(),
                type=np_types[index],
                orientation=orientation
            ))
        return blocks

    @staticmethod
    def create_cube_point(coordinate:Coordinates) -> BlockInfo:
        return Cube(
            min=coordinate.to_point(),
            max=coordinate.to_point()
        )

    @staticmethod
    def create_cube_around_loc(coordinate:Coordinates, shape:Coordinates) -> Cube:
        shape_half = shape/2
        return Cube(
                min=(coordinate - shape_half - 1).to_point(),
                max=(coordinate + shape_half).to_point()
        )

    @staticmethod
    def create_cube_from_loc(coordinate:Coordinates, shape:Coordinates) -> Cube:
        return Cube(
            min=coordinate.to_point(),
            max=(coordinate +shape -1).to_point()
        )

    @staticmethod
    def create_fill_request_around_loc(coordinate:Coordinates, shape:Coordinates, type:BlockType) -> FillCubeRequest:
        return FillCubeRequest(cube=ServerClient.create_cube_around_loc(coordinate, shape), type=type)

    @staticmethod
    def create_fill_request_from_loc(coordinate:Coordinates, shape:Coordinates, type:BlockType) -> FillCubeRequest:
        return FillCubeRequest(cube=ServerClient.create_cube_from_loc(coordinate, shape), type=type)

    @staticmethod
    def create_np_array_from_read_blocks(blocks:BlockInfo, shape:Coordinates, func) -> np.ndarray:
        return np.reshape([func(block) for block in blocks], shape.to_tuple())


    def place_blocks_around_loc(self, coordinate:Coordinates, shape:Coordinates, type:BlockType) -> None:
        self.client.fillCube(ServerClient.create_fill_request_around_loc(coordinate, shape, type))

    def place_blocks_from_loc(self, coordinate:Coordinates, shape:Coordinates, type:BlockType) -> None:
        self.client.fillCube(ServerClient.create_fill_request_from_loc(coordinate, shape, type))

    def place_fill_requests(self, fill_request_list:list[FillCubeRequest]) -> None:
        self.client.fillCubeList(FillCubeRequests(fill_cube_requests=fill_request_list))

    def place_block_types_from_np_array(self, coordinate:Coordinates, np_types:np.ndarray[BlockType], orientation=NONE) -> None:
        self.client.spawnBlocks(Blocks(blocks=ServerClient.create_blocks_from_np_array(coordinate, np_types, orientation)))

    def place_blocks_from_list(self, blocks:list[Block]) -> None:
        for i in range(0, len(blocks), ServerClient._max_block_query):
            self.client.spawnBlocks(Blocks(blocks=blocks[i:i + ServerClient._max_block_query]))

    def place_sign(self, sign:SignDescriptor):
        self.client.signsWithText(SignDescriptors(sign_descriptors=[sign]))

    def place_signs(self, signs:list[SignDescriptor]):
        self.client.signsWithText(SignDescriptors(sign_descriptors=signs))


    def read_block(self, coordinate:Coordinates) -> BlockInfo:
        return self.client.readCube(Cube(
            min=coordinate.to_point(),
            max=coordinate.to_point()
        )).blocks[0]

    def read_blocks_from_loc(self, coordinate:Coordinates, shape:Coordinates) -> list[BlockInfo]:
        return self.client.readCube(ServerClient.create_cube_from_loc(coordinate, shape)).blocks

    def read_blocks_around_loc(self, coordinate:Coordinates, shape:Coordinates) -> list[BlockInfo]:
        return self.client.readCube(ServerClient.create_cube_around_loc(coordinate, shape)).blocks

    def read_cubes(self, cubes:list[Cube]) -> list[list[BlockInfo]]:
        results = self.client.readCubeList(Cubes(cubes=cubes)).blocks_infos
        return [result.blocks for result in results]

    def send_message(self, text:str) -> None:
        self.client.sendMessage(Message(text=text))

    def get_message(self) -> str:
        return self.client.readResponse(google_dot_protobuf_dot_empty__pb2.Empty()).text

    def ask_for_response(self, text:str) -> str:
        self.send_message(text)
        return self.get_message()
