use sge_vectors::{Vec2, Vec3};
use glium::texture::TextureCreationError;
use log::warn;
use sge_camera::cameras_for_resolution;
use sge_color::Color;
use sge_math::{collision::Aabb2d, transform::Transform2D};
use sge_programs::{
    BLINN_PHONG_3D_PROGRAM, FLAT_3D_PROGRAM, GOURAUD_3D_PROGRAM, TEXTURED_3D_PROGRAM,
};
use sge_textures::TextureRef;
use sge_window::window_size;

use crate::{
    d2::{Renderer2D, Scene2D},
    dq2d, get_render_state,
    materials::{Material, MaterialRef},
    pipeline::{
        ClearColor, RenderPipeline, RenderTarget, RenderTextureRef, current_render_pipeline,
        draw_queue_2d, empty_render_texture, world_draw_queue_2d,
    },
    wdq2d,
};

pub fn draw_texture(texture: TextureRef, position: Vec2, scale: f32) {
    draw_texture_scaled(texture, position, texture.normalized_dimensions * scale);
}

pub fn draw_texture_scaled(texture: TextureRef, position: Vec2, scale: Vec2) {
    draw_queue_2d().renderer().add_sprite(
        texture,
        Transform2D::from_scale_translation(scale, position),
        Color::WHITE,
        None,
    );
}

pub fn draw_texture_world(texture: TextureRef, position: Vec2, scale: f32) {
    draw_texture_scaled_world(texture, position, texture.normalized_dimensions * scale);
}

pub fn draw_texture_scaled_world(texture: TextureRef, position: Vec2, scale: Vec2) {
    let bounds = Aabb2d::new(position - scale, position + scale);

    if !bounds.is_visible_in_world() {
        return;
    }

    world_draw_queue_2d().renderer().add_sprite(
        texture,
        Transform2D::from_scale_translation(scale, position),
        Color::WHITE,
        None,
    );
}

pub fn draw_texture_world_ex(
    texture: TextureRef,
    transform: Transform2D,
    color: Color,
    region: Option<sge_vectors::Rect>,
) {
    let bounds = Aabb2d::new(
        transform.translation() - transform.scale(),
        transform.translation() + transform.scale(),
    );

    if !bounds.is_visible_in_world() {
        return;
    }

    world_draw_queue_2d()
        .renderer()
        .add_sprite(texture, transform, color, region);
}

pub fn draw_texture_ex(
    sprite: TextureRef,
    transform: Transform2D,
    color: Color,
    region: Option<sge_vectors::Rect>,
) {
    draw_queue_2d()
        .renderer()
        .add_sprite(sprite, transform, color, region);
}

pub fn create_flat_material(color: Color) -> MaterialRef {
    let material = Material::new(FLAT_3D_PROGRAM).with_color("color", color);
    material.create()
}

pub fn create_gouraud_material(
    regular_color: Color,
    dark_color: Color,
    light_pos: Vec3,
) -> MaterialRef {
    let material = Material::new(GOURAUD_3D_PROGRAM)
        .with_color("regular_color", regular_color)
        .with_color("dark_color", dark_color)
        .with_vec3("light_pos", light_pos);

    material.create()
}

pub fn create_textured_material(texture: TextureRef) -> MaterialRef {
    let material = Material::new(TEXTURED_3D_PROGRAM).with_texture("tex", texture);
    material.create()
}

pub fn create_blinn_phong_material(
    ambient: Color,
    diffuse: Color,
    specular: Color,
    rim: Color,
    light_pos: Vec3,
) -> MaterialRef {
    let material = Material::new(BLINN_PHONG_3D_PROGRAM)
        .with_color("ambient_color", ambient)
        .with_color("diffuse_color", diffuse)
        .with_color("specular_color", specular)
        .with_color("rim_color", rim)
        .with_vec3("light_pos", light_pos);
    material.create()
}

pub fn start_rendering_to_texture(texture: RenderTextureRef) {
    let size = texture.dimensions();

    get_render_state().texture_pipeline = Some(RenderPipeline::new(
        RenderTarget::Texture(texture),
        Some(cameras_for_resolution(size.x, size.y)),
    ));
}

pub fn end_rendering_to_texture() {
    let state = get_render_state();

    match &mut state.texture_pipeline {
        Some(pipeline) => pipeline.draw(),
        None => warn!(
            "Called `end_rendering_to_texture` without any texture pipeline loaded. Create one with `start_rendering_to_texture`."
        ),
    }

    state.texture_pipeline = None;
}

pub fn create_empty_render_texture(
    width: u32,
    height: u32,
) -> Result<RenderTextureRef, TextureCreationError> {
    Ok(empty_render_texture(width, height)?.create())
}

pub fn clear_screen(color: Color) {
    current_render_pipeline().clear_color = ClearColor::Clear(color);
}

pub fn dont_clear_screen() {
    current_render_pipeline().clear_color = ClearColor::DontClear;
}

pub fn draw_fullscreen_texture(texture: TextureRef) {
    draw_texture_scaled(texture, Vec2::ZERO, window_size());
}

pub fn draw_scene(scene: &Scene2D) {
    dq2d().add_scene(scene);
}

pub fn draw_scene_world(scene: &Scene2D) {
    wdq2d().add_scene(scene);
}

pub fn draw_scene_to(scene: &Scene2D, mut renderer: Renderer2D) {
    renderer.add_scene(scene);
}
