use crate::actions::{Action, ActionsHeld};
use crate::board::Board;
use crate::game::{CurrentTetromino, Game};
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::pixels::Color;
use sdl2::rect::Rect;
use sdl2::render::{Texture, TextureCreator, WindowCanvas};
use sdl2::ttf::Sdl2TtfContext;
use std::time::Duration;

use super::audio::{self};
use super::ui::{Rgb, UiCtx};

const WIDTH: i32 = 1000;
const HEIGHT: i32 = 800;

fn font_texture<'font, 'a, P: AsRef<std::path::Path>, Text: AsRef<str>, C>(
    font: P,
    text: Text,
    ttf_context: &'a Sdl2TtfContext,
    texture_creator: &'font TextureCreator<C>,
) -> Result<Texture<'font>, String> {
    let font = ttf_context.load_font(font, 24)?;
    let game_over_text = font
        .render(text.as_ref())
        .solid(Color::RGB(255, 255, 255))
        .map_err(|err| err.to_string())?;
    let texture = texture_creator
        .create_texture_from_surface(game_over_text)
        .map_err(|err| err.to_string())?;

    Ok(texture)
}

struct SdlUiCtx {
    canvas: WindowCanvas,
    ttf: Sdl2TtfContext,
}

impl SdlUiCtx {
    fn present(&mut self) {
        self.canvas.present();
    }
}

impl UiCtx<String> for SdlUiCtx {
    fn window_size(&self) -> Result<(i32, i32), String> {
        let (width, height) = self.canvas.window().size();
        Ok((width as i32, height as i32))
    }

    fn fill_rect(
        &mut self,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
        rgb: &super::ui::Rgb,
    ) -> Result<(), String> {
        self.canvas.set_draw_color(Color::RGB(rgb.0, rgb.1, rgb.2));
        self.canvas
            .fill_rect(Rect::new(x, y, width as u32, height as u32))?;
        Ok(())
    }

    fn outline_rect(
        &mut self,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
        rgb: &super::ui::Rgb,
    ) -> Result<(), String> {
        self.canvas.set_draw_color(Color::RGB(rgb.0, rgb.1, rgb.2));
        self.canvas
            .draw_rect(Rect::new(x, y, width as u32, height as u32))?;
        Ok(())
    }

    fn text_size<P: AsRef<std::path::Path>, Text: AsRef<str>>(
        &mut self,
        font: P,
        text: Text,
    ) -> Result<(i32, i32), String> {
        let texture_creator = self.canvas.texture_creator();
        let texture = font_texture(font, text, &self.ttf, &texture_creator)?;
        let query = texture.query();
        Ok((query.width as i32, query.height as i32))
    }

    fn fill_text<P: AsRef<std::path::Path>, Text: AsRef<str>>(
        &mut self,
        font: P,
        text: Text,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
    ) -> Result<(), String> {
        let texture_creator = self.canvas.texture_creator();
        let texture = font_texture(font, text, &self.ttf, &texture_creator)?;
        self.canvas.copy(
            &texture,
            None,
            Some(Rect::new(x, y, width as u32, height as u32)),
        )?;
        Ok(())
    }

    fn clear(&mut self, rgb: &Rgb) -> Result<(), String> {
        self.canvas.set_draw_color(Color::RGB(rgb.0, rgb.1, rgb.2));
        self.canvas.clear();
        Ok(())
    }
}

pub fn start_game() -> Result<(), String> {
    let mut game = Game::new();
    let mut actions = ActionsHeld::new();
    let mut paused = false;

    let audio_thread = audio::audio_thread();

    let sdl_context = sdl2::init()?;
    let ttf_context = sdl2::ttf::init().unwrap();
    let video_subsystem = sdl_context.video()?;

    let window = video_subsystem
        .window("reimtris2", WIDTH as u32, HEIGHT as u32)
        .position_centered()
        .build()
        .unwrap();

    let canvas = window.into_canvas().build().unwrap();
    let mut ctx = SdlUiCtx {
        canvas,
        ttf: ttf_context,
    };
    let mut event_pump = sdl_context.event_pump()?;
    'running: loop {
        ctx.clear(&Rgb(16, 16, 16))?;
        for event in event_pump.poll_iter() {
            match event {
                Event::Quit { .. }
                | Event::KeyDown {
                    keycode: Some(Keycode::Escape),
                    ..
                } => break 'running Ok(()),
                Event::KeyDown {
                    keycode: Some(keycode),
                    ..
                } => {
                    let keycode = match keycode {
                        Keycode::Return if !paused && game.game_over => {
                            game = Game::new();
                            continue;
                        }
                        Keycode::M => {
                            audio_thread.send(audio::Command::ToggleMuted).unwrap();
                            continue;
                        }

                        Keycode::P => {
                            paused = !paused;
                            continue;
                        }
                        Keycode::Left | Keycode::A => Action::Left,
                        Keycode::Right | Keycode::D => Action::Right,
                        Keycode::Down | Keycode::S => Action::SoftDrop,
                        Keycode::Space => Action::HardDrop,
                        Keycode::Z => Action::RotateCcw,
                        Keycode::X => Action::RotateCw,
                        Keycode::C => Action::Swap,
                        _ => continue,
                    };
                    actions.insert(keycode, game.ticks);
                }
                Event::KeyUp {
                    keycode: Some(keycode),
                    ..
                } => {
                    let keycode = match keycode {
                        Keycode::Left | Keycode::A => Action::Left,
                        Keycode::Right | Keycode::D => Action::Right,
                        Keycode::Down | Keycode::S => Action::SoftDrop,
                        Keycode::Space => Action::HardDrop,
                        Keycode::Z => Action::RotateCcw,
                        Keycode::X => Action::RotateCw,
                        Keycode::C => Action::Swap,
                        _ => continue,
                    };
                    actions.remove(&keycode);
                }
                _ => {}
            }
        }

        ctx.draw_board(&game.board, &game.current_tetromino)?;

        if paused {
            ctx.draw_important_text(
                "resources/josenfin_sans_regular.ttf",
                "game paused o_o... press [p] to unpause !!",
            )?;
        } else if game.game_over {
            ctx.draw_important_text(
                "resources/josenfin_sans_regular.ttf",
                "game over T_T... press [enter] 2 restart :D",
            )?;
        } else {
            let effects = game.step(&actions);
            effects.into_iter().for_each(|effect| {
                audio_thread
                    .send(audio::Command::PlayEffect(effect))
                    .unwrap()
            });
        }

        ctx.present();
        ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
    }
}