"""Base class for the games."""
import random
from typing import Union

from discord import File, User
from discord.abc import Messageable
from interactions import Button, ActionRow
from interactions import InteractionContext as IntCont

from PIL import ImageFont, Image, ImageDraw

from gwendolyn_old.exceptions import GameNotInDatabase
from gwendolyn_old.utils import encode_id

class GameBase():
  """The base class for the games."""

  def __init__(self, bot, game_name: int, drawer):
    """Initialize the class."""
    self.bot = bot
    self.long_strings = self.bot.long_strings
    self.resources =  "gwendolyn/resources/games/"
    self.game_name = game_name
    self.draw = drawer(bot, self)

  def _get_action_rows(self, buttons: list[tuple[str, list]]):
    self.bot.log("Generation action rows")
    button_objects = []
    for label, data, style in buttons:
      custom_id = encode_id([self.game_name] + data)
      button_objects.append(Button(
        style=style,
        label=label,
        custom_id=custom_id
      ))

    action_rows = []
    for i in range(((len(button_objects)-1)//5)+1):
      action_rows.append(ActionRow(*button_objects[i*5:i*5+5]))

    return action_rows

  async def _send_image(self, channel: Messageable,
              buttons: list[tuple[str, list]] = None, delete=True):
    self.draw.draw(str(channel.id))
    file_path = f"{self.resources}images/{self.game_name}{channel.id}.png"
    old_image = await channel.send(
      file=File(file_path), delete_after=120 if delete else None)

    if buttons is not None and len(buttons) < 25:
      await old_image.edit(components = self._get_action_rows(buttons))

    return old_image

class DatabaseGame(GameBase):
  """The base class for the games."""

  def __init__(self, bot, game_name, drawer):
    """Initialize the class."""
    super().__init__(bot, game_name, drawer)
    self.database = self.bot.database
    self.old_images_path = f"{self.resources}old_images/{self.game_name}"

  def access_document(self, channel: str, collection_name: str="games",
            raise_missing_error: bool=True):
    collection = self.bot.database[f"{self.game_name} {collection_name}"]
    game = collection.find_one({"_id": channel})
    if game is None and raise_missing_error:
      raise GameNotInDatabase(self.game_name, channel)

    return game

  def _test_document(self, channel: str, collection_name: str="games"):
    collection = self.bot.database[f"{self.game_name} {collection_name}"]
    game = collection.find_one({"_id": channel})
    return game is not None

  def _update_document(self, channel: str, updater: dict,
             collection_name: str="games"):
    self.database[f"{self.game_name} {collection_name}"].update_one(
      {"_id": channel},
      updater,
      upsert=True
    )

  def _delete_document(self, channel: str, collection_name: str="games"):
    self.database[f"{self.game_name} {collection_name}"].delete_one(
      {"_id": channel}
    )

  def _insert_document(self, data: dict, collection_name: str="games"):
    self.database[f"{self.game_name} {collection_name}"].insert_one(data)

  async def _delete_old_image(self, channel: Messageable):
    with open(self.old_images_path + str(channel.id), "r") as file_pointer:
      old_image = await channel.fetch_message(int(file_pointer.read()))

    await old_image.delete()

  async def _send_image(self, channel: Messageable,
              buttons: list[tuple[str, list]] = None, delete=True):
    old_image = await super()._send_image(channel, buttons, delete)
    with open(self.old_images_path + str(channel.id), "w") as file_pointer:
      file_pointer.write(str(old_image.id))

  async def _start_new(self, channel: Messageable, new_game: dict,
             buttons: list[tuple[str, list]] = None,
             delete = True):
    new_game['_id'] = str(channel.id)
    self._insert_document(new_game)
    await self._send_image(channel, buttons, delete)

  async def _end_game(self, channel: Messageable):
    await self._delete_old_image(channel)
    await self._send_image(channel, delete=False)
    self._delete_document(str(channel.id))


class CardGame(DatabaseGame):
  """The class for card games."""
  def __init__(self, bot, game_name, drawer, deck_used):
    """Initialize the class."""
    super().__init__(bot, game_name, drawer)
    self.decks_used = deck_used

  def _shuffle_cards(self, channel: str):
    self.bot.log(f"Shuffling cards for {self.game_name}")
    with open(f"{self.resources}deck_of_cards.txt", "r") as file_pointer:
      deck = file_pointer.read()

    all_decks = deck.split("\n") * self.decks_used
    random.shuffle(all_decks)

    self._update_document(
      channel,
      {"$set": {"_id": channel, "cards": all_decks}},
      "cards"
    )

  def _draw_card(self, channel: str):
    """
    Draw a card from the stack.

    *Parameters*
    ------------
      channel: str
        The id of the channel the card is drawn in.
    """
    self.bot.log("drawing a card")

    drawn_card = self.access_document(channel, "cards")["cards"][0]
    self._update_document(channel, {"$pop": {"cards": -1}}, "cards")

    return drawn_card

class BoardGame(GameBase):
  async def _test_opponent(self, ctx: IntCont, opponent: Union[int, User]):
    if isinstance(opponent, int):
      # Opponent is Gwendolyn
      if opponent in range(1, 6):
        difficulty = int(opponent)
        difficulty_text = f" with difficulty {difficulty}"
        opponent = self.bot.user.id
      else:
        await ctx.send("Difficulty doesn't exist")
        self.bot.log("They challenged a difficulty that doesn't exist")
        return False
    elif isinstance(opponent, User):
      if opponent.bot:
        # User has challenged a bot
        if opponent == self.bot.user:
          # It was Gwendolyn
          difficulty = 3
          difficulty_text = f" with difficulty {difficulty}"
          opponent = self.bot.user.id
        else:
          await ctx.send("You can't challenge a bot!")
          self.bot.log("They tried to challenge a bot")
          return False
      else:
        # Opponent is another player
        if ctx.author != opponent:
          opponent = opponent.id
          difficulty = 5
          difficulty_text = ""
        else:
          await ctx.send("You can't play against yourself")
          self.bot.log("They tried to play against themself")
          return False

    return opponent, (difficulty, difficulty_text)

class BaseDrawer():
  """Class for drawing games."""
  def __init__(self, bot, game: GameBase):
    self.bot = bot
    self.game = game
    self.fonts_path = "gwendolyn/resources/fonts/"
    self.font_name = "futura-bold"

    self.resources = game.resources
    game_name = game.game_name
    self.default_image = f"{self.resources}default_images/{game_name}.png"
    self.default_size = None
    self.default_color = None
    self.images_path = f"{self.resources}images/{game_name}"

  def _get_size(self, game: dict):
    return self.default_size

  def _draw_image(self, game: dict, image: Image.Image):
    pass

  def draw(self, channel: str):
    game = self.game.access_document(channel)
    if self.default_image is not None:
      image = Image.open(self.default_image)
    else:
      image = Image.new("RGB", self._get_size(game), self.default_color)
    self._draw_image(game, image)
    self._save_image(image, channel)

  def _get_font(self, size: int, font_name: str = None):
    if font_name is None:
      font_name = self.font_name
    return ImageFont.truetype(f"{self.fonts_path}{font_name}.ttf", size)

  def _adjust_font(self, max_font_size: int, text: str, max_text_size: int,
           font_name: str = None):
    font_size = max_font_size
    font = self._get_font(font_size, font_name)
    text_width = font.getsize(text)[0]

    while text_width > max_text_size:
      font_size -= 1
      font = self._get_font(font_size, font_name)
      text_width = font.getsize(text)[0]

    return font, font_size

  def _save_image(self, image: Image.Image, channel: str):
    self.bot.log("Saving image")
    image.save(f"{self.images_path}{channel}.png")

  def _draw_shadow_text(self, text: str, colors: list[tuple[int, int, int]],
              font_size: int):
    font = self._get_font(font_size)
    offset = font_size//20
    shadow_offset = font_size//10

    text_size = list(font.getsize(text))
    text_size[0] += 1 + (offset * 2)
    text_size[1] += 1 + (offset * 2) + shadow_offset

    image = Image.new("RGBA", tuple(text_size), (0, 0, 0, 0))
    text_image = ImageDraw.Draw(image)

    for color_index, color in enumerate(colors[:-1]):
      color_offset = offset//(color_index+1)
      if color_index == 0:
        color_shadow_offset = shadow_offset
      else:
        color_shadow_offset = 0

      for i in range(4):
        x_pos = [-1,1][i % 2] * color_offset
        y_pos = [-1,1][(i//2) % 2] * color_offset + color_shadow_offset
        position = (offset + x_pos, offset + y_pos)
        text_image.text(position, text, fill=color, font=font)

    text_image.text((offset, offset), text, fill=colors[-1], font=font)

    return image

class CardDrawer(BaseDrawer):
  def __init__(self, bot, game: GameBase):
    super().__init__(bot, game)
    self.cards_path = f"{self.resources}cards/"

    # pylint: disable=invalid-name
    self.CARD_WIDTH = 691
    self.CARD_HEIGHT = 1065

    self.CARD_BORDER = 100
    self.CARD_OFFSET = 125
    # pylint: enable=invalid-name

  def _set_card_size(self, wanted_card_width: int):
    ratio = wanted_card_width / self.CARD_WIDTH
    self.CARD_WIDTH = wanted_card_width
    self.CARD_HEIGHT = int(self.CARD_HEIGHT * ratio)
    self.CARD_BORDER = int(self.CARD_BORDER * ratio)
    self.CARD_OFFSET = int(self.CARD_OFFSET * ratio)

  def _draw_cards(self, hand: list[str], hidden_cards: int = 0):
    image_width = (self.CARD_BORDER * 2) + self.CARD_WIDTH
    image_width += (self.CARD_OFFSET * (len(hand)-1))
    image_size = (image_width, (self.CARD_BORDER * 2) + self.CARD_HEIGHT)
    background = Image.new("RGBA", image_size, (0, 0, 0, 0))

    for i, card in enumerate(hand):
      position = (
        self.CARD_BORDER + (self.CARD_OFFSET*i),
        self.CARD_BORDER
      )

      if i + hidden_cards < len(hand):
        card_image = Image.open(f"{self.cards_path}{card.upper()}.png")
      else:
        card_image = Image.open(f"{self.cards_path}red_back.png")

      card_image = card_image.resize(
        (self.CARD_WIDTH, self.CARD_HEIGHT),
        resample=Image.BILINEAR
      )
      background.paste(card_image, position, card_image)

    return background
