from __future__ import annotations
import json
import logging
import os
import time
from typing import TYPE_CHECKING
import pygame
from nqp.core.constants import ASSET_PATH, DEFAULT_IMAGE_SIZE, FontEffects, FontType
from nqp.core.utility import clamp, clip
from nqp.ui_elements.generic.fancy_font import FancyFont
from nqp.ui_elements.generic.font import Font
if TYPE_CHECKING:
    from typing import Dict, List, Optional, Tuple
    from nqp.core.game import Game
__all__ = ["Assets"]
######### TO DO LIST  ##########
# TODO - Delete module when all classes updates to use new Visuals
[docs]class Assets:
    """
    DO NOT USE. Use visuals.py instead
    """
[docs]    def __init__(self, game: Game):
        # start timer
        start_time = time.time()
        self._game: Game = game
        self._fonts = {
            FontType.NEGATIVE: (str(ASSET_PATH / "fonts/small_font.png"), (255, 0, 0)),
            FontType.DISABLED: (str(ASSET_PATH / "fonts/small_font.png"), (128, 128, 128)),
            FontType.DEFAULT: (str(ASSET_PATH / "fonts/small_font.png"), (255, 255, 255)),
            FontType.POSITIVE: (str(ASSET_PATH / "fonts/small_font.png"), (0, 255, 0)),
            FontType.INSTRUCTION: (str(ASSET_PATH / "fonts/small_font.png"), (240, 205, 48)),
            FontType.NOTIFICATION: (str(ASSET_PATH / "fonts/large_font.png"), (117, 50, 168)),
        }
        # used to hold images so only one copy per dimension ever exists.
        self._images: Dict[str, Dict[str, pygame.Surface]] = self._load_images()
        self.unit_animations = {
            unit: {
                action: self.load_image_dir(ASSET_PATH / "units/" / unit / action)
                for action in os.listdir(ASSET_PATH / "units/" / unit)
            }
            for unit in os.listdir(ASSET_PATH / "units/")
        }
        # self.trap_animations = {}
        #
        # self.commander_animations = {
        #     commander: {
        #         action: self.load_image_dir(ASSET_PATH / "commanders/" / commander / action)
        #         for action in os.listdir(ASSET_PATH / "commanders/" / commander)
        #     }
        #     for commander in os.listdir(ASSET_PATH / "commanders/")
        # }
        #
        # self.boss_animations = {
        #     commander: {
        #         action: self.load_image_dir(ASSET_PATH / "bosses/" / commander / action)
        #         for action in os.listdir(ASSET_PATH / "bosses/" / commander)
        #     }
        #     for commander in os.listdir(ASSET_PATH / "bosses/")
        # }
        self.tilesets = {
            tileset.split(".")[0]: self.load_tileset(ASSET_PATH / "tiles" / tileset)
            for tileset in os.listdir(ASSET_PATH / "tiles")
        }
        # self.ui = {
        #     ui_image.split(".")[0]: pygame.image.load(str(ASSET_PATH / "ui" / ui_image)).convert_alpha()
        #     for ui_image in os.listdir(ASSET_PATH / "ui")
        # }
        #
        # self.actions = {
        #     action_image.split(".")[0]: pygame.image.load(str(ASSET_PATH / "actions" / action_image)).convert_alpha()
        #     for action_image in os.listdir(ASSET_PATH / "actions")
        # }
        #
        # self.projectiles = {
        #     projectiles_image.split(".")[0]: pygame.image.load(
        #         str(ASSET_PATH / "projectiles" / projectiles_image)
        #     ).convert_alpha()
        #     for projectiles_image in os.listdir(ASSET_PATH / "projectiles")
        #     if projectiles_image.split(".")[-1] == "png"
        # }
        self.maps = dict()
        for file in os.listdir("data/maps"):
            if file.endswith("json"):
                name = file.split(".")[0]
                path = "data/maps/" + file
                with open(path) as fp:
                    data = json.load(fp)
                self.maps[name] = data
        # record duration
        end_time = time.time()
        logging.debug(f"Assets: initialised in {format(end_time - start_time, '.2f')}s.") 
[docs]    def get_image(
        self,
        folder_name: str,
        image_name: str,
        desired_dimensions: pygame.Vector2 = (DEFAULT_IMAGE_SIZE, DEFAULT_IMAGE_SIZE),
        copy: bool = False,
    ) -> pygame.Surface:
        """
        Get the specified image and resize if dimensions provided. Dimensions are in (width, height) format.
        A transparent surface can be returned by folder_name = "debug" and image_name = "blank".
        """
        desired_width, desired_height = desired_dimensions
        # ensure numbers arent negative
        if desired_width <= 0 or desired_height <= 0:
            logging.warning(
                f"Get_image: Tried to use dimensions of {desired_dimensions}, which are negative. Default size "
                f"used instead."
            )
            desired_dimensions = (DEFAULT_IMAGE_SIZE, DEFAULT_IMAGE_SIZE)
        internal_name = f"{image_name}@{desired_width}x{desired_height}"
        # check if exists already
        try:
            image = self._images[folder_name][internal_name]
        except KeyError:
            # try and get the image specified
            try:
                image = pygame.image.load(str(ASSET_PATH / folder_name / image_name) + ".png").convert_alpha()
                # resize if needed - should only need to resize if we havent got it from storage
                if image.get_width() != desired_width or image.get_height() != desired_height:
                    image = pygame.transform.smoothscale(image, desired_dimensions)
                # add new image to storage
                self._images[folder_name][internal_name] = image
            except FileNotFoundError:
                # didnt find image requested so use not found image
                not_found_name = f"not_found@{desired_width}x{desired_height}"
                if not_found_name in self._images["debug"]:
                    image = self._images["debug"][not_found_name]
                else:
                    image = pygame.image.load(str(ASSET_PATH / "debug/image_not_found.png")).convert_alpha()
                    # add new image to storage
                    self._images["debug"][internal_name] = image
                logging.warning(
                    f"Get_image: Tried to use {folder_name}/{image_name} but it wasn't found. "
                    f"Used the not_found image instead."
                )
        # return a copy if requested
        if copy:
            return image.copy()
        else:
            return image 
[docs]    def create_font(self, font_type: FontType, text: str, pos: pygame.Vector2 = (0, 0), line_width: int = 0) -> Font:
        """
        Create a font instance.
        """
        line_width = clamp(line_width, 0, self._game.window.width)
        path, colour = self._fonts[font_type]
        font = Font(path, colour, text, line_width, pos)
        return font 
[docs]    def create_fancy_font(
        self,
        text: str,
        pos: pygame.Vector2 = pygame.Vector2(0, 0),
        line_width: int = 0,
        font_effects: Optional[List[FontEffects]] = None,
    ) -> FancyFont:
        """
        Create a FancyFont instance. If line_width isnt given then will default to full screen.
        """
        line_width = clamp(line_width, 0, self._game.window.width)
        # handle mutable default
        if font_effects is None:
            font_effects = []
        font = FancyFont(text, pos, line_width, font_effects)
        return font 
[docs]    def load_tileset(self, path):
        """
        Loads a tileset from a spritesheet.
        """
        tileset_data = []
        spritesheet = pygame.image.load(str(path)).convert_alpha()
        for y in range(spritesheet.get_height() // DEFAULT_IMAGE_SIZE):
            tileset_data.append([])
            for x in range(spritesheet.get_width() // DEFAULT_IMAGE_SIZE):
                tileset_data[-1].append(
                    clip(
                        spritesheet,
                        x * DEFAULT_IMAGE_SIZE,
                        y * DEFAULT_IMAGE_SIZE,
                        DEFAULT_IMAGE_SIZE,
                        DEFAULT_IMAGE_SIZE,
                    )
                )
        return tileset_data 
[docs]    def load_image_dir(self, path, format="list"):
        """
        Load images in a directory with specified format.
        """
        images = None
        if format == "list":
            images = []
        if format == "dict":
            images = {}
        for img_path in os.listdir(path):
            img = pygame.image.load(str(path) + "/" + img_path).convert_alpha()
            if format == "list":
                images.append(img)
            if format == "dict":
                images[img_path.split(".")[0]] = img
        return images 
    @staticmethod
    def _load_images() -> Dict[str, Dict[str, pygame.Surface]]:
        """
        Load all images by folder.
        N.B. if image isnt loading ensure the containing folder is listed in the method.
        """
        images = {}
        # specify folders in assets that need to be loaded
        folders = ["rooms"]
        for folder in folders:
            path = ASSET_PATH / folder
            images[folder] = {}
            for image_name in os.listdir(path):
                if image_name.split(".")[-1] == "png":
                    # avoid duplicates
                    if image_name in images[folder].keys():
                        logging.warning(f"{image_name} already loaded, non-unique file name.")
                    image = pygame.image.load(str(path / image_name)).convert_alpha()
                    width = image.get_width()
                    height = image.get_height()
                    images[folder][f"{image_name.split('.')[0]}@{width}x{height}"] = image  # split to remove extension
        # add not found image to debug
        images["debug"] = {}
        image = pygame.image.load(str(ASSET_PATH / "debug/image_not_found.png")).convert_alpha()
        width = image.get_width()
        height = image.get_height()
        images["debug"][f"not_found@{width}x{height}"] = image
        # add transparent surface to debug
        image = pygame.Surface((DEFAULT_IMAGE_SIZE, DEFAULT_IMAGE_SIZE))
        image.set_alpha(0)
        images["debug"][f"blank@{DEFAULT_IMAGE_SIZE}x{DEFAULT_IMAGE_SIZE}"] = image
        logging.debug(f"Assets: All images loaded.")
        return images