#[cfg(not(feature = "advanced_threading"))]
use std::thread;
#[cfg(feature = "advanced_threading")]
use rayon::prelude::*;

#[cfg(feature = "advanced_hashing")]
type Board = rustc_hash::FxHashSet<Coord>;
#[cfg(not(feature = "advanced_hashing"))]
type Board = std::collections::HashSet<Coord>;

pub type Coord = (isize, isize);

/// An error in the game's logic
#[derive(Debug)]
pub enum GameError {
    NoPreviousTurn
}

/// A representation of the game's state. 
#[derive(Default)]
pub struct Game {
    primary_board: Board,
    initial_state: Board,
    count: usize,
    #[cfg(not(feature = "advanced_threading"))]
    parallelism: usize,
}

impl Game {
    /// Constructs a new board with all dead cells 
    pub fn new() -> Self {
        Self {
            primary_board: Board::default(),
            initial_state: Board::default(),
            count: 0,
            #[cfg(not(feature = "advanced_threading"))]
            parallelism: std::thread::available_parallelism().unwrap().into(),
        }
    }

    /// Returns whether the cell at the given coordinate is alive
    pub fn get_state(&self, coord: Coord) -> bool {
        self.primary_board.contains(&coord)
    }

    /// Gets all live cells on the board
    pub fn cells(&self) -> Vec<Coord> {
        self.primary_board.iter().cloned().collect()
    }

    /// Flips the cell at the given coordinate betweeen alive and dead. Will return whether the new
    /// cell is alive
    pub fn flip_state(&mut self, coord: Coord) -> bool {
        self.count = 0;
        if self.primary_board.contains(&coord) {
            self.primary_board.remove(&coord);
            self.initial_state = self.primary_board.clone();
            false
        } else {
            self.primary_board.insert(coord);
            self.initial_state = self.primary_board.clone();
            true
        }
    }

    /// Advances the board by one turn
    pub fn advance_board(&mut self) {
        // Get all the cells to check
        let mut check_set = Board::default();
        for (x, y) in &self.primary_board {
            let x = x.clone();
            let y = y.clone();
            check_set.insert((x, y));
            check_set.insert((x, y+1));
            check_set.insert((x, y-1));
            check_set.insert((x+1, y));
            check_set.insert((x+1, y+1));
            check_set.insert((x+1, y-1));
            check_set.insert((x-1, y));
            check_set.insert((x-1, y+1));
            check_set.insert((x-1, y-1));
        }

        // Iterate through the cells and modify the other HashSet
        //let old_board = std::mem::take(&mut self.secondary_board);
        let mut new_board = Board::default();
        #[cfg(not(feature = "advanced_threading"))]
        {
            let binding = check_set.iter().collect::<Vec<&Coord>>();
            let chunks = binding.chunks((binding.len()/self.parallelism)+1);
            thread::scope(|s| {
                let mut handles = vec![];
                for chunk in chunks {
                    let handle = s.spawn(|| {
                        let owned_chunk = chunk.iter().map(|x| x.to_owned().to_owned());
                        let mut return_set = Board::default();
                        for coord in owned_chunk {
                            if get_next_state(&self.primary_board, coord) {
                                return_set.insert(coord);
                            }
                        }
                        return_set
                    });
                    handles.push(handle);
                }
                for handle in handles {
                    new_board.extend(handle.join().unwrap());
                }
            });
        }

        #[cfg(feature = "advanced_threading")]
        {
            new_board.par_extend(check_set.par_iter().filter(|coord| get_next_state(&self.primary_board, coord.to_owned().to_owned())));
        }

        // Swap the HashSets
        self.primary_board = new_board;
        self.count += 1;
    }



    /// Returns a vector of the values between the given top-left and bottom-right
    /// coordinate
    pub fn get_view(&self, coord1: Coord, coord2: Coord) -> Vec<&Coord>{
        self.primary_board.iter()
            .filter(|(x, y)| x >= &coord1.0 && y >= &coord1.1 && x <= &coord2.0 && y <= &coord2.1)
            .collect()

    }

    /// Sets the board to its previous state. The board's history will only go back to the last
    /// manual intervention done by a player (such as loading a file or toggling a cell). Returns
    /// true if it reverted, and false if there was no previous revert. 
    pub fn revert_board(&mut self) -> bool {
        // Check if there have been any turns since the initial state
        if self.count == 0 {
            return false;
        }
        self.primary_board = self.initial_state.clone();
        // Check if we can simply swap out the old board. 
        /*if self.easy_buffer_revert {
          std::mem::swap(&mut self.primary_board, &mut self.secondary_board);
          self.easy_buffer_revert = false;
          return;
          }*/
        let count = self.count;
        for _ in 0..count-1 {
            self.advance_board();
        }
        // let set = self.into_iter().nth(count).unwrap();
        //self.primary_board = set;
        self.count = count - 1;
        true
    }
}

impl Iterator for Game {
    type Item = Board;
    fn next(&mut self) -> Option<Self::Item> {
        self.advance_board();
        Some(self.primary_board.clone())
    }
}

/// Given a board and a coordinate, return whether the cell at that coordinate should be live next
/// round
fn get_next_state(board: &Board, coord: Coord) -> bool {
    let current_state = board.contains(&coord);
    let (x, y) = coord;
    let mut count: u8 = 0;
    for coord in [(x, y+1), (x, y-1), (x+1, y), (x+1, y+1), (x+1, y-1), (x-1, y), (x-1, y+1), (x-1, y-1)] {
        if board.contains(&coord) {
            count += 1;
        }
    }
    if current_state {
        (2 <= count) && (count <= 3)
    } else {
        count == 3
    }
}
