use crate::d3::DrawQueue3D;
use crate::get_render_state;
use crate::post_processing::PostProcessingEffect;
use crate::{DrawQueues, d2::DrawQueue2D};
use sge_vectors::{Mat4, UVec2, Vec2, Vec3};
use glium::Texture2d;
use glium::{
    Surface,
    framebuffer::SimpleFrameBuffer,
    texture::{DepthTexture2d, TextureCreationError},
    uniform,
};
use sge_camera::{Cameras, get_cameras};
use sge_color::Color;
use sge_macros::gen_ref_type;
use sge_programs::COPY_PROGRAM;
use sge_textures::{SgeTexture, TextureRef, get_texture_state};
use sge_window::{get_display, get_display_mut, get_window_state};

pub struct RenderTexture {
    pub dimensions: UVec2,
    pub color_texture: TextureRef,
    pub depth_texture: DepthTexture2d,
}

impl RenderTexture {
    pub fn framebuffer(&mut self) -> SimpleFrameBuffer<'_> {
        let window = get_window_state();
        let texture = self.color_texture.get();
        SimpleFrameBuffer::with_depth_buffer(
            &window.display,
            &texture.gl_texture,
            &self.depth_texture,
        )
        .unwrap()
    }
}

gen_ref_type!(RenderTexture, RenderTextureRef, render_textures);

impl RenderTextureRef {
    pub fn dimensions(&self) -> UVec2 {
        self.get().dimensions
    }

    pub fn framebuffer(&self) -> SimpleFrameBuffer<'_> {
        self.get_mut().framebuffer()
    }
}

pub enum RenderTarget {
    Screen,
    Texture(RenderTextureRef),
}

pub struct RenderPipeline {
    pub steps: Vec<RenderStep>,
    pub output: RenderTarget,
    pub clear_color: ClearColor,
    pub camera_override: Option<Cameras>,
}

pub enum ClearColor {
    Clear(Color),
    DontClear,
    Default,
}

impl From<Color> for ClearColor {
    fn from(value: Color) -> Self {
        Self::Clear(value)
    }
}

// i dont care 😝😝😝😝😝
#[allow(clippy::large_enum_variant)]
pub enum RenderStep {
    Drawing(DrawQueues),
    PostProcessing(PostProcessingStep),
}

impl DrawQueues {
    pub fn empty() -> Self {
        let draw_queue_2d = DrawQueue2D::screen();
        let world_draw_queue_2d = DrawQueue2D::world();
        let draw_queue_3d = DrawQueue3D::empty();

        Self {
            draw_queue_2d,
            draw_queue_3d,
            world_draw_queue_2d,
        }
    }
}

pub struct PostProcessingStep(pub Vec<PostProcessingEffect>);

impl RenderPipeline {
    pub fn draw_queues(&mut self) -> &mut DrawQueues {
        if !matches!(self.most_recent_step(), Some(RenderStep::Drawing(_))) {
            self.steps.push(RenderStep::Drawing(DrawQueues::empty()));
        }

        let len = self.steps.len() - 1;
        match &mut self.steps[len] {
            RenderStep::Drawing(draw) => draw,
            RenderStep::PostProcessing(_) => unreachable!(),
        }
    }

    pub fn post_processing_effects(&mut self) -> &mut PostProcessingStep {
        if !matches!(self.most_recent_step(), Some(RenderStep::PostProcessing(_))) {
            self.steps
                .push(RenderStep::PostProcessing(PostProcessingStep(Vec::new())));
        }

        let len = self.steps.len() - 1;
        match &mut self.steps[len] {
            RenderStep::PostProcessing(p) => p,
            RenderStep::Drawing(_) => unreachable!(),
        }
    }

    pub fn draw_queue_2d(&mut self) -> &mut DrawQueue2D {
        &mut self.draw_queues().draw_queue_2d
    }

    pub fn world_draw_queue_2d(&mut self) -> &mut DrawQueue2D {
        &mut self.draw_queues().world_draw_queue_2d
    }

    pub fn draw_queue_3d(&mut self) -> &mut DrawQueue3D {
        &mut self.draw_queues().draw_queue_3d
    }

    pub fn most_recent_step(&self) -> Option<&RenderStep> {
        self.steps.last()
    }

    pub fn cameras(&self) -> Cameras {
        match self.camera_override {
            Some(c) => c,
            None => *get_cameras(),
        }
    }

    pub fn add_effect(&mut self, effect: PostProcessingEffect) {
        self.post_processing_effects().0.push(effect);
    }

    pub fn new(output: RenderTarget, camera_override: Option<Cameras>) -> Self {
        Self {
            steps: vec![],
            output,
            clear_color: ClearColor::Default,
            camera_override,
        }
    }

    pub fn draw(&mut self) {
        match self.output {
            RenderTarget::Screen => {
                self.draw_on(&mut get_display_mut().draw());
            }
            RenderTarget::Texture(rt) => {
                let rt_mut = rt.get_mut();
                let texture = rt_mut.color_texture;
                let mut framebuffer = SimpleFrameBuffer::with_depth_buffer(
                    get_display(),
                    &texture.gl_texture,
                    &rt_mut.depth_texture,
                )
                .unwrap();
                self.draw_on(&mut framebuffer);
            }
        }
    }

    pub fn draw_on<T: Surface>(&mut self, frame: &mut T) {
        let mut cameras = self.cameras();
        let is_texture_target = matches!(self.output, RenderTarget::Texture(_));

        let has_post_processing = self
            .steps
            .iter()
            .any(|step| matches!(step, RenderStep::PostProcessing(_)));

        if has_post_processing {
            let dimensions = frame.get_dimensions();

            let mut a = empty_render_texture(dimensions.0, dimensions.1).unwrap();
            let mut b = empty_render_texture(dimensions.0, dimensions.1).unwrap();

            match self.clear_color {
                ClearColor::DontClear => (),
                ClearColor::Default => {
                    a.framebuffer().clear_color(0.0, 0.0, 0.0, 1.0);
                    b.framebuffer().clear_color(0.0, 0.0, 0.0, 1.0);
                }
                ClearColor::Clear(c) => {
                    a.framebuffer().clear_color(c.r, c.g, c.b, c.a);
                    b.framebuffer().clear_color(c.r, c.g, c.b, c.a);
                }
            }

            for step in std::mem::take(&mut self.steps) {
                a.framebuffer().clear_depth(1.0);
                b.framebuffer().clear_depth(1.0);

                match step {
                    RenderStep::Drawing(draw_queues) => {
                        self.draw_queues_to(
                            &mut a.framebuffer(),
                            draw_queues,
                            &mut cameras,
                            is_texture_target,
                        );
                    }
                    RenderStep::PostProcessing(effects) => {
                        for effect in effects.0 {
                            effect
                                .apply(
                                    a.color_texture,
                                    &mut b.framebuffer(),
                                    Vec2::new(dimensions.0 as f32, dimensions.1 as f32),
                                )
                                .unwrap();

                            std::mem::swap(&mut a, &mut b);
                        }
                    }
                }
            }

            self.draw_texture_to_target(frame, a.color_texture);

            let storage = get_texture_state();
            storage.pop();
            storage.pop();
        } else {
            match self.clear_color {
                ClearColor::DontClear => (),
                ClearColor::Default => {
                    frame.clear_color(0.0, 0.0, 0.0, 1.0);
                    frame.clear_depth(1.0);
                }
                ClearColor::Clear(c) => {
                    let lin = c.to_linear();
                    frame.clear_color(lin.r, lin.g, lin.b, lin.a);
                    frame.clear_depth(1.0);
                }
            }

            for step in std::mem::take(&mut self.steps) {
                match step {
                    RenderStep::Drawing(draw_queues) => {
                        self.draw_queues_to(frame, draw_queues, &mut cameras, is_texture_target);
                    }
                    RenderStep::PostProcessing(_) => {
                        unreachable!();
                    }
                }
            }
        }
    }

    fn draw_queues_to<T: Surface>(
        &self,
        target: &mut T,
        mut draw_queues: DrawQueues,
        cameras: &mut Cameras,
        is_texture_target: bool,
    ) {
        let view_proj = cameras.d3.view_proj();
        draw_queues.draw_queue_3d.draw(target, &view_proj);
        target.clear_depth(1.0);

        let mut projection = cameras.d2.projection_matrix();
        if is_texture_target {
            projection = Mat4::from_scale(Vec3::new(1.0, -1.0, 1.0)) * projection;
        }
        draw_queues.world_draw_queue_2d.draw(target, &projection);
        target.clear_depth(1.0);

        let mut flat_projection = cameras.flat;
        if is_texture_target {
            flat_projection = Mat4::from_scale(Vec3::new(1.0, -1.0, 1.0)) * flat_projection;
        }
        draw_queues.draw_queue_2d.draw(target, &flat_projection);
        target.clear_depth(1.0);
    }

    fn draw_texture_to_target<T: Surface>(&self, target: &mut T, texture: TextureRef) {
        use crate::post_processing::render_fullscreen_quad;

        let uniforms = uniform! {
            tex: texture.get().gl_texture.sampled()
        };

        render_fullscreen_quad(target, &COPY_PROGRAM, &uniforms).unwrap();
    }

    pub fn screen() -> Self {
        Self::new(RenderTarget::Screen, None)
    }

    pub(crate) fn new_draw_queues(&mut self) {
        self.steps.push(RenderStep::Drawing(DrawQueues::empty()));
    }
}

pub fn current_render_pipeline() -> &'static mut RenderPipeline {
    let state = get_render_state();

    match &mut state.texture_pipeline {
        Some(pipeline) => pipeline,
        None => &mut state.render_pipeline,
    }
}

pub fn new_draw_queues() {
    current_render_pipeline().new_draw_queues();
}

pub fn draw_queue_2d() -> &'static mut DrawQueue2D {
    current_render_pipeline().draw_queue_2d()
}

pub fn draw_queue_3d() -> &'static mut DrawQueue3D {
    current_render_pipeline().draw_queue_3d()
}

pub fn world_draw_queue_2d() -> &'static mut DrawQueue2D {
    current_render_pipeline().world_draw_queue_2d()
}

pub(crate) fn empty_render_texture(
    width: u32,
    height: u32,
) -> Result<RenderTexture, TextureCreationError> {
    let window = get_window_state();
    let facade = &window.display;
    let texture = Texture2d::empty(facade, width, height)?;
    let texture = SgeTexture::new(texture).create();
    Ok(RenderTexture {
        dimensions: UVec2::new(width, height),
        depth_texture: DepthTexture2d::empty(facade, width, height)?,
        color_texture: texture,
    })
}

pub fn init() {
    init_render_textures_storage();
    log::info!("Initialized render texture storage");
}
