use crate::playfield::PlayField;
use crate::tetromino::{Position, RotationState, TetrominoType};
use std::collections::HashMap;

pub trait RotationSystem {
    fn default() -> Self;

    fn rotate_left(&self, playfield: &PlayField) -> Result<Position, ()>;

    fn rotate_right(&self, playfield: &PlayField) -> Result<Position, ()>;
}

#[derive(Clone)]
pub struct SRS {
    jlstz_offset_data: HashMap<RotationState, Vec<Position>>,
    i_offset_data: HashMap<RotationState, Vec<Position>>,
    o_offset_data: HashMap<RotationState, Vec<Position>>,
}

enum RotationDirection {
    Left,
    Right,
}

impl SRS {
    fn rotate_common(
        &self,
        playfield: &PlayField,
        rotation: RotationDirection,
    ) -> Result<Position, ()> {
        let active_piece = match playfield.active_piece {
            Some(piece) => piece,
            None => return Err(()),
        };
        let offset_data = match active_piece.piece_type {
            TetrominoType::I => &self.i_offset_data,
            TetrominoType::O => &self.o_offset_data,
            _ => &self.jlstz_offset_data,
        };
        let prev_offsets = offset_data.get(&active_piece.rotation_state).unwrap();

        let mut test_tetromino = active_piece.clone();
        match rotation {
            RotationDirection::Left => test_tetromino.rotate_left(),
            RotationDirection::Right => test_tetromino.rotate_right(),
        }

        let cur_offsets = offset_data.get(&test_tetromino.rotation_state).unwrap();
        let mut offsets = Vec::with_capacity(cur_offsets.len());

        for i in 0..cur_offsets.len() {
            offsets.push(prev_offsets[i] - cur_offsets[i]);
        }

        for offset in offsets {
            let x = offset.x;
            let y = offset.y;
            let test_position = active_piece.position.offset(x, y);
            test_tetromino.position = test_position;
            if playfield
                .can_piece_be_at_position(&test_tetromino)
                .is_none()
            {
                return Ok(offset);
            }
        }

        Err(())
    }
}

impl RotationSystem for SRS {
    fn default() -> Self {
        let mut jlstz_offset_data = HashMap::with_capacity(4);
        let mut i_offset_data = HashMap::with_capacity(4);
        let mut o_offset_data = HashMap::with_capacity(4);

        jlstz_offset_data.insert(RotationState::O, vec![Position { x: 0, y: 0 }].repeat(5));
        jlstz_offset_data.insert(
            RotationState::R,
            vec![
                Position { x: 0, y: 0 },
                Position { x: 1, y: 0 },
                Position { x: 1, y: 1 },
                Position { x: 0, y: -2 },
                Position { x: 1, y: -2 },
            ],
        );
        jlstz_offset_data.insert(RotationState::U, vec![Position { x: 0, y: 0 }].repeat(5));
        jlstz_offset_data.insert(
            RotationState::L,
            vec![
                Position { x: 0, y: 0 },
                Position { x: -1, y: 0 },
                Position { x: -1, y: 1 },
                Position { x: 0, y: -2 },
                Position { x: -1, y: -2 },
            ],
        );

        i_offset_data.insert(
            RotationState::O,
            vec![
                Position { x: 0, y: 0 },
                Position { x: -1, y: 0 },
                Position { x: 2, y: 0 },
                Position { x: -1, y: 0 },
                Position { x: 2, y: 0 },
            ],
        );
        i_offset_data.insert(
            RotationState::R,
            vec![
                Position { x: -1, y: 0 },
                Position { x: 0, y: 0 },
                Position { x: 0, y: 0 },
                Position { x: 0, y: -1 },
                Position { x: 0, y: 2 },
            ],
        );
        i_offset_data.insert(
            RotationState::U,
            vec![
                Position { x: -1, y: -1 },
                Position { x: 1, y: -1 },
                Position { x: -2, y: -1 },
                Position { x: 1, y: 0 },
                Position { x: -2, y: 0 },
            ],
        );
        i_offset_data.insert(
            RotationState::L,
            vec![
                Position { x: 0, y: -1 },
                Position { x: 0, y: -1 },
                Position { x: 0, y: -1 },
                Position { x: 0, y: 1 },
                Position { x: 0, y: -2 },
            ],
        );

        o_offset_data.insert(RotationState::O, vec![Position { x: 0, y: 0 }]);
        o_offset_data.insert(RotationState::R, vec![Position { x: 0, y: 1 }]);
        o_offset_data.insert(RotationState::U, vec![Position { x: -1, y: 1 }]);
        o_offset_data.insert(RotationState::L, vec![Position { x: -1, y: 0 }]);

        SRS {
            jlstz_offset_data,
            i_offset_data,
            o_offset_data,
        }
    }

    fn rotate_left(&self, playfield: &PlayField) -> Result<Position, ()> {
        self.rotate_common(playfield, RotationDirection::Left)
    }

    fn rotate_right(&self, playfield: &PlayField) -> Result<Position, ()> {
        self.rotate_common(playfield, RotationDirection::Right)
    }
}
