from common import os, json, uuid, deepcopy, dataclass, field
from common import pygame, pygame_gui
import eb_objects
import eb_terrain_objects
import eb_creature_objects

#from pympler import muppy, summary
import gc, psutil, os

cell_classes = {"grass_small": eb_terrain_objects.Ground, 
                "sword_default": eb_objects.Item, "elf_watching": eb_creature_objects.Unit}

main_dir = os.path.dirname(os.path.abspath(__file__))
sprites_dir = os.path.join(main_dir, "res", "sprites") 

#class MapManager
#class Event
#class EventManager

@dataclass
class Cell:
    terrain_obj:  None = None
    item_obj:     None = None
    creature_obj: None = None
    is_target:    bool = False

@dataclass
class Map:
    name:             str
    sprites:          dict
    sprites_refresh:  int    =   60
    cells:            dict   =   field(default_factory = dict)
    color:            str    =   "gray57"
    target_color:     str    =   "gold"
    size:             int    =   150
    bord:             int    =   3
    scale:            float  =   1
    cam_x:            int    =   0
    cam_y:            int    =   0
    cell_dist:        int    =   1

    def __post_init__(self):
        self.cells = {}
        with open(self.name, 'r') as file:
            buff = json.load(file)
        for line in range(len(buff)):
            self.cells[line] = []
            for cell in buff[str(line)]:
                final_cell = Cell(cell_classes[cell["terrain_obj"]["sprite_name"]](**cell["terrain_obj"]))
                
                if cell["item_obj"]:
                    final_cell.item_obj = cell_classes[cell["item_obj"]["sprite_name"]](**cell["item_obj"])
                
                if cell["creature_obj"]:
                    final_cell.creature_obj = cell_classes[cell["creature_obj"]["sprite_name"]](**cell["creature_obj"])

                self.cells[line].append(final_cell)   

    def draw_map(self, screen, current_frame, grid = True):
        for j in range(len(self.cells)):
            for i, cell in enumerate(self.cells[j]):
                dd = {"x": int((i * self.size + self.cam_x) * self.scale),
                             "y": int((j * self.size + self.cam_y) * self.scale),
                             "w": int(self.size * self.scale - self.cell_dist),
                             "h": int(self.size * self.scale - self.cell_dist),
                             "spr_up": current_frame % self.sprites_refresh,
                             "sprites": self.sprites, "scale": self.scale,
                             "screen": screen}

                cell.terrain_obj.draw(dd)

                if cell.item_obj:
                    cell.item_obj.draw(dd)
                
                if cell.creature_obj:
                    cell.creature_obj.draw(dd)

                if grid:
                    pygame.draw.rect(screen, self.color, pygame.Rect(dd["x"], dd["y"], dd["w"], dd["h"]), self.bord)
                
@dataclass
class Engine:
    sprites:         dict             =   field(default_factory = dict)
    cached_sprites:  dict             =   field(default_factory = dict)
    screen:          pygame.Surface   =   ((1, 1))
    width:           int              =   1600
    height:          int              =   800
    camera_step:     int              =   10
    scale_step:      float            =   0.01
    spr_scale:       float            =   1
    
    def __post_init__(self):
        self.sprites = {}
        pygame.init()
        pygame.display.set_caption('Elvenbane')
        self.screen = pygame.display.set_mode((self.width, self.height), pygame.HWSURFACE | pygame.DOUBLEBUF)
        print("The engine has started. Sprites were successfully loaded.\n")
        self.load_sprites()
        print("Sprites were successfully loaded.\n")
        self.cached_sprites = deepcopy(self.sprites)
        print("Sprites were successfully cached.\n")

    def load_sprites(self, folder_path = sprites_dir):
        self.sprites = {}
        files = [f for f in os.listdir(folder_path) if f.lower().endswith('.png')]
        #TOTAL SLOR - REWRITE THIS FUNC PLS
        groups = {}
        for f in files:
            name = os.path.splitext(f)[0]
            if '_' in name and name.rsplit('_', 1)[0].count('_') >= 1:
                prefix = name.rsplit('_', 1)[0]
                num = int(name.rsplit('_', 1)[1])
                groups.setdefault(prefix, []).append((num, f))

        for prefix, items in groups.items():
            items.sort()
            self.sprites[prefix] = [
                pygame.image.load(os.path.join(folder_path, f)).convert_alpha()
                for num, f in items
            ]

    def scale_image(self, image):
        orig_size = image.get_size()
        new_size = (int(orig_size[0] * self.spr_scale), int(orig_size[1] * self.spr_scale))
        return pygame.transform.smoothscale(image, new_size)

    def scale_sprites(self):
        for sp_name, sprite_list in self.sprites.items():
            for i, sprite in enumerate(sprite_list):
                scaled = self.scale_image(sprite)
                self.cached_sprites[sp_name][i] = scaled

    def create_console(self, manager):
        console_window = pygame_gui.elements.UIWindow(
            pygame.Rect(100, 100, 400, 300),
            manager=manager,
            window_display_title='Console',
            resizable=True
        )

        input_entry = pygame_gui.elements.UITextEntryLine(
            relative_rect=pygame.Rect(10, 250, 380, 30),
            container=console_window,
            manager=manager
        )

        # ★ UIScrollingContainer ★
        scroll_container = pygame_gui.elements.UIScrollingContainer(
            relative_rect=pygame.Rect(10, 10, 380, 230),
            container=console_window,
            manager=manager,
            allow_scroll_x=False
        )

        # ★ UITextBox ВНУТРИ контейнера ★
        output_box = pygame_gui.elements.UITextBox(
            html_text=">>> Готово!\n",
            relative_rect=pygame.Rect(0, 0, 380, 1000),  # ← Увеличьте высоту!
            container=scroll_container,
            manager=manager
        )

        scroll_container.set_scrollable_area_dimensions((380, 2000))
        return console_window, input_entry, scroll_container, output_box

    def main_loop(self):                
        easy_map = Map("def_map.json", self.cached_sprites)
        #sp = eb_objects.Sprite(self.sprites, "elf_watching")
        #gr = pygame.image.load(file_path).convert_alpha()

        background = pygame.Surface((1600, 800))
        background.fill("chartreuse4")

        manager = pygame_gui.UIManager((1600, 800))

        #hello_button = pygame_gui.elements.UIButton(relative_rect=pygame.Rect((350, 275), (100, 50)),
        #                                     text='Say Hello',
        #                                     manager=manager)

        console_window, input_entry, scroll_container, output_box = self.create_console(manager)
        output_log = ">>> hlwrld1\n"
        console_active = False

        clock = pygame.time.Clock()        
        running = True
        unlock = True
        current_frame = 0
        max_fps = 60
        global_counter = 0
        global_counter_cap = 100000

        # profiling

        process = psutil.Process(os.getpid())
        gc.collect()
        mem_before = process.memory_info().rss / 1024  

        while running:
            time_delta = clock.tick(60)/1000.0
            #pygame.event.clear()
            if global_counter % 1000 == 0:
                gc.collect()
                mem_after = process.memory_info().rss / 1024 
                print(f"Leak: {mem_after - mem_before:.1f}  KB per 1000 frames")

            # poll for events
            # pygame.QUIT event means the user clicked X to close your window
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False

                if event.type == pygame_gui.UI_TEXT_ENTRY_FINISHED and event.ui_element == input_entry:
                    user_text = input_entry.get_text()
                    if user_text.strip():
                        output_log += f">>> {user_text}\n"
                        output_box.set_text(output_log)
                    input_entry.set_text("")
                    console_active = False
                
                console_active = bool(input_entry.get_text().strip())

                if event.type == pygame.VIDEORESIZE:
                    new_size = event.size
                    self.screen = pygame.display.set_mode(new_size, pygame.RESIZABLE)
                    manager.clear_and_set_new_size((new_size[0], new_size[1]))
                    
                    # ★ ПЕРЕСОЗДАНИЕ ★
                    console_window.kill()
                    input_entry.kill()
                    scroll_container.kill()
                    output_box.kill()

                    console_window, input_entry, scroll_container, output_box = self.create_console(manager)


                manager.process_events(event)

            manager.update(time_delta)
            # fill the screen with a color to wipe away anything from last frame
            #self.screen.fill("chartreuse4")
            self.screen.blit(background, (0, 0))

            easy_map.draw_map(self.screen, current_frame + 1)
            manager.draw_ui(self.screen)
            #print(easy_map.cells[0][0].item_obj.sprite_cache)

            if not console_active:
                keys = pygame.key.get_pressed()   
                if keys[pygame.K_w]:
                    easy_map.cam_y += self.camera_step
                if keys[pygame.K_s]:
                    easy_map.cam_y -= self.camera_step
                if keys[pygame.K_a]:
                    easy_map.cam_x += self.camera_step
                if keys[pygame.K_d]:
                    easy_map.cam_x -= self.camera_step
                if keys[pygame.K_q]:
                    easy_map.scale += self.scale_step
                    self.spr_scale += self.scale_step
                    self.scale_sprites()
                if keys[pygame.K_e] and easy_map.scale >= self.scale_step:
                    easy_map.scale -= self.scale_step
                    self.spr_scale -= self.scale_step
                    self.scale_sprites()
                if keys[pygame.K_ESCAPE]:
                    running = False

            current_frame = (current_frame + 1) % max_fps
            if global_counter < global_counter_cap:
                global_counter += 1
            else:
                global_counter = 0

            # flip() the display to put your work on screen
            pygame.display.update()

            if global_counter % 100 == 0:
                current_fps = clock.get_fps()
                #print(f"Current FPS: {current_fps:.2f}")

        pygame.quit()