from alexander.concepts import Action, Program
from .algorithm import bfs
from enum import Enum


class GridContent(Enum):
    UNKNOWN = -1
    EMPTY = 0
    MONSTER = 1
    HUNTER = 2


class Actions(Enum):
    MOVE_FORWARD = Action('move_forward')
    SHOOT = Action('shoot')
    ROTATE_LEFT = Action('rotate_left')
    ROTATE_RIGHT = Action('rotate_right')


class HunterProgram(Program):
    def __init__(self):
        self.initiated = False
        self.grid_size = None
        self.grid = None
        self.hunter_position = None
        self.hunter_rotation = None
        self.monster_count = 0
        self.hunter_projectile_range = 1

    def process(self, percept):
        self.update_state_with_percept(percept)
        action = self.choose_action()
        self.update_state_with_action(action)
        return action

    def update_state_with_percept(self, percept):
        if not self.initiated:
            self.grid_size = percept.state['MapSize']
            self.grid = [[GridContent.UNKNOWN for _ in range(self.grid_size['x'])] for _ in range(self.grid_size['y'])]
            self.initiated = True

        if self.monster_count < percept.state['MonsterCount']:
            self.grid = [[GridContent.UNKNOWN for _ in range(self.grid_size['x'])] for _ in range(self.grid_size['y'])]

        self.hunter_position = self.translate_pos(percept.state['HunterPosition']['x'],
                                                  percept.state['HunterPosition']['y'])
        self.hunter_rotation = percept.state['HunterRotation']
        self.monster_count = percept.state['MonsterCount']
        self.hunter_projectile_range = percept.state['HunterProjectileDistance']
        for vision in percept.state['Vision']:
            x, y = self.translate_pos(vision['Position']['x'], vision['Position']['y'])
            self.grid[y][x] = GridContent(vision['Content'])

    def choose_action(self):
        if self.monster_count > 0:
            monsters_sighted = []
            unknown_cells = []
            for y, row in enumerate(self.grid):
                for x, col in enumerate(row):
                    if col == GridContent.MONSTER:
                        monsters_sighted.append((x, y))
                    elif col == GridContent.UNKNOWN:
                        unknown_cells.append((x, y))

            if len(monsters_sighted) == 0:
                path_to_nearest_unknown = bfs.bfs(self.hunter_position, unknown_cells, self.grid)
                if path_to_nearest_unknown:
                    next_cell = path_to_nearest_unknown[0]
                    rotate_action = self.calc_rotate_action(next_cell)
                    if rotate_action:
                        return rotate_action
                    else:
                        return Actions.MOVE_FORWARD.value
            else:
                target_cells = []
                target_cells_monster = {}
                for x, y in monsters_sighted:
                    for i in range(1, self.hunter_projectile_range + 1):
                        target_cells_curr = [(x + i, y), (x - i, y), (x, y + i), (x, y - i)]
                        for target_cell in target_cells_curr:
                            target_cells.append(target_cell)
                            key = HunterProgram.stringify_coordinate(target_cell[0], target_cell[1])
                            target_cells_monster[key] = (x, y)

                if self.hunter_position in target_cells:
                    key = HunterProgram.stringify_coordinate(self.hunter_position[0], self.hunter_position[1])
                    monster_cell = target_cells_monster[key]
                    rotate_action = self.calc_rotate_action(monster_cell)
                    if rotate_action:
                        return rotate_action
                    else:
                        return Actions.SHOOT.value

                else:
                    path_to_target = bfs.bfs(self.hunter_position, target_cells, self.grid)
                    if path_to_target:
                        next_cell = path_to_target[0]
                        rotate_action = self.calc_rotate_action(next_cell)
                        if rotate_action:
                            return rotate_action
                        else:
                            return Actions.MOVE_FORWARD.value

    def update_state_with_action(self, action):
        pass

    def calc_rotate_action(self, cell):
        if self.hunter_position[0] == cell[0] and self.hunter_position[1] == cell[1]:
            return None
        elif self.hunter_position[0] == cell[0]:
            if self.hunter_position[1] > cell[1]:
                target_direction = 180
            else:
                target_direction = 0
        elif self.hunter_position[1] == cell[1]:
            if self.hunter_position[0] > cell[0]:
                target_direction = 270
            else:
                target_direction = 90
        else:
            return None

        if (self.hunter_rotation - target_direction) % 360 == 90:
            return Actions.ROTATE_LEFT.value
        elif (self.hunter_rotation - target_direction) % 360 == 180:
            return Actions.ROTATE_LEFT.value
        elif (self.hunter_rotation - target_direction) % 360 == 270:
            return Actions.ROTATE_RIGHT.value
        else:
            return None

    def translate_pos(self, x, y):
        return self.grid_size['x'] // 2 + x, self.grid_size['y'] // 2 + y

    def debug(self):
        print('map_size', self.grid_size)
        print('map\n---')
        if self.grid:
            for row in self.grid:
                row_str = ''
                for col in row:
                    if col == GridContent.EMPTY:
                        row_str += '- '
                    elif col == GridContent.MONSTER:
                        row_str += 'M '
                    elif col == GridContent.HUNTER:
                        row_str += 'H '
                    elif col == GridContent.UNKNOWN:
                        row_str += '? '
                print(row_str)

    @staticmethod
    def stringify_coordinate(x, y):
        return '%d,%d' % (x, y)

    @staticmethod
    def parse_coordinate(s):
        x, y = s.split(',')
        return int(x), int(y)
