from math import fabs

import logging

from pyduel_engine.content import default_boards as boards
from pyduel_engine.content.engine_states import SqState, BoardType
from pyduel_engine.model import position as ps


def _adjacent_square_pos(pos):
    return [ps.shift_down(pos), ps.shift_up(pos),
            ps.shift_left(pos), ps.shift_right(pos),
            ps.shift_down_left(pos), ps.shift_down_right(pos),
            ps.shift_up_left(pos), ps.shift_up_right(pos)]

DEFAULT_MAX_X = 10
DEFAULT_MAX_Y = 7


class Board(object):
    """Board object that maintains the states of each position """

    # logging info
    logger = logging.getLogger(__name__)

    def __init__(self, kwargs):
        self._name = kwargs.get('name', None)
        self._type = kwargs.get('board_type', None)
        self._max_x = kwargs.get('max_x', DEFAULT_MAX_X)
        self._max_y = kwargs.get('max_y', DEFAULT_MAX_Y)
        self._board = kwargs.get('board', self.empty_board())

        if self._type:
            self.board_initializer(self._type)

    def __repr__(self):
        return self.type

    def __str__(self):
        """String representation of the board """
        board = str(self.type) + '\n    '
        for x in range(0, self.max_x):
            board += str(x)
        board += '\n--' + ('-' * (self.max_x + 2)) + '\n'

        for y in range(0, self.max_y):
            board += str(y) + ' | '
            for x in range(0, self.max_x):
                if self.board[x][y]['state'] == SqState.empty:
                    board += 'E'
                if self.board[x][y]['state'] == SqState.dark:
                    board += 'D'
                if self.board[x][y]['state'] == SqState.hole:
                    board += 'H'
                if self.board[x][y]['state'] == SqState.light:
                    board += 'L'
                if self.board[x][y]['state'] == SqState.obstacle:
                    board += 'O'
            board += '\n'

        return board

    # ############################ Properties #################################

    @property
    def board(self):
        return self._board

    @property
    def name(self):
        return self._name

    @property
    def max_x(self):
        return self._max_x

    @property
    def max_y(self):
        return self._max_y

    @property
    def type(self):
        return self._type

    # ############################ Modifiers ##################################

    def sqr_state(self, pos):
        """Get square state from board """
        return self.board[pos.x][pos.y]['state']

    def set_sqr_state(self, pos, char_side):
        """Sets square state at pos on board """
        self.board[pos.x][pos.y]['state'] = char_side

    def make_empty(self, pos):
        """Sets position to empty on board"""
        self.board[pos.x][pos.y]['state'] = SqState.empty

    def empty_board(self):
        """Creates an empty board with the given dimensions"""
        return {x: {y: {'state': SqState.empty}
                    for y in range(0, self._max_y + 1)}
                for x in range(0, self._max_x + 1)}

    def find_moves(self, char, num_moves, pos=None, moves=None):
        """Finds list of all possible moves for a character """
        # TODO: FIX THIS Method
        # create list
        if moves is None:
            moves = set()
            pos = char.pos
        else:
            # if the new position isn't valid return without adding
            # only specifying side here as an input, can't remember why
            if not self.is_valid_move(char.side, pos):
                return moves

            # add move if square is not occupied
            if self.is_empty(pos):
                moves.add(pos)

            # if no num_moves
            if num_moves == 0:
                return moves

                # decrement the number of moves
        num_moves -= 1

        # check up down left and right and make sure we don't backtrack
        up = ps.shift_up(pos)
        down = ps.shift_down(pos)
        left = ps.shift_left(pos)
        right = ps.shift_right(pos)

        # if up not in moves:
        self.find_moves(char, num_moves, up, moves)
        # if down not in moves:
        self.find_moves(char, num_moves, down, moves)
        # if left not in moves:
        self.find_moves(char, num_moves, left, moves)
        # if right not in moves:
        self.find_moves(char, num_moves, right, moves)

        # return list(moves)
        return list(moves)

    def get_adj_empty_pos(self, pos):
        """Returns list of all empty positions on the board"""
        if not self.out_of_bounds(pos):
            adj_pos = _adjacent_square_pos(pos)
            return [x for x in adj_pos
                    if not self.out_of_bounds(x) and self.is_empty(x)]
        return []

    def board_initializer(self, board_type):
        """ builds board by adding holes, obstructions, name, type"""
        selected_board = boards.BOARDS[board_type]
        self._name = selected_board['name']
        self._type = BoardType(board_type)

        # Json board coordinates
        for name, sq_type in SqState.__members__.items():
            if sq_type in selected_board['states']:
                for pos in selected_board['states'][sq_type]:
                    self.board[pos['x']][pos['y']]['state'] = sq_type

    # ############################ Discovery ##################################

    def is_empty(self, pos):
        """check if square is empty"""
        return self.sqr_state(pos) == SqState.empty

    def is_ally(self, side, pos):
        """check if square is ally"""
        return self.sqr_state(pos) == side

    def _is_parallel_clr_x_axis(self, origin, target):
        """called if the pivot is on the x plane and traverses through all the
        squares to check if path is clear"""
        pivot = origin.x
        if origin.y < target.y:
            i = origin.y + 1
            end = target.y
        else:
            i = target.y + 1
            end = origin.y
        while i != end:
            if self.board[pivot][i]['state'] == SqState.empty or \
               self.board[pivot][i]['state'] == SqState.hole:
                i += 1
            else:
                return False
        return True

    def _is_parallel_clr_y_axis(self, origin, target):
        """called if the pivot is on the y plane and traverses through all the
        squares to check if path is clear"""
        pivot = origin.y

        if origin.x < target.x:
            i = origin.x + 1
            end = target.x
        else:
            i = target.x + 1
            end = origin.x
        while i != end:
            if self.board[i][pivot]['state'] == SqState.empty or \
               self.board[i][pivot]['state'] == SqState.hole:
                i += 1
            else:
                return False
        return True

    def is_parallel_clr(self, origin, target):
        """Verify if there is a clear parallel path between two points"""
        # not parallel, moot point
        if not origin.is_parallel(target):
            return False

        # if adj it's already a valid target
        if origin.is_adj(target):
            return True
        if origin.x == target.x:
            # when x is the pivot
            return self._is_parallel_clr_x_axis(origin, target)
        else:
            # when y is the pivot
            return self._is_parallel_clr_y_axis(origin, target)

    def is_diagonal_clr(self, origin, target):
        """Verify if there is a clear diagonal path between two points"""
        # default x and y to negative modifiers
        x, y = -1, -1

        # count number of loops required
        end = int(fabs(origin.x - target.x) - 1)

        # make x modifier positive
        if origin.x < target.x:
            x = 1

        # make y modifier positive
        if origin.y < target.y:
            y = 1

        # loop and check
        for i in range(1, end):
            front = self.board[origin.x + x * i][origin.y + y * i]['state']
            if front != SqState.empty and front != SqState.hole:
                return False
        return True

    def is_obstructed(self, char):
        """Verify if a character can move through any of the adj squares"""
        return not self.can_move_through(char.side, ps.shift_down(char.pos)) \
            and not self.can_move_through(char.side, ps.shift_up(char.pos)) \
            and not self.can_move_through(char.side, ps.shift_left(char.pos)) \
            and not self.can_move_through(char.side, ps.shift_right(char.pos))

    def out_of_bounds(self, pos):
        """check if square is out of bounds or not"""
        return pos.x > self.max_x - 1 or pos.x < 0 \
            or pos.y > self.max_y - 1 or pos.y < 0

    def can_move_through(self, side, new_pos):
        """verifies if a a square can be moved on or through """
        return self.is_empty(new_pos) or self.is_ally(side, new_pos)

    def is_valid_move(self, side, new_pos):
        """check if is valid move
        takes in team side and pos. This function asks if a character of a
        specific side can move to another square. calls two methods
        to see if the square is out of bounds and if the char can validly move
        through the square """
        return not self.out_of_bounds(new_pos) and self.can_move_through(
            side, new_pos)

    def can_target(self, char, target):
        """Verify if target can be range attacked. """
        if char.is_adj(target):
            return True
        if char.is_range:
            if (char.is_diagonal(target) and
                    self.is_diagonal_clr(char.pos, target.pos)) \
                    or (char.is_parallel and
                        self.is_parallel_clr(char.pos, target.pos)):
                return True
        return False

# ######################## Print / Format Methods ############################

    def print_board_coordinates(self):
        """Prints the board out with coordinates used this before to
        help visualize the board more easily """
        board = str(self.type) + '\n    '
        for x in range(0, self.max_x):
            board += ' ' + str(x) + '   '
        board += '\n' + ('----' + ('-' * self.max_x * 5)) + '\n'

        for y in range(0, self.max_y):
            board += str(y) + ' | '
            for x in range(0, self.max_x):
                if self.board[x][y]['state'] == SqState.empty:
                    board += ''.join(['(', str(x), ',', str(y), ')'])
                if self.board[x][y]['state'] == SqState.dark:
                    board += ''.join(['(', str(x), ',', str(y), ')'])
                if self.board[x][y]['state'] == SqState.hole:
                    board += ''.join(['(', str(x), ',', str(y), ')'])
                if self.board[x][y]['state'] == SqState.light:
                    board += ''.join(['(', str(x), ',', str(y), ')'])
                if self.board[x][y]['state'] == SqState.obstacle:
                    board += ''.join(['(', str(x), ',', str(y), ')'])
            board += '\n'
        return board
