extern crate rand;

use std::io;
use std::io::Write;

fn main() {

    use minesweeper::{MineSweeper, GameStatus};

    let mut width = 6;
    let mut height = 4;
    let mut mine_perctg = 10;
    let mut game = MineSweeper::new(width, height, mine_perctg);

    loop {
        let mut command = String::new();

        println!(
            "\n\
             M I N E S W E E P E R\n\
             \n\
             Commands: \n\
             line col            - reveal line,col \n\
             m line col          - mark   line,col \n\
             q                   - quit\n\
             n                   - new game\n\
             n width height perc - new game size and mine percentage\n"
        );

        game.print();
        print!("> ");
        io::stdout().flush().unwrap();
        while let Ok(_) = io::stdin().read_line(&mut command) {
            let mut command_ok = false;
            {
                let values: Vec<&str> = command.trim().split(' ').collect();
                if values.len() == 1 {
                    if values[0] == "q" {
                        println!("Goodbye");
                        return;
                    } else if values[0] == "n" {
                        println!("New game");
                        game = MineSweeper::new(width, height, mine_perctg);
                        command_ok = true;
                    }
                } else if values.len() == 2 {
                    if let (Ok(x), Ok(y)) = (
                        values[0].parse::<usize>(),
                        values[1].parse::<usize>(),
                    )
                    {
                        game.play(x - 1, y - 1);

                        match game.game_status {
                            GameStatus::Won => println!("You won!"),
                            GameStatus::Lost => println!("You lost!"),
                            _ => (),
                        }
                        command_ok = true;
                    }
                } else if values.len() == 3 {
                    if values[0] == "m" {
                        if let (Ok(x), Ok(y)) = (
                            values[1].parse::<usize>(),
                            values[2].parse::<usize>(),
                        )
                        {
                            game.mark(x - 1, y - 1);
                            command_ok = true;
                        }
                    }
                } else if values.len() == 4 {
                    if values[0] == "n" {
                        if let (Ok(new_width), Ok(new_height), Ok(new_mines_perctg)) =
                            (
                                values[1].parse::<usize>(),
                                values[2].parse::<usize>(),
                                values[3].parse::<usize>(),
                            )
                        {
                            width = new_width;
                            height = new_height;
                            mine_perctg = new_mines_perctg;
                            game = MineSweeper::new(width, height, mine_perctg);
                            command_ok = true;
                        }
                    }
                }
            }

            if command_ok {
                game.print();
            } else {
                println!("Invalid command");
            }

            print!("> ");
            io::stdout().flush().unwrap();
            command.clear();
        }
    }
}

pub mod minesweeper {

    pub struct MineSweeper {
        cell: [[Cell; 100]; 100],
        pub game_status: GameStatus,
        mines: usize,
        width: usize,
        height: usize,
        revealed_count: usize,
    }

    #[derive(Copy, Clone)]
    struct Cell {
        content: CellContent,
        mark: Mark,
        revealed: bool,
    }

    #[derive(Copy, Clone)]
    enum CellContent {
        Empty,
        Mine,
        MineNeighbour { count: u8 },
    }

    #[derive(Copy, Clone)]
    enum Mark {
        None,
        Mine,
    }

    pub enum GameStatus {
        InGame,
        Won,
        Lost,
    }

    extern crate rand;

    use std::cmp::max;
    use std::cmp::min;
    use self::rand::Rng;
    use self::CellContent::*;
    use self::GameStatus::*;

    impl MineSweeper {
        pub fn new(width: usize, height: usize, percentage_of_mines: usize) -> MineSweeper {
            let mut game = MineSweeper {
                cell: [[Cell {
                    content: Empty,
                    mark: Mark::None,
                    revealed: false,
                }; 100]; 100],
                game_status: InGame,
                mines: (width * height * percentage_of_mines) / 100,
                width: width,
                height: height,
                revealed_count: 0,
            };
            game.put_mines();
            game.calc_neighbours();
            game
        }

        pub fn play(&mut self, x: usize, y: usize) {
            match self.game_status {
                InGame => {
                    if!self.cell[x][y].revealed {
                        match self.cell[x][y].content {
                            Mine => {
                                self.cell[x][y].revealed = true;
                                self.revealed_count += 1;
                                self.game_status = Lost;
                            }
                            Empty => {
                                self.flood_fill_reveal(x, y);
                                if self.revealed_count + self.mines == self.width * self.height {
                                    self.game_status = Won;
                                }
                            }
                            MineNeighbour { .. } => {
                                self.cell[x][y].revealed = true;
                                self.revealed_count += 1;
                                if self.revealed_count + self.mines == self.width * self.height {
                                    self.game_status = Won;
                                }
                            }
                        }
                    }
                }
                _ => println!("Game has ended"),
            }
        }

        pub fn mark(&mut self, x: usize, y: usize) {
            self.cell[x][y].mark = match self.cell[x][y].mark {
                Mark::None => Mark::Mine,
                Mark::Mine => Mark::None,
            }
        }

        pub fn print(&self) {
            print!("");
            for _ in 0..self.width {
                print!("");
            }
            println!("");
            for y in 0..self.height {
                print!("");
                for x in 0..self.width {
                    self.cell[x][y].print();
                }
                println!("");
            }
            print!("");
            for _ in 0..self.width {
                print!("");
            }
            println!("");
        }

        fn put_mines(&mut self) {
            let mut rng = rand::thread_rng();
            for _ in 0..self.mines {
                while let (x, y, true) = (
                    rng.gen::<usize>()% self.width,
                    rng.gen::<usize>()% self.height,
                    true,
                )
                {
                    match self.cell[x][y].content {
                        Mine => continue,
                        _ => {
                            self.cell[x][y].content = Mine;
                            break;
                        }
                    }
                }
            }
        }

        fn calc_neighbours(&mut self) {
            for x in 0..self.width {
                for y in 0..self.height {
                    if!self.cell[x][y].is_bomb() {
                        let mut adjacent_bombs = 0;

                        for i in max(x as isize - 1, 0) as usize..min(x + 2, self.width) {
                            for j in max(y as isize - 1, 0) as usize..min(y + 2, self.height) {
                                adjacent_bombs += if self.cell[i][j].is_bomb() { 1 } else { 0 };
                            }
                        }

                        if adjacent_bombs == 0 {
                            self.cell[x][y].content = Empty;
                        } else {
                            self.cell[x][y].content = MineNeighbour { count: adjacent_bombs };
                        }
                    }
                }
            }
        }

        fn flood_fill_reveal(&mut self, x: usize, y: usize) {
            let mut stack = Vec::<(usize, usize)>::new();
            stack.push((x, y));

            while let Some((i, j)) = stack.pop() {
                if self.cell[i][j].revealed {
                    continue;
                }
                self.cell[i][j].revealed = true;
                self.revealed_count += 1;
                if let Empty = self.cell[i][j].content {
                    for m in max(i as isize - 1, 0) as usize..min(i + 2, self.width) {
                        for n in max(j as isize - 1, 0) as usize..min(j + 2, self.height) {
                            if!self.cell[m][n].is_bomb() &&!self.cell[m][n].revealed {
                                stack.push((m, n));
                            }
                        }
                    }
                }
            }
        }
    }

    impl Cell {
        pub fn print(&self) {
            print!(
                "{}",
                if self.revealed {
                    match self.content {
                        Empty => ' ',
                        Mine => '*',
                        MineNeighbour { count } => char::from(count + b'0'),
                    }
                } else {
                    match self.mark {
                        Mark::Mine => '?',
                        Mark::None => '.',
                    }
                }
            );
        }

        pub fn is_bomb(&self) -> bool {
            match self.content {
                Mine => true,
                _ => false,
            }
        }
    }
}