use std::{fmt};

#[derive(Clone, Copy, PartialEq)]
pub enum Terrain {
    Plain,
    Tree,
}

impl Default for Terrain {
    fn default() -> Self {
        Terrain::Plain
    }
}

impl fmt::Debug for Terrain {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", match self {
            Terrain::Plain => '.',
            Terrain::Tree => '#',
        })
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Default)]
pub struct Vec2 {
    pub x: i64,
    pub y: i64,
}

impl From<(i64, i64)> for Vec2 {
    fn from((x,y): (i64, i64)) -> Self {
        Self { x, y }
    }
}

#[derive(Clone)]
pub struct Map {
    size: Vec2,
    tiles: Vec<Terrain>
}

impl fmt::Debug for Map {
    fn fmt(&self,f: &mut fmt::Formatter) -> fmt::Result {
        // A most honourable moment, but chaining debugs means lots of spare ""[]
        //let dd: Vec<String> = self.tiles.chunks_exact(self.size.x as usize).map(|x| format!("{:?}", x)).collect();
        for line in self.tiles.chunks_exact(self.size.x as usize) {
            let line_s: String = line.iter().map(|x| format!("{:?}", x)).collect();
            writeln!(f, "{}", line_s)?
        }
        // let banana = self.tiles
        //     .chunks_exact(self.size.x as usize)
        //     .map(|x| format!("{:?}", x))
        //     .collect::<Vec<String>>()
        //     .join("\n");
        // writeln!(f, "{}", banana)
        Ok(())
    }
}

impl Map {
    pub fn new(size: Vec2) -> Self {
        Self {
            size,
            tiles: vec![Default::default();(size.x*size.y) as usize]
        }
    }

    pub fn get(&self, pos: Vec2) -> Terrain {
        self.tiles[self.idx(pos).unwrap_or_default()]
    }

    pub fn set(&mut self, pos: Vec2, tile: Terrain) {
        if let Some(idx) = self.idx(pos) {
            self.tiles[idx] = tile;
        }
    }

    fn wrap_coords(&self, pos: Vec2) -> Option<Vec2> {
        if !(0..=self.size.y).contains(&pos.y) {
            None
        } else {
            Some(Vec2{
                x: pos.x % self.size.x,
                y: pos.y})
        }
    }

    fn idx(&self, pos: Vec2) -> Option<usize> {
        self.wrap_coords(pos).map(|pos| (pos.x + pos.y * self.size.x) as usize)
    }

    pub fn from_bytes(s: &[u8]) -> Self {
        let rows = s.iter().filter(|&&x| x == b'\n').count();
        let cols = (s.len() - rows) / rows;
        println!("Size : {} {}", rows, cols);
        let mut res = Self::new((cols as i64,rows as i64).into());

        let mut pos = Vec2 { x: 0, y: 0 };
        for tile in s {
            match tile {
                b'\n' => {
                    pos.y += 1;
                    pos.x = 0;
                }
                b'#' => {
                    res.set(pos, Terrain::Tree);
                    pos.x += 1;
                }
                _ => {
                    res.set(pos, Terrain::Plain);
                    pos.x += 1;
                }
            }
        }
        res
    }

    pub fn solve(&self, slope: (i64, i64)) -> i64 {
        let mut seek_pos = Vec2 { x: 0, y: 0};
        let mut tree_count = 0;
        loop {
            if self.get(seek_pos) == Terrain::Tree {
                tree_count += 1;
            }
            if seek_pos.y + slope.1 >= self.size.y {
                break;
            }
            seek_pos.y += slope.1;
            seek_pos.x += slope.0;
        } 
        tree_count
    }
}