# Copyright (C) 2024 Araten & Marigold
#
# This file is part of Edgeware++.
#
# Edgeware++ is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Edgeware++ is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Edgeware++.  If not, see <https://www.gnu.org/licenses/>.

import logging
import random
from pathlib import Path

import filetype
from paths import PATH, CustomAssets, PackPaths

from pack.data import MoodBase, MoodSet
from pack.load import list_media, load_allowed_moods, load_config, load_corruption, load_discord, load_index, load_info


class Pack:
    def __init__(self, root: Path) -> None:
        logging.info(f"Loading pack at {root.relative_to(PATH)}.")

        self.paths = PackPaths(root)

        # Weights for randomization
        self.image_ranks = {}
        self.video_ranks = {}
        self.audio_ranks = {}

        # Pack files
        self.corruption_levels = load_corruption(self.paths)
        self.discord = load_discord(self.paths)
        self.index = load_index(self.paths)
        self.info = load_info(self.paths)
        self.config = load_config(self.paths)

        # Data files
        self.allowed_moods = load_allowed_moods(self.info.mood_file) or MoodSet(map(lambda mood: mood.name, self.index.moods))
        self.active_moods = self.allowed_moods.copy()  # Should not be accessed directly except for modification
        self.get_active_moods = lambda: self.active_moods
        self.block_corruption_moods()

        # Media
        self.images = list_media(self.paths.image, filetype.is_image)
        self.videos = list_media(self.paths.video, filetype.is_video)
        self.audio = list_media(self.paths.audio, filetype.is_audio)
        self.hypnos = list_media(self.paths.hypno, filetype.is_image) or list_media(self.paths.hypno_legacy, filetype.is_image) or [CustomAssets.hypno()]

        # Paths
        self.icon = self.paths.icon if self.paths.icon.is_file() else CustomAssets.icon()
        self.wallpaper = self.paths.wallpaper if self.paths.wallpaper.is_file() else None
        self.startup_splash = next((path for path in self.paths.splash if path.is_file()), None) or CustomAssets.startup_splash()

        logging.info(f"Allowed moods: {self.allowed_moods}")

    def block_corruption_moods(self) -> None:
        # Remove moods that aren't enabled by the user from each corruption level
        for level in self.corruption_levels:
            level.added_moods.intersection_update(self.allowed_moods)
            level.removed_moods.intersection_update(self.allowed_moods)

    def filter_media(self, media_list: list[Path]) -> list[Path]:
        active_moods = self.get_active_moods()
        return list(filter(lambda media: self.index.media_moods.get(media.name) in active_moods, media_list))

    def random_media(self, media_list: list[Path], media_ranks: dict[Path, int]) -> Path | None:
        filtered = self.filter_media(media_list)
        if not filtered:
            return None

        # Give lower preference to media that has been recently selected
        max_rank = len(media_list)
        ranks = [media_ranks.get(media, max_rank) for media in filtered]
        weights = [2 ** (16 * rank / max_rank) for rank in ranks]
        media = random.choices(filtered, weights, k=1)[0]

        for key, value in media_ranks.items():
            media_ranks[key] = min(value + 1, max_rank)
        media_ranks[media] = 1

        return media

    def random_image(self, unweighted: bool = False) -> Path | None:
        if unweighted:
            images = self.filter_media(self.images)
            return random.choice(images) if images else None
        return self.random_media(self.images, self.image_ranks)

    def random_video(self) -> Path | None:
        return self.random_media(self.videos, self.video_ranks)

    def random_audio(self) -> Path | None:
        return self.random_media(self.audio, self.audio_ranks)

    def random_hypno(self) -> Path:
        return random.choice(self.hypnos)  # Guaranteed to be non-empty

    def find_list(self, attr: str) -> list:
        active_moods = self.get_active_moods()
        moods = list(filter(lambda mood: mood.name in active_moods, self.index.moods))
        lists = [getattr(self.index.default, attr)] + list(map(lambda mood: getattr(mood, attr), moods))
        return [item for list in lists for item in list]

    def find_media_mood(self, media: Path) -> MoodBase:
        return next((mood for mood in self.index.moods if mood.name == self.index.media_moods.get(media.name)), None) or self.index.default

    def find_captions(self, media: Path | None = None) -> list[str]:
        return (self.find_media_mood(media).captions or self.index.default.captions) if media else self.find_list("captions")

    def random_caption(self, media: Path | None = None) -> str | None:
        captions = self.find_captions(media)
        return random.choice(captions) if captions else None

    def random_clicks_to_close(self, media: Path) -> int:
        return random.randint(1, self.find_media_mood(media).max_clicks)

    def random_subliminal(self) -> str | None:
        subliminals = self.find_list("subliminals")
        return random.choice(subliminals) if subliminals else self.random_caption()

    def random_notification(self) -> str | None:
        notifications = self.find_list("notifications")
        return random.choice(notifications) if notifications else self.random_caption()

    def random_denial(self) -> str:
        return random.choice(self.find_list("denial") or ["Not for you~"])

    def random_prompt(self) -> str | None:
        prompts = self.find_list("prompts")
        if not prompts:
            return None

        length = random.randint(self.index.default.prompt_min_length, self.index.default.prompt_max_length)

        prompt = ""
        for n in range(length):
            prompt += random.choice(prompts) + " "

        return prompt.strip()

    def random_web(self) -> str | None:
        web = random.choice(self.find_list("web") or [None])
        return web.url + random.choice(web.args or [""]) if web else None
