use std::ops;
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use rand::{Rng, thread_rng};

/// Our indices here are two 32-bit signed values.
type Index = (i32, i32);
/// A `SparseBoard` here is a board that may or may not have every
/// tile filled in, which we represent as a hashmap from indices to
/// cells. When we use a `SparseBoard` as the LHS of a rule, we try to
/// match every cell provided, but don't bother matching the cells
/// which aren't provided; when we use one as the RHS of a rule, we
/// instead overwrite only those cells provided in the board.
///
/// A `SparseBoard` has a conceptual center, which is the cell at
/// index `(0, 0)`. Indices in a `SparseBoard` can be negative.
type SparseBoard<Cell> = HashMap<Index, Cell>;

/// The most important parts of a `Rule` are its LHS and its
/// RHS. These correspond respectively to the patterns which are
/// matched, and the cells that are used to replace those
/// patterns. Note that both of these are sparse boards: a pattern may
/// be an arbitrary shape, and its replacement may replace all, some,
/// or none of the cells matched by the LHS.
pub struct Rule<Cell> {
    pub rule_name: Option<String>,
    pub requires: HashSet<Cell>,
    pub lhs: SparseBoard<Cell>,
    pub rhs: SparseBoard<Cell>,
}

pub struct Board<Cell> {
    pub width: u32,
    pub height: u32,
    pub cells: Vec<Cell>,
    pub contents: HashSet<Cell>,
}

impl<Cell: Clone + Hash + Eq> Board<Cell> {
    pub fn new(width: u32, height: u32, default: Cell) -> Board<Cell> {
        let cells = (0..width*height).map(|_| default.clone()).collect();
        let contents = vec![default].into_iter().collect();
        Board { width, height, cells, contents }
    }

    pub fn get<'a>(&'a self, (w, h): Index) -> Option<&'a Cell> {
        if w > 0 && w < self.width as i32 && h > 0 && h < self.height as i32 {
            Some(&self[(w, h)])
        } else {
            None
        }
    }

    pub fn indices(&self) -> Vec<Index> {
        let mut v = Vec::new();
        for x in 0..self.width {
            for y in 0..self.height {
                v.push((x as i32, y as i32))
            }
        }
        v
    }

    pub fn random_indices(&self) -> Vec<Index> {
        let mut v = self.indices();
        {
            let slice = v.as_mut_slice();
            thread_rng().shuffle(slice);
        }
        v
    }
}

impl Board<char> {
    pub fn print(&self) {
        for x in 0..self.width {
            for y in 0..self.height {
                print!("{} ", self[(x as i32, y as i32)]);
            }
            println!("");
        }
    }
}

impl<Cell> ops::Index<Index> for Board<Cell> {
    type Output = Cell;

    fn index(&self, (w, h): Index) -> &Cell {
        let n: usize = (w + (h * self.width as i32)) as usize;
        &self.cells[n as usize]
    }
}

impl<Cell> ops::IndexMut<Index> for Board<Cell> {
    fn index_mut(&mut self, (w, h): Index) -> &mut Cell {
        let n: usize = (w + (h * self.width as i32)) as usize;
        &mut self.cells[n as usize]
    }
}

impl<Cell: Clone + Hash + Eq> Rule<Cell> {
    /// Create a new `Rule` from two sparse grids of cells,
    /// corresponding respectively to the LHS and the RHS
    pub fn new(
        lhs: SparseBoard<Cell>,
        rhs: SparseBoard<Cell>,
    ) -> Rule<Cell> {
        let rule_name = None;
        let requires = lhs.values().cloned().collect();
        Rule { rule_name, requires, lhs, rhs }
    }

    /// Get mutable access to the LHS of the `Rule`, for modification
    /// later
    pub fn lhs_mut(&mut self) -> &mut SparseBoard<Cell> {
        &mut self.lhs
    }

    /// Get mutable access to the RHS of the `Rule`, for modification
    /// later
    pub fn rhs_mut(&mut self) -> &mut SparseBoard<Cell> {
        &mut self.lhs
    }

    /// Attempt to apply this rule to the provided board at random
    /// (i.e. if there are multiple possible applications of this
    /// rule, then it should choose one of them entirely at random
    pub fn apply_to_board(&self, b: &mut Board<Cell>) -> bool {
        // for each random location in the board
        'outer: for (x, y) in b.random_indices() {
            // find out whether each of our tiles matche
            for (&(i, j), v) in self.lhs.iter() {
                // if it doesn't, then skip ahead
                match b.get((x + i, y + j)) {
                    Some(r) if v == r => (),
                    _ => continue 'outer,
                }
            }

            // if all of them match, then do the rewrites!
            for (&(i, j), r) in self.rhs.iter() {
                b[(x + i, y + j)] = r.clone();
            }

            // and because the rule applied, we can quit now
            return true;
        }
        // if we've tried them all and none of them worked, then we've
        // failed
        false
    }
}


#[cfg(test)]
#[test]
pub fn grammar_test() {
    let rule1: Rule<char> = Rule::new(
        vec![ ((0, 0), 'x') ].into_iter().collect(),
        vec![ ((0, 0), 'y'), ((1, 0), 'y') ].into_iter().collect(),
    );
    let rule2: Rule<char> = Rule::new(
        vec![ ((0, 0), 'y') ].into_iter().collect(),
        vec![ ((0, 0), 'z') ].into_iter().collect(),
    );
    let mut board = Board::new(8, 8, '.');
    board[(2, 2)] = 'x';
    assert!(true, rule1.apply_to_board(&mut board));
    assert!(true, rule2.apply_to_board(&mut board));
    board.print();
}
