use enum_iterator::Sequence;
use serde::{Deserialize, Serialize};

pub mod logic;
pub mod simulation;

pub const MAX_HEALTH: i32 = 100;

// API and Response Objects
// See https://docs.battlesnake.com/api

#[derive(
    Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Deserialize, Serialize, Sequence,
)]
#[serde(rename_all = "lowercase")]
pub enum Direction {
    /// Move left (-x)
    Left,
    /// Move up (+y)
    Up,
    /// Move right (+x)
    Right,
    /// Move down (-y)
    Down,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Action {
    /// In which direction the snake should move
    pub r#move: Direction,
    /// Say something to the other snakes
    #[serde(default, skip_serializing_if = "is_default")]
    pub shout: Option<String>,
}

fn is_default<T: Default + PartialEq>(value: &T) -> bool {
    *value == T::default()
}

#[derive(Deserialize, Serialize, Debug)]
pub struct Game {
    /// A unique identifier for this Game
    pub id: String,
    /// Information about the ruleset being used to run this game
    pub ruleset: Ruleset,
    /// The name of the map being played on.
    pub map: String,
    /// How much time your snake has to respond to requests for this Game
    pub timeout: u32,
    /// The source of this game.
    ///
    /// One of:
    /// - "tournament"
    /// - "league"
    /// - "arena"
    /// - "challenge"
    /// - "custom"
    ///
    /// The values may change.
    pub source: String,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Ruleset {
    /// Name of the ruleset being used to run this game.
    pub name: String,
    /// The release version of the [Rules](https://github.com/BattlesnakeOfficial/rules) module used in this game.
    pub version: String,
    /// A collection of specific settings being used by the current game that control how the rules
    /// are applied.
    pub settings: RulesetSettings,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct RulesetSettings {
    /// Percentage chance of spawning a new food every round.
    #[serde(rename = "foodSpawnChance")]
    pub food_spawn_chance: u8,
    /// Minimum food to keep on the board every turn.
    #[serde(rename = "minimumFood")]
    pub minimum_food: u8,
    /// Health damage a snake will take when ending its turn in a hazard. This stacks on top of the
    /// regular 1 damage a snake takes per turn.
    #[serde(rename = "hazardDamagePerTurn")]
    pub hazard_damage_per_turn: u8,
    /// rules for the royale mode
    pub royale: RulesetRoyale,
    /// rules for the squad mode
    pub squad: RulesetSquad,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct RulesetRoyale {
    /// The number of turns between generating new hazards (shrinking the safe board space).
    #[serde(rename = "shrinkEveryNTurns")]
    pub shrink_every_n_turns: i32,
}

#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Deserialize, Serialize)]
pub struct RulesetSquad {
    /// Allow members of the same squad to move over each other without dying.
    #[serde(rename = "allowBodyCollisions")]
    pub allow_body_collisions: bool,
    /// All squad members are eliminated when one is eliminated.
    #[serde(rename = "sharedElimination")]
    pub shared_elimination: bool,
    /// All squad members share health.
    #[serde(rename = "sharedHealth")]
    pub shared_health: bool,
    /// All squad members share length.
    #[serde(rename = "sharedLength")]
    pub shared_length: bool,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Board {
    /// The number of rows in the y-axis of the game board.
    pub height: i32,
    /// The number of columns in the x-axis of the game board.
    pub width: i32,
    /// Array of coordinates representing food locations on the game board.
    pub food: Vec<Coord>,
    /// Array of Battlesnakes representing all Battlesnakes remaining on the game board (including
    /// yourself if you haven't been eliminated).
    pub snakes: Vec<Battlesnake>,
    /// Array of coordinates representing hazardous locations on the game board.
    pub hazards: Vec<Coord>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Battlesnake {
    /// Unique identifier for this Battlesnake in the context of the current Game
    pub id: String,
    /// Name given to this Battlesnake by its author
    pub name: String,
    /// Health value of this Battlesnake, between 0 and 100
    pub health: i32,
    /// Array of coordinates representing this Battlesnake's location on the game board. This array
    /// is ordered from head to tail
    pub body: Vec<Coord>,
    /// Coordinates for this Battlesnake's head. Equivalent to the first element of the body array.
    pub head: Coord,
    /// Length of this Battlesnake from head to tail. Equivalent to the length of the body array.
    pub length: i32,
    /// The previous response time of this Battlesnake, in milliseconds. If the Battlesnake timed
    /// out and failed to respond, the game timeout will be returned.
    pub latency: String,
    /// Message shouted by this Battlesnake on the previous turn.
    pub shout: Option<String>,
    /// The squad that the Battlesnake belongs to. Used to identify squad members in Squad Mode
    /// games.
    pub squad: String,
    // /// The collection of customizations that control how this Battlesnake is displayed.
    // customizations: {color, head, tail}
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Deserialize, Serialize)]
pub struct Coord {
    pub x: i32,
    pub y: i32,
}

impl Coord {
    #[must_use]
    pub const fn move_to(mut self, direction: Direction) -> Self {
        match direction {
            Direction::Left => self.x -= 1,
            Direction::Up => self.y += 1,
            Direction::Right => self.x += 1,
            Direction::Down => self.y -= 1,
        }
        self
    }
}

#[derive(Deserialize, Serialize, Debug)]
pub struct GameState {
    pub game: Game,
    pub turn: i32,
    pub board: Board,
    pub you: Battlesnake,
}
