import random
import math
import os
from math import sqrt
import ctypes
import sdl2
import sdl2.ext
import sdl2.sdlmixer as sdlmixer
from .tilemanager import TileManager
from .spritemanager import SpriteManager
from .sdl2_utils.isogeometry import IsometricGeometry
from .sdl2_utils.gui import SDL2Gui

class SDL2Renderer(IsometricGeometry, SDL2Gui):
    def __init__(self, game, engine):
        self.engine = engine
        self.game = game
        sdl2.ext.init()
        # Initialize SDL2 mixer
        sdlmixer.Mix_Init(sdlmixer.MIX_INIT_OGG | sdlmixer.MIX_INIT_MP3)
        sdlmixer.Mix_OpenAudio(44100, sdlmixer.MIX_DEFAULT_FORMAT, 2, 1024)
        sdlmixer.Mix_AllocateChannels(16)  # Allocate channels for multiple sounds

        self.height = len(self.engine.map)
        self.width = len(self.engine.map[0])
        self.view_size = (800, 600)
        self.target_size = (800, 600)
        self.base_cell_size = 132  # Original/base cell size
        self.scaling_factor = 0.5  # Default scaling factor (can be changed)
        self.cell_size = int(self.base_cell_size * self.scaling_factor)  # Effective cell size
        self.ground_level_offset = self.base_cell_size // 4
        self.view_offset_x = 400
        self.view_offset_y = 0
        self.surface_width = 0
        self.surface_height = 0
        self.window = sdl2.ext.Window("Enne2EngineDEMO", size=self.view_size)
        self.window.show()  # Mostra la finestra
        self.renderer = sdl2.ext.Renderer(self.window, flags=sdl2.SDL_RENDERER_ACCELERATED)
        self.tile_managers = {}
        self.sprite_managers = {}
        self.factory = sdl2.ext.SpriteFactory(renderer=self.renderer)
        self.fonts = self.generate_fonts("assets/decterm.ttf")
        self.cursor = (0, 0)
        # Add selection rectangle tracking variables
        self.selection_start = None
        self.selection_current = None
        self.selecting = False
        self.tiles_texture = sdl2.SDL_CreateTexture(self.renderer.renderer,
                                 sdl2.SDL_PIXELFORMAT_RGBA8888,
                                 sdl2.SDL_TEXTUREACCESS_TARGET,
                                 self.view_size[0],
                                 self.view_size[1])
        self.sprite_texture = sdl2.SDL_CreateTexture(self.renderer.renderer,
                     sdl2.SDL_PIXELFORMAT_RGBA8888,
                     sdl2.SDL_TEXTUREACCESS_TARGET,
                     self.view_size[0],
                     self.view_size[1])
        sdl2.SDL_SetTextureBlendMode(self.sprite_texture, sdl2.SDL_BLENDMODE_BLEND)
    def get_perf_counter(self):
        return int(sdl2.SDL_GetPerformanceCounter())
    
    def handle_events(self):
        for event in sdl2.ext.get_events():
            if event.type == sdl2.SDL_QUIT:
                return "QUIT"
            elif event.type == sdl2.SDL_KEYUP:
                key = sdl2.SDL_GetKeyName(event.key.keysym.sym).decode('utf-8')
                return f"KEYUP:{key}".lower()
            elif event.type == sdl2.SDL_KEYDOWN:
                key = sdl2.SDL_GetKeyName(event.key.keysym.sym).decode('utf-8')
                return f"KEYDOWN:{key}".lower()
            elif event.type == sdl2.SDL_MOUSEMOTION:
                x, y = event.motion.x, event.motion.y
                # Update current selection point if dragging
                if self.selecting:
                    self.selection_current = (x, y)
                return f"MOUSEMOTION:{x}:{y}"
            elif event.type == sdl2.SDL_MOUSEBUTTONDOWN:
                x, y = event.button.x, event.button.y
                # Left mouse button (button 1)
                self.selection_start = (x, y)
                self.selection_current = (x, y)
                if event.button.button == 1:
                    self.selecting = True
                return f"MOUSEDOWN:{x}:{y}:{event.button.button}"
            elif event.type == sdl2.SDL_MOUSEBUTTONUP:
                x, y = event.button.x, event.button.y
                # Left mouse button (button 1)
                if event.button.button == 1:
                    result = f"MOUSEUP:{x}:{y}:{event.button.button}"
                    # Return the selection area if it's large enough and we were selecting
                    if self.selecting and self.selection_start and self.get_selection_size() > 5:
                        result = f"SELECTION:{self.selection_start[0]}:{self.selection_start[1]}:{x}:{y}"
                    
                    # Always reset selection state on mouse up, regardless of selecting flag
                    self.selecting = False
                    self.selection_start = None
                    self.selection_current = None
                    return result
                return f"MOUSEUP:{x}:{y}"
        return False

    def clear_screen(self, color=(0, 0, 0, 255)):  # Aggiunto valore alfa di default
        sdl2.SDL_SetRenderDrawColor(self.renderer.sdlrenderer, *color)
        # Clear sprite texture with transparent color
        sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.sprite_texture)
        sdl2.SDL_SetRenderDrawColor(self.renderer.sdlrenderer, 0, 0, 0, 0)  # Transparent color (alpha=0)
        sdl2.SDL_RenderClear(self.renderer.sdlrenderer)
        sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None)
        # Return to main renderer with the specified color
        sdl2.SDL_SetRenderDrawColor(self.renderer.sdlrenderer, *color)
        sdl2.SDL_RenderClear(self.renderer.sdlrenderer)

    def present_renderer(self):
        sdl2.SDL_RenderPresent(self.renderer.sdlrenderer)

    def quit(self):
        # Clean up mixer resources
        sdlmixer.Mix_CloseAudio()
        sdlmixer.Mix_Quit()
        sdl2.SDL_Quit()
        
    def load_spritesheet(self, name, path):
        surface = sdl2.ext.load_image(path.replace('.json', '.png'))
        texture = sdl2.ext.Texture(self.renderer, surface)
        self.sprite_managers[name] = SpriteManager(path, surface, texture, self.base_cell_size)
        
    def load_tilesheet(self, name, path):
        surface = sdl2.ext.load_image(path)
        texture = sdl2.ext.Texture(self.renderer, surface)
        
        self.tile_managers[name] = TileManager(path.replace('.png', '.xml'), surface, texture, self.cell_size)
        
    def load_sprite(self, spritesheet, name):
        return self.tile_managers[spritesheet].get_tile_rect(name)
            
    def render_sprite(self, name, x, y, frame, occlusion=0):
        srcrect, total_frames = self.sprite_managers[name].get_frame_rect(frame)
        x -= self.cell_size // 2
        y -= self.cell_size // 4
        if occlusion:
            original_color_mods_r_g_b_a = self.apply_texture_color_mdod(self.sprite_managers[name].spritesheet_texture, occlusion)
        sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.sprite_texture)
        self.renderer.copy(self.sprite_managers[name].spritesheet_texture, 
                           dstrect=(x, y, self.cell_size, self.cell_size),
                            srcrect=srcrect)
        if occlusion:
            sdl2.SDL_SetTextureColorMod(self.sprite_managers[name].spritesheet_texture.tx,
                                *original_color_mods_r_g_b_a)
        sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None)
        return (frame + 1) % total_frames            

    def create_background(self, map, spritesheet_name, map_shadow):
            # Set page as render target and initialize it
        sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.tiles_texture)
        sdl2.SDL_SetRenderDrawColor(self.renderer.sdlrenderer, 0, 0, 0, 255)  # Green background
        sdl2.SDL_RenderClear(self.renderer.sdlrenderer)
        self.map_shadow = map_shadow
        
        tilesheet_texture = self.tile_managers[spritesheet_name].get_tilesheet_texture()
        
        def get_shadow(x, y):
            shadow = min(self.map_shadow[y][x], 1)
            if shadow == 1 and self.engine.map[y][x].get('visited'):
                shadow = 0.8
            return shadow
            
        def blit_tile(tile, x, y):
            tile_rect = self.tile_managers[spritesheet_name].get_tile_rect(tile)
            if tile_rect is not None:
                shadow = get_shadow(x, y)
                if shadow==1:
                    return
                self.apply_texture_color_mdod(tilesheet_texture, shadow)
                
                # Adjusted vertical offset calculation for half-sized cells
                vertical_offset = (self.cell_size - tile_rect[3] * self.scaling_factor - self.ground_level_offset * self.scaling_factor)
                # Adjusted horizontal offset calculation for half-sized cells  
                horizontal_offset = (self.cell_size - tile_rect[2] * self.scaling_factor ) 
                
                iso_x, iso_y = self.iso_transform(x, y)
                iso_y += vertical_offset 
                iso_x += (horizontal_offset - tile_rect[2] * self.scaling_factor // 2)
                
                # Scale the actual tile rectangle based on the current scaling factor
                dst_rect = (int(iso_x), int(iso_y),
                            int(tile_rect[2] * self.scaling_factor), 
                            int(tile_rect[3] * self.scaling_factor))
                            
                sdl2.SDL_RenderCopy(self.renderer.sdlrenderer, tilesheet_texture.tx,
                                    sdl2.SDL_Rect(*tile_rect),
                                    sdl2.SDL_Rect(*dst_rect))
    
        for y, row in enumerate(map):
            for x, cell in enumerate(row):
                tile_name = cell.get('tile')
                blit_tile(tile_name, x, y)
        # Reset render target to the default (window)
        sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None)
        
        
    def render_background(self):
        sdl2.SDL_RenderCopy(self.renderer.sdlrenderer, self.tiles_texture, None, None)
        
    def render_sprites(self):
        sdl2.SDL_RenderCopy(self.renderer.sdlrenderer, self.sprite_texture, None, None)
        
    def get_frame_time(self, perf_counter):
        return (sdl2.SDL_GetPerformanceCounter() - perf_counter) / sdl2.SDL_GetPerformanceFrequency() *1000       
     
    def delay_frame(self, frame_time, target):
            self.performance = (frame_time) 
            if self.performance < target:
                delay = target - round(self.performance)
            else:
                delay = 0
            sdl2.SDL_Delay(delay)

    def draw_cursor(self):
        x, y = self.cursor
        c_x, c_y = self.inv_iso_transform(x, y)
        iso_x, iso_y = self.iso_transform(c_x, c_y)
        sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.sprite_texture)
        
        self.renderer.draw_line(points=[(iso_x, iso_y), (iso_x + self.cell_size//2, iso_y + self.cell_size//4)], color=(255
                                                                                                                        , 0, 0, 255))
        self.renderer.draw_line(points=[(iso_x + self.cell_size//2, iso_y + self.cell_size//4), (iso_x, iso_y + self.cell_size//2)], color=(255, 0, 0, 255))
        self.renderer.draw_line(points=[(iso_x, iso_y + self.cell_size//2), (iso_x - self.cell_size//2, iso_y + self.cell_size//4)], color=(255, 0, 0, 255))
        self.renderer.draw_line(points=[(iso_x - self.cell_size//2, iso_y + self.cell_size//4), (iso_x, iso_y)], color=(255, 0, 0, 255))
        sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None)
        return c_x, c_y
    
    def draw_square(self, iso_x, iso_y, color=(0, 255, 0, 255), margin=6):
        sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.sprite_texture)
        
        # Apply margin to reduce the size of the square
        adjusted_size = self.cell_size - margin*2
        
        # Calculate half-sizes with margin applied
        half_width = adjusted_size // 2
        half_height = adjusted_size // 4
        
        # Draw an isometric square with margin
        self.renderer.draw_line(points=[(iso_x, iso_y + margin), 
                                        (iso_x + half_width-margin, iso_y + half_height +margin//2)], color=color)
        self.renderer.draw_line(points=[(iso_x + half_width-margin, iso_y + half_height +margin//2),
                                        (iso_x, iso_y + half_height*2+margin//4)], color=color)
        self.renderer.draw_line(points=[(iso_x, iso_y + half_height*2+margin//4),
                                        (iso_x - half_width+margin, iso_y + half_height +margin//2)], color=color)
        self.renderer.draw_line(points=[(iso_x - half_width+margin, iso_y + half_height +margin//2),
                                        (iso_x, iso_y + margin)], color=color)

        sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None)
    
    def apply_texture_color_mdod(self, texture, occlusion):
        color_mod_r = int(255 * (1 - occlusion))
        color_mod_g = int(255 * (1 - occlusion))
        color_mod_b = int(255 * (1 - occlusion))
        original_color_mods_r_g_b_a = [0] * 3
        r_ptr = ctypes.pointer(ctypes.c_ubyte())
        g_ptr = ctypes.pointer(ctypes.c_ubyte())
        b_ptr = ctypes.pointer(ctypes.c_ubyte())
        sdl_texture = texture.texture if hasattr(texture, 'texture') else texture.tx
        sdl2.SDL_GetTextureColorMod(sdl_texture,
                        r_ptr,
                        g_ptr,
                        b_ptr)
        original_color_mods_r_g_b_a[0] = r_ptr.contents.value
        original_color_mods_r_g_b_a[1] = g_ptr.contents.value
        original_color_mods_r_g_b_a[2] = b_ptr.contents.value
        sdl2.SDL_SetTextureColorMod(sdl_texture,
                                color_mod_r,
                                color_mod_g,
                                color_mod_b)
        return original_color_mods_r_g_b_a
    
    def render_tile(self, spritesheet_name, x, y, tile):
        tile_rect = self.tile_managers[spritesheet_name].get_tile_rect(tile)
        if tile_rect is not None:
            vertical_offset = self.cell_size - tile_rect[3]
            horizontal_offset = (self.cell_size - tile_rect[2])
            iso_x, iso_y = self.iso_transform(x, y)
            iso_y += vertical_offset - tile_rect[3]//4
            iso_x += horizontal_offset - tile_rect[2] // 2
            dst_rect = (iso_x, iso_y,
                        tile_rect[2], tile_rect[3])
            sdl2.SDL_RenderCopy(self.renderer.sdlrenderer, self.tile_managers[spritesheet_name].get_tilesheet_texture().tx,
                                sdl2.SDL_Rect(*tile_rect),
                                sdl2.SDL_Rect(*dst_rect))

    def set_scaling_factor(self, factor):
        """
        Set a new scaling factor and recalculate cell_size.
        
        Args:
            factor (float): The scaling factor (0.5 = half size, 1.0 = original size, etc.)
        """
        if factor > 0:
            print(f"Scaling factor set to {factor}")
            self.scaling_factor = factor
            self.cell_size = int(self.base_cell_size * self.scaling_factor)
    
    def play_sound(self, sound_file):
        """
        Play a sound file using SDL2 mixer.
        
        Args:
            sound_file (str): Name of the sound file to play
        """
        sound_path = os.path.join("assets", "Sounds", sound_file)
        if os.path.exists(sound_path):
            sound = sdlmixer.Mix_LoadWAV(sound_path.encode('utf-8'))
            if sound:
                channel = sdlmixer.Mix_PlayChannel(-1, sound, 0)
                if channel == -1:
                    print(f"Error playing sound: {sdlmixer.Mix_GetError().decode('utf-8')}")
                # Free the sound when finished (after a delay or in a callback)
                # For simplicity, we're not handling this here, but in a real app you might want to
        else:
            print(f"Sound file not found: {sound_path}")

    def draw_selection_rectangle(self):
        """Draw the selection rectangle if currently selecting."""
        # Only draw if we're in a valid selection state
        if not self.selecting or not self.selection_start or not self.selection_current:
            return
            
        # Get coordinates for the rectangle
        start_x, start_y = self.selection_start
        current_x, current_y = self.selection_current
        
        # Calculate the rectangle dimensions
        x = min(start_x, current_x)
        y = min(start_y, current_y)
        width = abs(current_x - start_x)
        height = abs(current_y - start_y)
        
        # Only draw if we have a meaningful selection area
        if width > 1 and height > 1:
            # Draw an empty rectangle in green
            self.draw_rectangle(x, y, width, height, "selection", outline=(0, 255, 0), filling=None)
    
    def get_selection_size(self):
        """Get the size (diagonal length) of the selection rectangle."""
        if not self.selection_start or not self.selection_current:
            return 0
            
        start_x, start_y = self.selection_start
        current_x, current_y = self.selection_current
        
        # Calculate diagonal length using Pythagorean theorem
        dx = current_x - start_x
        dy = current_y - start_y
        return int(math.sqrt(dx*dx + dy*dy))
    
    def is_mouse_button_pressed(self, button=1):
        """
        Check if a specific mouse button is currently pressed.
        
        Args:
            button (int): Button number (1=left, 2=middle, 3=right)
            
        Returns:
            bool: True if the button is pressed, False otherwise
        """
        # Create ctypes variables to store the mouse position
        x = ctypes.c_int(0)
        y = ctypes.c_int(0)
        
        # Get the current mouse state (returns a bitmask of buttons)
        button_state = sdl2.SDL_GetMouseState(ctypes.byref(x), ctypes.byref(y))
        
        # Map button numbers to SDL constants
        button_masks = {
            1: sdl2.SDL_BUTTON_LMASK,  # Left button
            2: sdl2.SDL_BUTTON_MMASK,  # Middle button
            3: sdl2.SDL_BUTTON_RMASK,  # Right button
        }
        
        # Check if the requested button is pressed
        if button in button_masks:
            return bool(button_state & button_masks[button])
        return False
    
    def set_opacity(self, x, y, opacity):
        self.engine.map_shadow[y][x] = max(self.engine.map_shadow[y][x], opacity)
        
    def draw_line(self, points, color=(255, 0, 0, 255)):
        sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, self.sprite_texture)
        self.renderer.draw_line(points=points, color=color)
        sdl2.SDL_SetRenderTarget(self.renderer.sdlrenderer, None)