use bincode;
use rand::prelude::*;
use std::collections::VecDeque;

use super::consts::*;
use super::level_query::*;
use super::row::*;
use super::skill::*;
use super::slab_def::*;
use super::slab_kind::*;

/// Contains the level definition, where are slabs, and of what type.
pub struct Level {
    seed: u64,
    rows: VecDeque<Row>,
    row0: isize,
    w: usize,
    skill: Skill,
    now_msec: i64,
}

impl Level {
    /// Create a new level, with a given size
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let _level = Level::new(0, 10, Skill::Easy);
    /// ```
    pub fn new(seed: u64, width: usize, skill: Skill) -> Level {
        let w = if width > WIDTH_MAX as usize {
            WIDTH_MAX
        } else {
            width
        };
        Level {
            seed,
            rows: VecDeque::with_capacity(2 * SLIDING_LENGTH_MAX),
            row0: 0,
            w,
            skill,
            now_msec: 0,
        }
    }

    /// Push a row at the end of the level.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let mut level = Level::new(0, 10, Skill::Easy);
    /// level.push(Row::new(SlabDef::Floor));
    /// assert_eq!(1, level.len());
    /// ```
    pub fn push(&mut self, row: Row) {
        if self.rows.len() >= ABSOLUTE_LENGTH_MAX {
            return;
        }
        self.rows.push_back(row);
        while self.rows.len() > SLIDING_LENGTH_MAX {
            self.row0 += 1;
            self.rows.pop_front();
        }
    }

    /// Generate new rows and append them to the level.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let mut level = Level::new(0, 10, Skill::Easy);
    /// level.generate(2);
    /// assert_eq!(2, level.len());
    /// level.generate(3);
    /// assert_eq!(5, level.len());
    /// ```
    pub fn generate(&mut self, n: usize) {
        if self.rows.len() >= ABSOLUTE_LENGTH_MAX {
            return;
        }
        for _ in 0..n {
            let row = self.todo_make_this_a_real_generator(
                self.seed,
                self.rows.len(),
                self.w,
                self.skill,
            );
            self.push(row)
        }
    }

    fn todo_make_this_a_real_generator(
        &self,
        seed: u64,
        row_index: usize,
        width: usize,
        skill: Skill,
    ) -> Row {
        let rng_seed = build_rng_seed(seed, row_index);
        let rng: StdRng = SeedableRng::from_seed(rng_seed);
        let density = skill.density();
        Row::from_def_alpha(rng, SlabDef::Floor, width, density)
    }

    /// Get a level content, at a given time.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let mut level = Level::new(0, 10, Skill::Easy);
    /// level.generate(2);
    /// assert_eq!(SlabKind::Floor, level.slab_kind(5, 1));
    /// ```
    pub fn slab_kind(&self, col: isize, row: isize) -> SlabKind {
        let real_row = row + self.row0;
        if col < 0 || col >= WIDTH_MAX as isize {
            return SlabKind::Void;
        }
        if real_row < 0 || real_row >= self.len() as isize {
            return SlabKind::Void;
        }
        return self.rows[real_row as usize].slab_kind(col, self.now_msec);
    }

    pub fn set_now_msec(&mut self, now_msec: i64) {
        self.now_msec = now_msec;
    }

    pub fn set_now_sec(&mut self, now_sec: f64) {
        self.now_msec = (now_sec * 1000.0) as i64;
    }
}

impl LevelQuery for Level {
    /// Get the level width. It is fixed for a given game session.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let level = Level::new(0, 10, Skill::Easy);
    /// assert_eq!(10, level.width());
    /// ```
    fn width(&self) -> usize {
        self.w
    }

    /// Get the level length. Length typically increases as
    /// the ball rolls on. However it is capped at some point
    /// and history at the beginning of the level is removed.
    /// View this as a sliding window.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let level = Level::new(0, 10, Skill::Easy);
    /// assert_eq!(0, level.len());
    /// ```
    fn len(&self) -> usize {
        self.rows.len()
    }

    /// Get the level first row, the row with the lowest index.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let level = Level::new(0, 10, Skill::Easy);
    /// assert_eq!(0, level.first());
    /// ```
    fn first(&self) -> isize {
        self.row0
    }

    /// Get the level last row, more precisely its index plus one.
    /// So if last is equal to first, then len is 0 an there are no rows at all.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let level = Level::new(0, 10, Skill::Easy);
    /// assert_eq!(0, level.last());
    /// ```
    fn last(&self) -> isize {
        self.row0 + self.rows.len() as isize
    }

    /// Get a level content, returns a plain integer.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let mut level = Level::new(0, 10, Skill::Easy);
    /// level.generate(2);
    /// assert_eq!(0, level.item(5, 1));
    /// ```
    fn item(&self, col: isize, row: isize) -> i64 {
        self.slab_kind(col, self.row0 + row).as_item()
    }

    /// Find a starting spot on a level.
    ///
    /// # Examples
    ///
    /// ```
    /// use babalcore::*;
    ///
    /// let mut level = Level::new(42, 10, Skill::Easy);
    /// assert_eq!(None, level.find_start_spot(2, 1));
    /// level.generate(100);
    /// assert_eq!(Some((2,1)), level.find_start_spot(2, 1));
    /// ```
    fn find_start_spot(&self, col: isize, row: isize) -> Option<(isize, isize)> {
        let r = std::cmp::max(self.first(), std::cmp::min(self.last() - 1, row));
        let c = std::cmp::max(0, std::cmp::min(self.width() as isize - 1, col));

        // Check if the curent spot is OK, if yes, use this.
        if self.item(c, r) >= 0 {
            return Some((c, r));
        }

        // Ok, it did not work out, find another one by walking
        // all the level down to first row.
        let mut r2 = r;
        let width = self.width() as isize;
        while r2 >= self.first() {
            for c2 in 0..width {
                let c3 = (c + c2) % width;
                if self.item(c3, r2) >= 0 {
                    return Some((c3, r2));
                }
            }
            r2 -= 1;
        }
        None
    }
}

fn build_rng_seed(seed: u64, row_index: usize) -> [u8; 32] {
    let mut buf: [u8; 32] = [0; 32];
    let buf1 = bincode::serialize(&seed).unwrap();
    let mut i: usize = 0;
    for v in buf1.into_iter() {
        buf[i] = v;
        i += 1;
    }
    let buf2 = bincode::serialize(&row_index).unwrap();
    for v in buf2.into_iter() {
        buf[i] = v;
        i += 1;
    }
    buf
}

impl std::fmt::Display for Level {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let str_list: Vec<String> = self
            .rows
            .iter()
            .map(|x| format!("{}", x)[0..self.w].to_string())
            .collect();
        write!(f, "{}", str_list.join("\n"))
    }
}

#[cfg(test)]
mod tests {
    use crate::*;
    use std::collections::HashMap;

    #[test]
    fn test_fmt() {
        assert_eq!("", format!("{}", Level::new(0, 10, Skill::Easy)));
        let mut lvl = Level::new(0, 5, Skill::Noob);
        lvl.generate(3);
        assert_eq!("#####\n#####\n#####", format!("{}", lvl));
    }

    #[test]
    fn test_sliding_length_max() {
        let mut lvl = Level::new(0, 2, Skill::Noob);
        assert_eq!(0, lvl.len());
        let row = Row::new(SlabDef::Floor);
        lvl.generate(SLIDING_LENGTH_MAX - 1);
        assert_eq!(SLIDING_LENGTH_MAX - 1, lvl.len());
        lvl.generate(2);
        assert_eq!(SLIDING_LENGTH_MAX, lvl.len());
        assert_eq!(1, lvl.first());
        assert_eq!((SLIDING_LENGTH_MAX + 1) as isize, lvl.last());
        lvl.push(row);
        assert_eq!(SLIDING_LENGTH_MAX, lvl.len());
        assert_eq!(2, lvl.first());
        assert_eq!((SLIDING_LENGTH_MAX + 2) as isize, lvl.last());
    }

    #[test]
    fn test_generate() {
        const W: usize = 6;
        const L: usize = 5;
        let mut expected = HashMap::<u64, &str>::new();
        expected.insert(0, "### ##\n### ##\n #   #\n ##   \n#   ##");
        expected.insert(123, "#   # \n ###  \n######\n#### #\n## ###");

        for (key, value) in expected.into_iter() {
            let mut lvl = Level::new(key, W, Skill::Medium);
            lvl.generate(L);
            assert_eq!(L, lvl.len());
            assert_eq!(value, format!("{}", lvl));
        }
    }
}
