use crate::random::RandomSystem;
use crate::tetromino::{Position, Tetromino, TetrominoType};
use std::collections::VecDeque;
use std::fmt;

pub const PLAYFIELD_HEIGHT: usize = 20;
pub const PLAYFIELD_WIDTH: usize = 10;

pub type Matrix = Vec<Vec<Option<TetrominoType>>>;

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Movement {
    Rotation,
    Gravity,
    Translation,
}

#[derive(Clone)]
pub struct PlayField {
    can_swap_hold: bool,
    hold_piece: Option<TetrominoType>,
    field: Matrix,
    pub active_piece: Option<Tetromino>,
    bag: RandomSystem,
    next_pieces: VecDeque<TetrominoType>,
    last_movement: Movement,
}

impl fmt::Debug for PlayField {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.active_piece {
            Some(active_piece) => {
                writeln!(
                    f,
                    "current piece: {:?} @ {}, {}",
                    active_piece.piece_type, active_piece.position.x, active_piece.position.y
                )?;
            }
            None => (),
        }

        writeln!(f, "next pieces: {:?}", self.next_pieces)?;
        let occupied_spaces = self
            .active_piece
            .and_then(|t| Some(t.get_cur_occupied_spaces()))
            .unwrap_or_default();
        for y in PLAYFIELD_HEIGHT..self.field.len() {
            write!(
                f,
                "{} │",
                ('a' as usize - PLAYFIELD_HEIGHT + y) as u8 as char
            )?;
            for x in 0..PLAYFIELD_WIDTH {
                if occupied_spaces.contains(&Position::new(x as isize, y as isize)) {
                    write!(f, "#")?;
                } else {
                    match self.field[y][x] {
                        Some(t) => write!(f, "{:?}", t)?,
                        None => write!(f, " ")?,
                    }
                }
            }
            writeln!(f, "│")?;
        }
        writeln!(f, "  └{}┘", "─".repeat(PLAYFIELD_WIDTH))?;
        write!(
            f,
            "   {}",
            (0..PLAYFIELD_WIDTH)
                .map(|e| e.to_string())
                .collect::<Vec<_>>()
                .join("")
        )?;
        Ok(())
    }
}

impl PlayField {
    pub fn new() -> Self {
        let mut bag = RandomSystem::new();
        let active_piece = Tetromino::from(bag.get_tetromino());
        let mut next_pieces = VecDeque::with_capacity(3);
        for _ in 0..next_pieces.capacity() {
            next_pieces.push_back(bag.get_tetromino());
        }

        let row = [None; PLAYFIELD_WIDTH].to_vec();
        let mut field = Vec::with_capacity(2 * PLAYFIELD_HEIGHT);
        for _ in 0..2 * PLAYFIELD_HEIGHT {
            field.push(row.clone());
        }
        PlayField {
            can_swap_hold: true,
            hold_piece: None,
            field,
            active_piece: Some(active_piece),
            bag,
            next_pieces,
            last_movement: Movement::Gravity,
        }
    }

    pub fn move_offset(&mut self, x: isize, y: isize) -> bool {
        if self.can_move_offset(x, y) {
            match self.active_piece {
                Some(mut piece) => {
                    piece.position.x += x;
                    piece.position.y += y;
                    self.active_piece = Some(piece);
                    true
                }
                None => panic!("Active piece missing!"),
            }
        } else {
            false
        }
    }

    fn can_move_offset(&self, x: isize, y: isize) -> bool {
        match self.active_piece {
            Some(piece) => piece
                .get_occupied_spaces(piece.position.offset(x, y))
                .iter()
                .fold(true, |acc, pos| {
                    acc && (pos.y as usize) < self.field.len()
                        && (pos.x as usize) < PLAYFIELD_WIDTH
                        && self.field[pos.y as usize][pos.x as usize].is_none()
                }),
            None => false,
        }
    }

    pub fn spawn_tetromino(&mut self) {
        self.active_piece = Some(Tetromino::from(
            self.next_pieces
                .pop_front()
                .expect("visible queue to be populated"),
        ));
        self.next_pieces.push_back(self.bag.get_tetromino());
        self.can_swap_hold = true;
        self.tick_gravity();
    }

    pub fn tick_gravity(&mut self) {
        match &self.active_piece {
            Some(mut active_piece) if self.can_active_piece_move_down() => {
                active_piece.position.y += 1;
                self.active_piece = Some(active_piece);
            }
            _ => (),
        }
    }

    pub fn can_active_piece_move_down(&self) -> bool {
        self.active_piece
            .expect("Tried to request the status of a non-existing active piece!")
            .get_falling_occupied_spaces()
            .iter()
            .fold(true, |acc, pos| {
                acc && (pos.y as usize) < self.field.len()
                    && self.field[pos.y as usize][pos.x as usize].is_none()
            })
    }

    pub fn lock_active_piece(&mut self) -> Vec<Position> {
        match &self.active_piece {
            Some(active_piece) => {
                let new_pieces = active_piece.get_cur_occupied_spaces();
                for Position { x, y } in &new_pieces {
                    self.field[*y as usize][*x as usize] = Some(active_piece.piece_type);
                }

                new_pieces
            }
            None => panic!("Tried to lock a piece that wasn't active"),
        }
    }

    pub fn is_active_piece_in_valid_position(&self) -> Option<Position> {
        self.active_piece
            .and_then(|t| self.can_piece_be_at_position(&t))
    }

    pub fn can_piece_be_at_position(&self, tetromino: &Tetromino) -> Option<Position> {
        for Position { x, y } in tetromino.get_cur_occupied_spaces() {
            if (y as usize) >= self.field.len()
                || (x as usize) >= PLAYFIELD_WIDTH
                || self.field[y as usize][x as usize].is_some()
            {
                return Some(Position { x, y });
            }
        }
        None
    }

    pub fn try_swap_hold(&mut self) -> Result<(), ()> {
        if self.can_swap_hold {
            match self.active_piece {
                Some(piece) => {
                    match self.hold_piece {
                        Some(hold) => self.next_pieces.push_front(hold),
                        None => (),
                    }
                    self.hold_piece = Some(piece.piece_type);
                    self.spawn_tetromino();
                    self.can_swap_hold = false;
                }
                None => return Err(()),
            }
        }

        Err(())
    }

    pub fn can_swap(&self) -> bool {
        self.can_swap_hold
    }

    pub fn try_clear_row(&mut self, row: usize) -> Result<(), ()> {
        if self.field[row].iter().all(|cell| cell.is_some()) {
            self.field[row] = [None; PLAYFIELD_WIDTH].to_vec();
            for y in (1..=row).rev() {
                self.field[y] = self.field[y - 1].clone();
            }
            Ok(())
        } else {
            Err(())
        }
    }

    pub fn field(&self) -> &Matrix {
        &self.field
    }

    pub fn hold_piece(&self) -> Option<&TetrominoType> {
        self.hold_piece.as_ref()
    }
}
