import asyncio
import json
import logging
import pathlib
from shutil import copyfile
from types import SimpleNamespace
from typing import Optional, Union

import discord
from PIL import Image, ImageOps
from redbot.core import commands

from conquest.regioner import ConquestMap, composite_regions

log = logging.getLogger("red.fox_v3.conquest.conquestgame")


class ConquestGame:
    ext = "PNG"
    ext_format = "PNG"

    default_zoom_json = {"enabled": False, "x": -1, "y": -1, "zoom": 1.0}

    def __init__(self, map_path: pathlib.Path, game_name: str, custom_map_path: pathlib.Path):
        self.source_map = ConquestMap(map_path)
        self.source_map.load_data()
        self.game_name = game_name
        self.current_map_folder = custom_map_path

        self.settings_json = self.current_map_folder / "settings.json"

        self.current_filename = f"current.{self.ext}"
        self.current_map = self.current_map_folder / self.current_filename

        self.zoomed_current_filename = f"current_zoomed.{self.ext}"
        self.zoomed_current_map = self.current_map_folder / self.zoomed_current_filename

        self.numbered_current_filename = f"current_numbered.{self.ext}"
        self.numbered_current_map = self.current_map_folder / self.numbered_current_filename

        self.zoomed_numbered_current_filename = f"current_zoomed_numbered.{self.ext}"
        self.zoomed_numbered_current_map = (
            self.current_map_folder / self.zoomed_numbered_current_filename
        )

        self.region_max = self.source_map.region_max

        # self.zoom_is_out_of_date = {'current': True, 'blank': True}

    async def save_region(self, region):
        if not self.custom:
            return False
        pass  # TODO: region data saving

    async def start_game(self):
        if not self.current_map_folder.exists():
            self.current_map_folder.mkdir()
        copyfile(self.source_map.blank_path(), self.current_map)

    async def resume_game(self, ctx: commands.Context, reset: bool):

        if not reset and self.current_map.exists():
            await ctx.maybe_send_embed(
                "This map is already in progress, resuming from last game\n"
                "Use `[p]conquest set map [mapname] True` to start a new game"
            )
        else:
            await self.start_game()

    async def _process_take_regions(self, color, regions):
        im = Image.open(self.current_map)

        out: Optional[Image.Image] = await composite_regions(
            im,
            regions,
            color,
            self.source_map.masks_path(),
        )
        out.save(self.current_map, self.ext_format)  # Overwrite current map with new map
        # self.zoom_is_out_of_date.current = True

    async def create_numbered_map(self):
        if (
            not self.source_map.numbers_path().exists()
        ):  # No numbers map, can't add numbers to current
            return self.source_map.numbered_path()

        current_map = Image.open(self.current_map)
        numbers = Image.open(self.source_map.numbers_path()).convert("L")

        inverted_map = ImageOps.invert(current_map)

        loop = asyncio.get_running_loop()
        current_numbered_img = await loop.run_in_executor(
            None, Image.composite, current_map, inverted_map, numbers
        )

        current_numbered_img.save(self.numbered_current_map, self.ext_format)

        return self.numbered_current_map

    async def create_zoomed_map(
        self,
        x,
        y,
        zoom,
        source_map: Union[Image.Image, pathlib.Path],
        target_path: pathlib.Path,
        **kwargs,
    ):
        """Pass out_of_date when created a zoomed map based on something other than the settings json"""
        # if out_of_date:
        #     self.zoom_is_out_of_date.current = True

        # if current_map is None:
        #     current_map = Image.open(self.current_map_folder)
        #     target_map = self.zoomed_current_map

        if not isinstance(source_map, Image.Image):
            source_map = Image.open(source_map)

        w, h = source_map.size
        zoom2 = zoom * 2
        zoomed_map = source_map.crop((x - w / zoom2, y - h / zoom2, x + w / zoom2, y + h / zoom2))
        # zoomed_map = zoomed_map.resize((w, h), Image.LANCZOS)
        zoomed_map.save(target_path, self.ext_format)

        return True

    async def get_maybe_zoomed_map(self, version):
        zoom_data = {"enabled": False}

        if self.settings_json.exists():
            with self.settings_json.open() as zoom_json:
                zoom_data = json.load(zoom_json)

        if version == "numbered":
            map_path = self.create_numbered_map()
            zoomed_path = self.zoomed_numbered_current_map
        else:  # version == "current"
            map_path = self.current_map
            zoomed_path = self.zoomed_current_map

        if zoom_data["enabled"]:  # Send zoomed map instead of current map
            # if self.zoom_is_out_of_date:
            await self.create_zoomed_map(**zoom_data, source_map=map_path, target_map=zoomed_path)
            map_path = zoomed_path
            #   self.zoom_is_out_of_date = False

        return discord.File(fp=map_path)  # lol file names

    async def reset_zoom(self):
        if not self.settings_json.exists():
            return False

        with self.settings_json.open("w+") as zoom_json:
            json.dump({"enabled": False}, zoom_json, sort_keys=True, indent=4)

        # self.zoom_is_out_of_date = True

        return True

    async def set_zoom(self, x, y, zoom):

        zoom_data = self.default_zoom_json.copy()
        zoom_data["enabled"] = True
        zoom_data["x"] = x
        zoom_data["y"] = y
        zoom_data["zoom"] = zoom

        # self.zoom_is_out_of_date = True

        with self.settings_json.open("w+") as zoom_json:
            json.dump(zoom_data, zoom_json, sort_keys=True, indent=4)

    async def save_as(self, save_name):
        copyfile(self.current_map, self.current_map_folder / f"{save_name}.{self.ext}")

    async def load_from(self, save_name):
        saved_map = self.current_map_folder / f"{save_name}.{self.ext}"

        if not saved_map.exists():
            return False

        copyfile(saved_map, self.current_map)  # Overwrite current map with saved map
        return True
