"""
.. module:: utils
   :synopsis: Contains miscellaneous utility functions.
"""
import numpy as np
from math import floor, ceil

from domains.gym_craft.utils.config import DIRECTIONS, OFFSETS


def json_default(obj):
    """
    Converts numpy types to standard types for serialisation into JSON.
    :param obj: object to be converted
    :returns: object as a standard type
    :raises TypeError: if the object is not a numpy type
    """
    if isinstance(obj, np.integer):
        return int(obj)
    elif isinstance(obj, np.floating):
        return float(obj)
    elif isinstance(obj, np.ndarray):
        return obj.tolist()
    else:
        raise TypeError


def discrete_position(position):
    x, y = position
    return int(x), int(y)


def discrete_direction(bearing):
    if bearing < 45 or bearing >= 315:
        return DIRECTIONS.north
    elif bearing < 135:
        return DIRECTIONS.east
    elif bearing < 225:
        return DIRECTIONS.south
    else:
        return DIRECTIONS.west


def facing_block(location, direction, grid_size=None):
    """
    Returns the block the player is currently facing.
    :return: block player is currently facing
    """
    position = [sum(x) for x in zip(location, OFFSETS[direction])]
    if grid_size is not None:
        position = np.clip(position, 0, grid_size)
    return tuple(position)


def blocking_terrain(terrain, block_mask):
    return np.max(terrain[:, :, block_mask], 2)


# def generate_maze(random, width=20, height=20, complexity=0.1, density=0.02):
#     """
#     Creates a randomly generated rectangular maze on a square grid.
#     :param width: width of maze
#     :param height: height of maze
#     :param complexity: Controls the length of each maze segment (as a percentage)
#     :param density: Controls the number of maze segments (as a percentage)
#     :returns: Array containing maze layout (True = wall)
#     """
#     # Only odd shapes
#     shape = ((height // 2) * 2 + 1, (width // 2) * 2 + 1)
#     # Adjust complexity and density relative to maze size
#     complexity = int(complexity * (5 * (shape[0] + shape[1])))  # number of components
#     density = int(density * ((shape[0] // 2) * (shape[1] // 2)))  # size of components
#     # Build actual maze
#     Z = np.zeros(shape, dtype=bool)
#     # Fill borders
#     Z[0, :] = Z[-1, :] = 1
#     Z[:, 0] = Z[:, -1] = 1
#     # Make aisles
#     for i in range(density):
#         x, y = (
#             random.randint(0, shape[1] // 2) * 2,
#             random.randint(0, shape[0] // 2) * 2,
#         )  # pick a random position
#         Z[y, x] = 1
#         for j in range(complexity):
#             neighbours = []
#             if x > 1:
#                 neighbours.append((y, x - 2))
#             if x < shape[1] - 2:
#                 neighbours.append((y, x + 2))
#             if y > 1:
#                 neighbours.append((y - 2, x))
#             if y < shape[0] - 2:
#                 neighbours.append((y + 2, x))
#             if len(neighbours):
#                 y_, x_ = neighbours[random.randint(0, len(neighbours) - 1)]
#                 if Z[y_, x_] == 0:
#                     Z[y_, x_] = 1
#                     Z[y_ + (y - y_) // 2, x_ + (x - x_) // 2] = 1
#                     x, y = x_, y_
#     return Z


# def generate_random_walls(grid_size, random, complexity=0.1, density=0.02):
#     """
#     Creates a list of random walls .
#     :param grid_size: dimension of gridworld
#     :param complexity: Controls the length of each wall section (as a percentage)
#     :param density: Controls the number of wall section (as a percentage)
#     :returns: List of walls represented as edges in lattice
#     :raises AssertError: if complexity and density values are invalid, must be in (0,1]
#     """
#     assert (
#         complexity > 0 and complexity <= 1
#     ), f"complexity value must be in the range (0,1] (currently {complexity})"
#     assert (
#         density > 0 and density <= 1
#     ), f"density value must be in the range (0,1] (currently {density})"
#     maze_size = 2 * grid_size + 1
#     m = generate_maze(random, maze_size, maze_size, complexity, density)[1:-1, 1:-1]
#     walls = []
#     for ((x, y), v) in np.ndenumerate(m):
#         if v and x % 2 + y % 2 == 1:  # xor of odd(x) and odd(y)
#             walls.append(
#                 (
#                     (int(floor(x / 2)), int(floor(y / 2))),
#                     (int(ceil(x / 2)), int(ceil(y / 2))),
#                 )
#             )
#     return walls

