use std::time::{Duration, Instant};

use sdl3::{
    Sdl,
    event::Event as SdlEvent,
    render::{Canvas, FPoint, FRect, TextureQuery, Vertex, VertexIndices},
    ttf::Font,
    video::Window as SdlWindow,
};

pub use sdl3::{keyboard::Keycode, pixels::Color};

use crate::{tri2::Tri2, v2::V2};

pub static WIDTH: f64 = 1920.0;
pub static HEIGHT: f64 = 1080.0;

pub trait App<R: Renderer> {
    fn update(&mut self, delta_time: Duration);
    fn render(&self, r: &mut R);
    fn event(&mut self, event: Event);
}

pub trait Renderer {
    fn draw_rect(&mut self, pos: V2, size: V2, color: Color);
    fn draw_line(&mut self, from: V2, to: V2, color: Color);
    fn draw_triangles(&mut self, tris: &[Tri2], color: Color);
    fn draw_text(&mut self, text: &str, pos: V2, color: Color);
}

pub enum Event {
    KeyDown(Keycode),
    KeyUp(Keycode),
}

pub struct Window {
    sdl_cx: Sdl,
    canvas: Canvas<SdlWindow>,
    font: Box<Font<'static>>,
}

impl Window {
    pub fn new() -> Self {
        let sdl_cx = sdl3::init().unwrap();
        let ttf_cx = sdl3::ttf::init().unwrap();

        let video_subsystem = sdl_cx.video().unwrap();
        let window = video_subsystem
            .window("Game", WIDTH as u32, HEIGHT as u32)
            .position_centered()
            .build()
            .unwrap();
        let mut canvas = window.into_canvas();

        let font = Box::new(
            ttf_cx
                .load_font("/usr/share/fonts/TTF/FiraCode-Medium.ttf", 20.0)
                .unwrap(),
        );

        canvas.set_draw_color(Color::BLACK);
        canvas.clear();
        canvas.present();

        Self {
            sdl_cx,
            canvas,
            font,
        }
    }

    pub fn run(&mut self, app: &mut impl App<Self>) {
        let mut event_pump = self.sdl_cx.event_pump().unwrap();

        let mut time_before = Instant::now();
        let time_per_frame = Duration::from_secs_f64(1.0 / 60.0);
        let mut print_fps_timer = Instant::now();
        let mut fps_count = 0;

        'running: loop {
            for event in event_pump.poll_iter() {
                match event {
                    SdlEvent::Quit { .. }
                    | SdlEvent::KeyDown {
                        keycode: Some(Keycode::Escape),
                        ..
                    } => break 'running,
                    SdlEvent::KeyDown {
                        keycode: Some(key), ..
                    } => app.event(Event::KeyDown(key)),
                    SdlEvent::KeyUp {
                        keycode: Some(key), ..
                    } => app.event(Event::KeyUp(key)),
                    _ => {}
                }
            }

            let time_now = Instant::now();
            let delta_time = time_now - time_before;
            time_before = time_now;
            app.update(delta_time);

            self.canvas.set_draw_color(Color::RGB(30, 30, 30));
            self.canvas.clear();
            app.render(self);
            self.canvas.present();

            fps_count += 1;
            if (time_now - print_fps_timer).as_secs_f64() > 1.0 {
                println!("fps: {fps_count}");
                fps_count = 0;
                print_fps_timer = time_now;
            }
            if delta_time < time_per_frame {
                std::thread::sleep(time_per_frame - delta_time);
            }
        }
    }

    fn scale_w2s(&self, v: V2) -> V2 {
        let factor = WIDTH / 2.0;
        V2(v.0 * factor, v.1 * factor)
    }
    fn translate_w2s(&self, v: V2) -> V2 {
        let aspect_ratio_h2w = HEIGHT / WIDTH;
        let middle = V2(1.0, 1.0 * aspect_ratio_h2w);
        V2(v.0 + middle.0, middle.1 - v.1)
    }
    fn point_w2s(&self, v: V2) -> V2 {
        self.scale_w2s(self.translate_w2s(v))
    }
}

impl Renderer for Window {
    fn draw_rect(&mut self, pos: V2, size: V2, color: Color) {
        let size = self.scale_w2s(size);
        let pos = self.point_w2s(pos).translate(V2(0.0, -size.0));
        self.canvas.set_draw_color(color);
        self.canvas
            .fill_rect(FRect::new(pos.0 as _, pos.1 as _, size.0 as _, size.1 as _))
            .unwrap();
    }
    fn draw_line(&mut self, from: V2, to: V2, color: Color) {
        self.canvas.set_draw_color(color);
        self.canvas
            .draw_line(self.point_w2s(from), self.point_w2s(to))
            .unwrap();
    }

    fn draw_triangles(&mut self, tris: &[Tri2], color: Color) {
        let vertices = tris
            .iter()
            .flat_map(|t| [t.0, t.1, t.2])
            .map(|p| self.point_w2s(p))
            .map(|p| Vertex {
                position: p.into(),
                color: color.into(),
                tex_coord: FPoint::new(0.0, 0.0),
            })
            .collect::<Vec<_>>();

        self.canvas
            .render_geometry(&vertices, None, VertexIndices::Sequential)
            .unwrap();
    }

    fn draw_text(&mut self, text: &str, pos: V2, color: Color) {
        let texture_creator = self.canvas.texture_creator();

        let surface = self.font.render(text).blended(color).unwrap();
        let texture = texture_creator
            .create_texture_from_surface(&surface)
            .unwrap();

        let TextureQuery { width, height, .. } = texture.query();

        let pos = self.point_w2s(pos);

        self.canvas.copy(
            &texture,
            None,
            FRect {
                x: pos.0 as _,
                y: pos.1 as _,
                w: width as _,
                h: height as _,
            },
        );
    }
}

impl From<V2> for FPoint {
    fn from(value: V2) -> Self {
        FPoint::new(value.0 as _, value.1 as _)
    }
}
