use rand::Rng;
use std::time::{SystemTime, UNIX_EPOCH};

const DIRECTION_DIST: phf::Map<char, [i32; 2]> = phf::phf_map! {
    'U' => [-1, 0],
    'D' => [1, 0],
    'L' => [0, -1],
    'R' => [0, 1]
};

const DIRECTION_LIST: [char; 4] = ['U', 'D', 'L', 'R'];


pub struct Puzzle {
    pub cmds_str: String,
    pub mode: usize,
    pub puzzle: Vec<Vec<i32>>,
    correct_puzzle: Vec<Vec<i32>>,
    pub start_time: u128,
    pub end_time: u128,
}

impl Puzzle {
    pub fn new(mode: usize) -> Self {
        let mut puzzle = vec![vec![0; mode]; mode];
        let mut correct_puzzle = vec![vec![0; mode]; mode];
        let mut num = 1;

        for i in 0..mode {
            for j in 0..mode {
                puzzle[i][j] = num;
                correct_puzzle[i][j] = num;
                num += 1;
            }
        }

        puzzle[mode - 1][mode - 1] = 0;
        correct_puzzle[mode - 1][mode - 1] = 0;

        let start_time = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_millis();

        let mut instance = Puzzle {
            cmds_str: String::new(),
            mode,
            puzzle,
            correct_puzzle,
            start_time,
            end_time: 0,
        };

        instance.shuffle();
        instance
    }

    fn find_0(&self) -> Option<(usize, usize)> {
        for (i, row) in self.puzzle.iter().enumerate() {
            for (j, &value) in row.iter().enumerate() {
                if value == 0 {
                    return Some((i, j));
                }
            }
        }
        None
    }

    pub fn move_tile(&mut self, direction: char) -> Option<char> {
        self.cmds_str.push(direction);
        if let Some((r, c)) = self.find_0() {
            if (r == 0 && direction == 'U')
                || (r == self.mode - 1 && direction == 'D')
                || (c == 0 && direction == 'L')
                || (c == self.mode - 1 && direction == 'R')
            {
                return None
            }

            if let Some(&[dr, dc]) = DIRECTION_DIST.get(&direction) {
                let rr = (r as i32 + dr) as usize;
                let cc = (c as i32 + dc) as usize;

                let num1 = self.puzzle[rr][cc];
                self.puzzle[r][c] = num1;
                self.puzzle[rr][cc] = 0;
                return Some(direction)
            }
        }
        return None
    }

    pub fn check(&mut self) -> bool {
        for i in 0..self.mode {
            for j in 0..self.mode {
                if self.puzzle[i][j] != self.correct_puzzle[i][j] {
                    return false;
                }
            }
        }
        self.end_time = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_millis();
        true
    }

    pub fn move_sequence(&mut self, sequence: &str) -> bool {
        let uppercase = sequence.to_uppercase();
        self.cmds_str.clear();
        for command in uppercase.chars() {
            self.move_tile(command);
            if self.check() {
                return true;
            }
        }
        false
    }

    pub fn shuffle(&mut self) {
        let mut rng = rand::thread_rng();
        for _ in 0..1000 {
            let random_direction = DIRECTION_LIST[rng.gen_range(0..4)];
            self.move_tile(random_direction);
        }
    }

    pub fn duration(&self) -> String {
        let time_now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_millis();
        let duration = time_now - self.start_time;
        self.format_duration(duration)
    }

    fn format_duration(&self, duration: u128) -> String {
        let hours = duration / 3600000;
        let minutes = (duration % 3600000) / 60000;
        let seconds = (duration % 60000) / 1000;
        format!("{}:{}:{}", hours, minutes, seconds)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let puzzle = Puzzle::new(4);
        for i in 0..4{
            println!("{:?}", puzzle.puzzle[i]);
        }
        println!();
        assert_eq!(puzzle.puzzle.len(), 4);
    }

    #[test]
    fn it_works_mode3() {
        let mut puzzle = Puzzle::new(3);
        for i in 0..3{
            println!("{:?}", puzzle.puzzle[i]);
        }
        println!();
        puzzle.move_tile('U');
        for i in 0..3{
            println!("{:?}", puzzle.puzzle[i]);
        }
        println!();
        assert_eq!(puzzle.puzzle.len(), 3);
    }
}
