use rand::Rng;

pub const COLUMNS: usize = 4;
pub const ROWS: usize = 4;

pub enum Directions {
    Down,
    Up,
    Right,
    Left,
}

pub struct TaquinGame {
    empty_coord: [usize; 2],
    grid: [[u8; COLUMNS]; ROWS],
    rng: rand::rngs::ThreadRng,
    score: usize,
    high_score: usize,
}

impl TaquinGame {
    pub fn new() -> Self {
        return TaquinGame {
            empty_coord: [0, 0],
            grid: [[0; COLUMNS]; ROWS],
            rng: rand::rng(),
            score: 0,
            high_score: 0,
        };
    }

    pub fn score(&self) -> usize {
        return self.score;
    }

    pub fn grid(&self) -> [[u8; COLUMNS]; ROWS] {
        return self.grid;
    }

    pub fn high_score(&self) -> usize {
        return self.high_score;
    }

    pub fn move_to(&mut self, direction: Directions) -> bool {
        let (mut row, mut column) = (self.empty_coord[0], self.empty_coord[1]);
        let mut valid_direction = false;

        match direction {
            Directions::Down => {
                if row + 1 < ROWS {
                    row += 1;
                    valid_direction = true;
                }
            }
            Directions::Up => {
                if row > 0 {
                    row -= 1;
                    valid_direction = true;
                }
            }
            Directions::Right => {
                if column + 1 < COLUMNS {
                    column += 1;
                    valid_direction = true;
                }
            }
            Directions::Left => {
                if column > 0 {
                    column -= 1;
                    valid_direction = true;
                }
            }
        }

        if valid_direction {
            self.exchange(self.empty_coord[1], self.empty_coord[0], column, row);
            self.score += 1;
        }

        return valid_direction;
    }

    pub fn is_grid_done(&mut self) -> bool {
        let mut error_found = false;
        let mut value = 0;

        for i in 0..ROWS {
            if !error_found {
                for j in 0..COLUMNS {
                    if !error_found {
                        if self.grid[i][j] != value {
                            error_found = true;
                        }
                        value += 1;
                    } else {
                        break;
                    }
                }
            } else {
                break;
            }
        }

        if !error_found {
            if self.score > 0 && (self.high_score == 0 || self.score < self.high_score) {
                self.high_score = self.score;
            }
        }

        return !error_found;
    }

    pub fn init_grid(&mut self) {
        let move_nmbr = self.rng.random_range(400..500);
        let mut rnd_nmbr = [0u8, 0, 0, 0];

        self.resolve();

        for _ in 0..move_nmbr {
            loop {
                rnd_nmbr[3] = self.rng.random_range(0..4);
                if rnd_nmbr[0] != rnd_nmbr[3] {
                    break;
                }
            }

            rnd_nmbr = [rnd_nmbr[1], rnd_nmbr[2], rnd_nmbr[3], rnd_nmbr[3]];

            self.move_to(match rnd_nmbr[3] {
                0 => Directions::Up,
                1 => Directions::Down,
                2 => Directions::Left,
                3 => Directions::Right,
                _ => Directions::Up,
            });
        }

        self.score = 0;
    }

    fn exchange(&mut self, ax: usize, ay: usize, bx: usize, by: usize) {
        if ax < COLUMNS && ay < ROWS && bx < COLUMNS && by < ROWS {
            let temp = self.grid[by][bx];
            self.grid[by][bx] = self.grid[ay][ax];
            self.grid[ay][ax] = temp;
            self.empty_coord = [by, bx];
        }
    }

    pub fn resolve(&mut self) {
        let mut value = 0;
        self.score = 0;
        self.empty_coord = [0, 0];

        for i in 0..ROWS {
            for j in 0..COLUMNS {
                self.grid[i][j] = value;
                value += 1;
            }
        }
    }
}