from server.GameBoard import GameBoard
import random

class TemplateSnake:
  def __init__(self):
    self.history = []
    self.target_food = None

  def clear_history(self):
    self.history = []

  def add_to_history(self, data:dict):
    self.history.append(data)

  def get_history(self):
    return self.history

  def add_calculations(self, calculations:dict):
    self.calculations.append(calculations)

  def choose_move(self, game_data:GameBoard):
    self.game_board = game_data
    self.calculations = []
    self.eat_the_snake_overwrite = False

    self.safe_positions = self.find_safe_positions(add_to_calculations=True)
    moves = list(self.safe_positions.keys())
    if len(moves) > 0:
      move = random.choice(moves)
    else:
      print("No safe positions left - Going to Die")
      move = None

    self.add_to_history({"turn": game_data.get_turn(), "data": self.calculations})
    return move if move else "up"

  def get_possible_moves(self, snake_head):
    return {
      "up": {
        "x": snake_head["x"],
        "y": snake_head["y"] + 1
      },
      "down": {
        "x": snake_head["x"],
        "y": snake_head["y"] - 1
      },
      "left": {
        "x": snake_head["x"] - 1,
        "y": snake_head["y"]
      },
      "right": {
        "x": snake_head["x"] + 1,
        "y": snake_head["y"]
      }
    }

  def get_snake_body_without_snake_tail(self, snake:list[dict]):
    if len(set((pos["x"], pos["y"]) for pos in snake)) < 3:
      return snake

    snake.pop()
    return snake

  def avoid_my_body(self, my_body:list[dict], my_head:dict, safe_positions:dict[str, dict], add_to_calculations:bool=False) -> list:
    """
    my_body: List of dictionaries of x/y coordinates for every segment of a Battlesnake.
            e.g. [ {"x": 0, "y": 0}, {"x": 1, "y": 0}, {"x": 2, "y": 0} ]
    possible_moves: List of strings. Moves to pick from.
            e.g. ["up", "down", "left", "right"]

    return: The list of remaining possible_moves, with the 'neck' direction removed
    """
    remove = []
    my_body = self.did_snake_eat_food(my_body, my_head, add_to_calculations)
    for direction, location in safe_positions.items():
      if location in my_body:
        remove.append(direction)

    for direction in remove:
      del safe_positions[direction]

    if add_to_calculations:
      self.add_calculations({"function": "avoid_my_body", "my_body": my_body, "safe_positions": safe_positions})

    return safe_positions

  def avoid_walls(self, safe_positions:dict[str, dict], add_to_calculations:bool=False):
    remove = []
    for direction, location in list(safe_positions.items()):
      x_out_range = (location["x"] < 0 or location["x"] == self.game_board.get_width())
      y_out_range = (location["y"] < 0 or location["y"] == self.game_board.get_height())
      if x_out_range or y_out_range:
        remove.append(direction)

    for direction in remove:
      del safe_positions[direction]

    if add_to_calculations:
      self.add_calculations({"function": "avoid_walls", "board_width": self.game_board.get_width(), "board_height": self.game_board.get_height(), "safe_positions": safe_positions})

    return safe_positions

  def avoid_snakes(self, other_snakes:list[dict], safe_positions:dict[str, dict], add_to_calculations:bool=False):
    remove = []
    for snake in other_snakes:
      for direction, location in safe_positions.items():
        #if self.game_type == "constrictor":
          if location in snake["body"]:
            remove.append(direction)
        #else:
        #  if location in self.get_snake_body_without_snake_tail(snake["body"]):
        #    remove.append(direction)

    remove = set(remove)
    for direction in remove:
      del safe_positions[direction]

    if add_to_calculations:
      self.add_calculations({"function": "avoid_snakes", "other_snakes": other_snakes, "safe_positions": safe_positions})

    return safe_positions

  def avoid_get_eaten_by_other_snakes(self, other_snakes:list[dict], safe_positions:dict[str, dict], add_to_calculations:bool=False):
    remove = []
    no_way_out = {}
    self.other_snake_posible_moves = []

    for snake in other_snakes:
      for direction, location in safe_positions.items():
        if len(safe_positions) > 1:
          self.other_snake_posible_moves = [{"x": v["x"], "y": v["y"]} for k, v in self.get_possible_moves(snake["head"]).items()]
          if snake["length"] < self.game_board.get_my_snake()["length"] and location in self.other_snake_posible_moves:
            self.eat_the_snake_overwrite = True
            self.kill_the_snake = direction
          #TODO: Testing - Check if snake on the way to the food here and only remove this pos
          elif location in self.other_snake_posible_moves and location in [{"x": food["x"], "y": food["y"]} for food in self.game_board.get_food()]:
            remove.append(direction)
          elif location in self.other_snake_posible_moves:
            no_way_out[direction] = location
            remove.append(direction)

    remove = set(remove)
    for direction in remove:
      del safe_positions[direction]

    if len(safe_positions) == 0:
      safe_positions = no_way_out

    if add_to_calculations:
      self.add_calculations({"function": "avoid_get_eaten_by_other_snakes", "other_snakes": other_snakes, "safe_positions": safe_positions})

    return safe_positions

  def find_safe_positions(self, add_to_calculations:bool=False):
    safe_positions = self.get_possible_moves(self.game_board.get_my_snake_head())
    if add_to_calculations:
      self.add_calculations({"function": "get_possible_moves", "safe_positions": safe_positions})

    safe_positions = self.avoid_my_body(self.game_board.get_my_snake_body(), self.game_board.get_my_snake_head(), safe_positions, add_to_calculations)
    safe_positions = self.avoid_walls(safe_positions, add_to_calculations)
    safe_positions = self.avoid_snakes(self.game_board.get_other_snakes(), safe_positions, add_to_calculations)
    safe_positions = self.avoid_get_eaten_by_other_snakes(self.game_board.get_other_snakes(), safe_positions, add_to_calculations)
    return safe_positions

  def calculate_new_body_position(self, move:str=None, with_tail:bool=False):
    if move:
      head = self.get_possible_moves(self.game_board.get_my_snake_head())[move]

      body = [head]
      body.extend(self.game_board.get_my_snake_body())
      body.pop()

      if not with_tail:
        body.pop()

      return body
    return move

  def did_snake_eat_food(self, my_body:list[dict], my_head:dict, add_to_calculations:bool=False):
    if self.target_food is None:
      if add_to_calculations:
        self.add_calculations({"function": "did_snake_eat_food", "my_body": my_body, "my_head": my_head, "target_food": self.target_food, "action": "No Target Food"})
      return my_body

    if self.target_food["x"] == my_head["x"] and self.target_food["y"] == my_head["y"]:
      if add_to_calculations:
        self.add_calculations({"function": "did_snake_eat_food", "my_body": my_body, "my_head": my_head, "target_food": self.target_food, "action": "Snake Eat no food"})
      return my_body

    if add_to_calculations:
      self.add_calculations({"function": "did_snake_eat_food", "my_body": my_body[:-1], "my_head": my_head, "target_food": self.target_food, "action": "Remove Tail from Body"})
    return my_body[:-1]

  def set_target_food(self, target_food:dict):
    self.target_food = target_food
    return True
