from decorators import help
from discord.ext import commands
from enums.help_categories import Category
from functions import checks
import itertools
import random


class Minesweeper(commands.Cog):
    def __init__(self, client):
        self.client = client

    # Don't allow any commands to work when locked
    def cog_check(self, ctx):
        return not self.client.locked

    @commands.command(name="Minesweeper", aliases=["Ms"], usage="[Niveau]*")
    @commands.check(checks.allowedChannels)
    @help.Category(category=Category.Games)
    async def minesweeper(self, ctx, difficulty="m"):
        if difficulty[0].lower() not in "emh":
            await ctx.send("Geef een geldige moeilijkheidsgraad op.")
            return

        await ctx.send(self.createGame(difficulty[0].lower()))

    def createGame(self, difficutly):

        # [X, Y, BombCount]
        dimensions = {
            "e": [5, 5, 4],
            "m": [10, 10, 20],
            "h": [13, 13, 35]
        }

        numbers = {
            0: "||:zero:||",
            1: "||:one:||",
            2: "||:two:||",
            3: "||:three:||",
            4: "||:four:||",
            5: "||:five:||",
            6: "||:six:||",
            7: "||:seven:||",
            8: "||:eight:||",
        }

        # Initialize an empty grid
        grid = [[" " for _ in range(dimensions[difficutly][0])] for _ in range(dimensions[difficutly][1])]

        # Generate every possible position on the grid
        positions = set(itertools.product([x for x in range(len(grid))], repeat=2))

        # Place the bombs in the grid randomly
        for i in range(dimensions[difficutly][2]):
            bombPosition = random.choice(list(positions))
            positions.remove(bombPosition)
            grid[bombPosition[0]][bombPosition[1]] = "||:boom:||"

        # Add in the numbers representing the amount of bombs nearby
        for i, row in enumerate(grid):
            for j, cell in enumerate(row):
                if cell == " ":
                    grid[i][j] = numbers[self.countBombs(grid, [i, j])]

        # Reveal the biggest empty space to the player
        self.revealSpaces(grid, self.findBiggestEmptySpace(grid))

        # Join the grid into a string
        li = [" ".join(row) for row in grid]
        return "\n".join(li)

    # Counts the amount of bombs near a given cell
    def countBombs(self, grid, cell):
        positions = [
            [1, -1], [1, 0], [1, 1],
            [0, -1], [0, 1],
            [-1, -1], [-1, 0], [-1, 1]
        ]

        count = 0
        for position in positions:
            if 0 <= cell[0] + position[0] < len(grid) and 0 <= cell[1] + position[1] < len(grid[0]):
                if "boom" in grid[cell[0] + position[0]][cell[1] + position[1]]:
                    count += 1

        return count

    # Finds the biggest spot of 0's on the grid to reveal at the start
    def findBiggestEmptySpace(self, grid):
        spaces = []
        biggest = []

        # Floodfill
        for i, row in enumerate(grid):
            for j, cell in enumerate(row):
                # Only check cells that aren't part of a space yet
                if not any(cell in space for space in spaces) and "zero" in cell:
                    li = [[i, j]]
                    changed = True
                    while changed:
                        changed = False
                        for added in li:
                            neighb = self.neighbours(grid, added)
                            # Add all neighbours that have not yet been added to this space
                            for neighbour in neighb:
                                if neighbour not in li:
                                    li.append(neighbour)
                                    changed = True
                    spaces.append(li)

                    # If it's bigger than the current biggest, make it the new biggest
                    if len(li) > len(biggest):
                        biggest = li
        return biggest

    # Returns all neighbouring cells containing a 0
    def neighbours(self, grid, cell):
        positions = [
            [1, 0],
            [0, -1], [0, 1],
            [-1, 0]
        ]

        neighb = []

        for position in positions:
            if 0 <= cell[0] + position[0] < len(grid) and 0 <= cell[1] + position[1] < len(grid[0]):
                if "zero" in grid[cell[0] + position[0]][cell[1] + position[1]]:
                    neighb.append([cell[0] + position[0], cell[1] + position[1]])
        return neighb

    # Take away the spoiler marks from the biggest empty space to help the player start
    def revealSpaces(self, grid, emptySpaces):
        positions = [
            [1, -1], [1, 0], [1, 1],
            [0, -1], [0, 1],
            [-1, -1], [-1, 0], [-1, 1]
        ]

        for space in emptySpaces:
            grid[space[0]][space[1]] = ":zero:"
            # Reveal all spaces around this one
            for position in positions:
                # Check if the space is not zero & is contained inside the grid & the space hasn't been cut before
                if 0 <= space[0] + position[0] < len(grid) and \
                        0 <= space[1] + position[1] < len(grid[0]) and \
                        "zero" not in grid[space[0] + position[0]][space[1] + position[1]] and \
                        "||" in grid[space[0] + position[0]][space[1] + position[1]]:
                    # Cut the spoiler from this cell
                    grid[space[0] + position[0]][space[1] + position[1]] = grid[space[0] + position[0]][
                                                                               space[1] + position[1]][2:-2]


def setup(client):
    client.add_cog(Minesweeper(client))