// Welcome to
// __________         __    __  .__                               __
// \______   \_____ _/  |__/  |_|  |   ____   ______ ____ _____  |  | __ ____
//  |    |  _/\__  \\   __\   __\  | _/ __ \ /  ___//    \\__  \ |  |/ // __ \
//  |    |   \ / __ \|  |  |  | |  |_\  ___/ \___ \|   |  \/ __ \|    <\  ___/
//  |________/(______/__|  |__| |____/\_____>______>___|__(______/__|__\\_____>
//
// This file can be a nice home for your Battlesnake logic and helper functions.
//
// To get you started we've included code to prevent your Battlesnake from moving backwards.
// For more info see docs.battlesnake.com

use log::info;
use rand::seq::SliceRandom;
use serde_json::{json, Value};

use crate::{Battlesnake, Board, Coord, Direction, Game, Move};

impl Battlesnake {
    /// Check if moving the snake in `direction` is safe.
    #[must_use]
    pub fn is_direction_safe(&self, direction: Direction, game: &Game, board: &Board) -> bool {
        if self.is_direction_death(direction, game, board) {
            return false;
        }

        let target = self.head.move_to(direction);

        // check if a bigger snake could move to this square
        if board
            .snakes
            .iter()
            .filter(|snake| snake.id != self.id && self.length <= snake.length)
            .flat_map(|snake| enum_iterator::all().map(|dir| snake.head.move_to(dir)))
            .any(|coord| target == coord)
        {
            return false;
        }

        // check for hazards
        if board.hazards.iter().any(|&coord| target == coord) {
            return false;
        }

        true
    }

    #[must_use]
    pub fn is_direction_death(&self, direction: Direction, game: &Game, board: &Board) -> bool {
        let target = self.head.move_to(direction);

        // check if target is out of bounds
        if !((0..board.width).contains(&target.x) && (0..board.height).contains(&target.y)) {
            return true;
        }

        // check if target is inside a snake
        if board
            .snakes
            .iter()
            .filter(|snake| {
                !(game.ruleset.settings.squad.allow_body_collisions && self.squad == snake.squad)
            })
            .flat_map(|snake| snake.body.iter())
            .any(|&coord| target == coord)
        {
            return true;
        }
        false
    }

    #[must_use]
    pub fn health_cost(&self, tile: Coord, game: &Game, board: &Board) -> i32 {
        // we can't leave the board
        if !((0..board.width).contains(&tile.x) && (0..board.height).contains(&tile.y)) {
            return self.health;
        }

        // we can't move into other snakes
        if board
            .snakes
            .iter()
            .filter(|snake| {
                !(game.ruleset.settings.squad.allow_body_collisions && self.squad == snake.squad)
            })
            .flat_map(|snake| snake.body.iter())
            .any(|&coord| tile == coord)
        {
            return self.health;
        }

        // every step costs us one health point
        let mut cost = 1;

        // hazards increase the health loss
        if board.hazards.iter().any(|&hazard| tile == hazard) {
            cost += game.ruleset.settings.hazard_damage_per_turn;
        }

        // check for bigger snakes that can move into this square
        for snake in board
            .snakes
            .iter()
            .filter(|snake| snake.id != self.id && self.length <= snake.length)
        {
            let moves = enum_iterator::all().map(|dir| snake.head.move_to(dir));
        }

        cost
    }
}

// info is called when you create your Battlesnake on play.battlesnake.com
// and controls your Battlesnake's appearance
// TIP: If you open your Battlesnake URL in a browser you should see this data
pub fn info() -> Value {
    info!("INFO");

    json!({
        "apiversion": "1",
        "author": "der-informatiker",
        "color": "#00FFEE",
        "head": "smart-caterpillar",
        "tail": "mouse",
    })
}

// start is called when your Battlesnake begins a game
pub fn start(_game: &Game, _turn: i32, _board: &Board, _you: &Battlesnake) {
    info!("GAME START");
}

// end is called when your Battlesnake finishes a game
pub fn end(_game: &Game, _turn: i32, _board: &Board, _you: &Battlesnake) {
    info!("GAME OVER");
}

// move is called on every turn and returns your next move
// Valid moves are "up", "down", "left", or "right"
// See https://docs.battlesnake.com/api/example-move for available data
pub fn get_move(game: &Game, turn: i32, board: &Board, you: &Battlesnake) -> Move {
    let moves = enum_iterator::all()
        .filter(|&direction| !you.is_direction_death(direction, game, board))
        .collect::<Vec<_>>();
    let safe_moves = moves
        .iter()
        .copied()
        .filter(|&direction| you.is_direction_safe(direction, game, board))
        .collect::<Vec<_>>();

    // Choose a random move from the safe ones
    let chosen = safe_moves
        .choose(&mut rand::thread_rng())
        .or_else(|| moves.choose(&mut rand::thread_rng()))
        .unwrap_or(&Direction::Up);

    info!("MOVE {}: {:?}", turn, chosen);
    Move {
        r#move: *chosen,
        shout: None,
    }
}
