use std::rc::Rc;
use std::cell::RefCell;
use std::thread::sleep;
use std::time::Duration;
use rand::random;
use crate::labyrinth::Direction::{Down, Left, Right, Up};
use crate::map::{Field, Map};

type Ref<T> = Rc<RefCell<T>>;

fn new<T>(value: T) -> Ref<T> {
    return Rc::new(RefCell::new(value))
}

struct Labyrinth {
    map: Ref<Map>,
    player: Ref<Player>,
    solver: Ref<dyn Solver>
}

impl Labyrinth {
    fn new(create_solver: fn(Ref<Map>, Ref<Player>) -> Ref<dyn Solver>) -> Labyrinth {
        let map = new(Map::new(10, 10));
        let player = new(Player { x: 8, y: 8, map: map.clone()});
        let solver = create_solver(map.clone(), player.clone());
        return Labyrinth {
            map,
            player,
            solver
        };
    }

    fn solve(&self) {
        self.solver.borrow_mut().solve()
    }
}

struct Player {
    x: i16,
    y: i16,
    map: Ref<Map>
}

enum Direction {
    Up,
    Down,
    Left,
    Right
}

impl Player {
    fn free(&self, direction: Direction) -> bool {
        let [x, y] = self.new_position_for_direction(direction);
        return self.map.borrow().free(x, y)
    }

    fn new_position_for_direction(&self, direction: Direction) -> [i16; 2] {
        return match direction {
            Direction::Up => {[self.x, self.y - 1]}
            Direction::Down => {[self.x, self.y + 1]}
            Direction::Left => {[self.x - 1, self.y]}
            Direction::Right => {[self.x + 1, self.y]}
        };
    }

    fn move_if_possible(&mut self, direction: Direction) {
        let [x, y] = self.new_position_for_direction(direction);
        if self.map.borrow().free(x, y) {
            self.x = x;
            self.y = y;
        }
    }
}

trait Solver {
    fn solve(&mut self);
}

struct RandomSolver {
    map: Ref<Map>,
    player: Ref<Player>,
    number_of_moves: usize
}

impl Solver for RandomSolver {
    fn solve(&mut self) {
        loop {
            let direction = match random::<u8>() % 4 {
                 0 => Up,
                 1 => Down,
                 2 => Left,
                 3 => Right,
                _ => panic!("Logical error")
            };
            let mut player= self.player.borrow_mut();
            player.move_if_possible(direction);
            let map = self.map.borrow();
            map.print(player.x, player.y);
            self.number_of_moves += 1;
            let number_of_moves = self.number_of_moves;
            println!("{number_of_moves}");
            if map.get(player.x, player.y) == Field::Goal {
                return;
            }
            sleep(Duration::from_secs(1));
        }
    }
}

pub(crate) fn run() {
    let labyrinth = Labyrinth::new(
        |map: Ref<Map>, player: Ref<Player>| new(RandomSolver { map: map, player: player, number_of_moves: 0}));
    labyrinth.solve()
}