use std::ops::RangeInclusive;

use crate::position::{HasPosition, Position};
use macros::CreateMonsters;
use rand::Rng;
use ratatui::prelude::Color;

pub trait Monster: HasPosition {
    fn get_name(&self) -> &str;
    fn is_dead(&self) -> bool;
    fn get_representation(&self) -> (&str, Color);
    fn decrease_life(&mut self, by: usize);
    // fn get_immutable_position(&self) -> &Position;
    fn get_experience_gain(&self) -> usize;
    fn get_ticks_between_steps(&self) -> u128;
    #[cfg(test)]
    fn get_life(&self) -> usize;
    fn damage(&self) -> usize;
}

#[derive(CreateMonsters, PartialEq, Eq, Hash, Clone, Copy)]
pub enum MonsterTypes {
    Rat,
    Orc,
    Snake,
    Skeleton,
    Spider,
}

macro_rules! create_monster {
    ($t:ident $(, $k:ident : $v:expr)*$(,)?) => (
        impl $t {
            pub fn new_with_position(position: Position) -> Self {
                Self {
                    position,
                    $($k: $v,)*
                }
            }
        }
    )
}

create_monster!(
    Rat,
    name:"rat".to_string(),
    life: 2,
    symbol: String::from("r"),
    color: Color::Black,
    experience_gain: 5,
    ticks_between_steps: 5,
    damage_range: 1..=2,
);

create_monster!(
    Spider,
    name:"spider".to_string(),
    life: 3,
    symbol: String::from("s"),
    color: Color::Blue,
    experience_gain: 7,
    ticks_between_steps: 7,
    damage_range: 2..=3,
);

create_monster!(
    Orc,
    name: "orc".to_string(),
    life: 4,
    symbol: String::from("O"),
    color: Color::DarkGray,
    experience_gain: 10,
    ticks_between_steps: 10,
    damage_range: 2..=3,
);

create_monster!(
    Snake,
    name: "snake".to_string(),
    life: 3,
    symbol: String::from("s"),
    color: Color::Black,
    experience_gain: 10,
    ticks_between_steps: 20,
    damage_range: 1..=4,
);

create_monster!(
    Skeleton,
    name: "skeleton".to_string(),
    life: 3,
    symbol: String::from("S"),
    color: Color::DarkGray,
    experience_gain: 20,
    ticks_between_steps: 10,
    damage_range: 1..=5,
);

pub fn create_monster_by_type(monster_type: &MonsterTypes, position: Position) -> Box<dyn Monster> {
    match monster_type {
        MonsterTypes::Rat => Box::new(Rat::new_with_position(position)),
        MonsterTypes::Orc => Box::new(Orc::new_with_position(position)),
        MonsterTypes::Snake => Box::new(Snake::new_with_position(position)),
        MonsterTypes::Skeleton => Box::new(Skeleton::new_with_position(position)),
        MonsterTypes::Spider => Box::new(Spider::new_with_position(position)),
    }
}

#[test]
fn monsters_can_move() {
    let mut m = Rat::new_with_position(Position::new(0, 0, 0));
    assert_eq!(m.get_position(), &Position::new(0, 0, 0));
    m.get_position().change(1, 2);
    assert_eq!(m.get_position(), &Position::new(0, 1, 2));
    m.get_position().change(2, 1);
    assert_eq!(m.get_position(), &Position::new(0, 3, 3));
    m.get_position().set(1, 2, 3);
    m.get_position().change(2, 1);
    assert_eq!(m.get_position(), &Position::new(1, 4, 4));
}

#[test]
fn monsters_can_die() {
    let mut m = Rat::new_with_position(Position::new(0, 0, 0));
    assert_eq!(m.get_life(), 2);
    assert_eq!(m.is_dead(), false);
    m.decrease_life(1);
    assert_eq!(m.get_life(), 1);
    m.decrease_life(2);
    assert_eq!(m.get_life(), 0);
    assert_eq!(m.is_dead(), true);
}