import time
import grpc
import minecraft_pb2_grpc
from minecraft_pb2 import *
from utils_coordinates import *
import numpy as np
import blocks_by_tags as tags
import blocks_by_item_tags as item_tags
import utils_blocks_categories as cat

class BlockShowcase:
    
    default_point = Coordinates(80085, 0, 0)
    orientations_len = len(Orientation.values())
    blocks_len = len(BlockType.values())
    default_spacing = 2
    default_offset = 3
    
    def __init__(self, client: minecraft_pb2_grpc.MinecraftServiceStub):
        self.client = client
    
    def place_blocks_in_all_orientations(self, block_types, strating_pos: Coordinates, spacing=2, description=None, clean=False, verbose=0):
        orientations = Orientation.values()
        if  spacing<1:
            print("Invalid spacing. Setting spacing to 1")
            spacing =1
        
        if verbose>0:
            print(f"Block Types: {np.array(BlockType.keys())[block_types]}")
            print(f"Orientations: {np.array(Orientation.keys())[orientations]}")
        
        ending_pos = strating_pos + Coordinates(spacing*self.orientations_len,0,spacing*len(block_types))
    
        # Clean working space
        if clean:
            self.client.fillCube(FillCubeRequest( 
                cube=Cube(
                    min=(strating_pos + Coordinates(-1,-1,-1)).to_point(),
                    max=(ending_pos + Coordinates(1,1,1)).to_point()
                ),
                type=AIR
            ))
        
        # Prepare working space
        self.client.fillCube(FillCubeRequest( 
            cube=Cube(
                min=(strating_pos + Coordinates(0,-1,0)).to_point(),
                max=(ending_pos + Coordinates(0,1,0)).to_point()
            ),
            type=BARRIER
        ))

        blocks = []
        signs = []
        # place descriptor sign
        if description:
            signs.append(SignDescriptor(
                block=Block(
                    position=(strating_pos + Coordinates(0,0,-1)).to_point() ,
                    type=OAK_WALL_SIGN,
                    orientation=NORTH
                ),
                text=description
            ))
        
        # print all blocks
        for i, b in enumerate(block_types):
            signs.append(SignDescriptor(
                block=Block(
                    position=(strating_pos + Coordinates(-1, 0, 1+ spacing*i)).to_point(),
                    type=OAK_WALL_SIGN,
                    orientation=WEST
                ),
                text=BlockType.keys()[b].replace('_',' ')
            ))
            for j, o in enumerate(orientations):
                offset = Coordinates(1+ spacing*j, 0, 1+ spacing*i)
                blocks.append(Block(
                    position=(strating_pos+ offset).to_point(),
                    type=b,
                    orientation=o))
        self.client.spawnBlocks(Blocks(blocks=blocks))
        self.client.signsWithText(SignDescriptors(sign_descriptors=signs))
        
    def place_all_categories(self, categories_of_block_types, strating_pos: Coordinates, offset=3, spacing=2, descriptions=[], max_layers=None, verbose=0, waittime=0.5):
        if  offset<3:
            print("Invalid offset. Setting offset to 3")
            offset =3

        # Clear working space
        available_layers= (Coordinates.y_max - strating_pos.y)//offset
        full_spacing= spacing*len(Orientation.values())
        categories_num= len(categories_of_block_types)
        work_space_size = Coordinates(
            (1 + categories_num//available_layers)*(offset+ full_spacing),
            (categories_num if available_layers>categories_num else available_layers)*offset,
            spacing*max(len(cat) for cat in categories_of_block_types)
        )
        
        cube = Cube(
            min=(Coordinates.move_down(strating_pos) + Coordinates(-1,0,-1)).to_point(),
            max=(Coordinates.move_down(strating_pos) + work_space_size + Coordinates(1,0,1)).to_point()
        )
        self.client.fillCube(FillCubeRequest(
            cube=cube,
            type=AIR
        ))
        
        if (len(descriptions)!=categories_num):
            descriptions += ([None] * (categories_num-len(descriptions))) 
        
        # Place the categories
        for i,(block_types,description) in enumerate(zip(categories_of_block_types,descriptions)):
            time.sleep(waittime)
            full_offset = Coordinates((i//available_layers)*(offset+ full_spacing), (i%available_layers)*offset, 0)
            
            self.place_blocks_in_all_orientations(
                block_types, 
                strating_pos+full_offset,
                spacing,
                description,
                verbose
            )
    
    @staticmethod 
    def place_blocks_default(client: minecraft_pb2_grpc.MinecraftServiceStub):        
        bs =BlockShowcase(client)
        bs.place_blocks_in_all_orientations(
            BlockType.values(),
            BlockShowcase.default_point,
            BlockShowcase.default_spacing,
            verbose= 1
        )
    
    @staticmethod 
    def place_taged_blocks_default(client: minecraft_pb2_grpc.MinecraftServiceStub):        
        bs =BlockShowcase(client)
        length = BlockShowcase.orientations_len*BlockShowcase.default_spacing+BlockShowcase.default_offset
        lengths = np.array([len(cat) for cat in tags.BLOCK_TYPE_TAGS])
        idxs = np.argsort(lengths,)[::-1]
        bs.place_all_categories(
            [tags.BLOCK_TYPE_TAGS[i] for i in idxs],
            BlockShowcase.default_point + Coordinates(length, 0, 0),
            BlockShowcase.default_offset,
            BlockShowcase.default_spacing,
            [tags.BLOCK_TYPE_TAGS_NAME[i] for i in idxs],
            verbose= 1
        )

    @staticmethod 
    def place_item_taged_blocks_default(client: minecraft_pb2_grpc.MinecraftServiceStub):        
        bs =BlockShowcase(client)
        length = BlockShowcase.orientations_len*BlockShowcase.default_spacing+BlockShowcase.default_offset
        lengths = np.array([len(cat) for cat in item_tags.ITEM_TYPE_TAGS])
        idxs = np.argsort(lengths,)[::-1]
        bs.place_all_categories(
            [item_tags.ITEM_TYPE_TAGS[i] for i in idxs],
            BlockShowcase.default_point + Coordinates(4*length, 0, 0),
            BlockShowcase.default_offset,
            BlockShowcase.default_spacing,
            [item_tags.ITEM_TYPE_TAGS_NAME[i] for i in idxs],
            verbose= 1
        )
        
    @staticmethod 
    def place_cat_blocks_default(client: minecraft_pb2_grpc.MinecraftServiceStub):        
        bs =BlockShowcase(client)
        length = BlockShowcase.orientations_len*BlockShowcase.default_spacing+BlockShowcase.default_offset
        lengths = np.array([len(cat) for cat in cat.BLOCK_LISTS])
        names = cat.print_enum_names(cat.BLOCK_LISTS)
        idxs = np.argsort(lengths,)[::-1]
        bs.place_all_categories(
            [cat.BLOCK_LISTS[i] for i in idxs],
            BlockShowcase.default_point + Coordinates(6*length, 0, 0),
            BlockShowcase.default_offset,
            BlockShowcase.default_spacing,
            [names[i] for i in idxs],
            verbose= 1
        )

if __name__ == "__main__":
    channel = grpc.insecure_channel('localhost:5001')
    client = minecraft_pb2_grpc.MinecraftServiceStub(channel)
    
    BlockShowcase.place_blocks_default(client)

    print(len(tags.BLOCK_TYPE_TAGS))
    BlockShowcase.place_taged_blocks_default(client)

    print(len(item_tags.ITEM_TYPE_TAGS))
    BlockShowcase.place_item_taged_blocks_default(client)

    print(len(cat.BLOCK_LISTS))  
    BlockShowcase.place_cat_blocks_default(client)

        