use std::error::Error;

use crate::fleet::FleetsManager;
use crate::solar_system::orbit::StaticOrbiter;
use crate::solar_system::{Kilometers};
use crate::timeman::{Second};
use crate::wgpuctx::{WgpuCtx, pipeline::RenderPipelineBuilder};

use super::*;

pub struct FleetRenderer
{
    last_time: Option<Second>,

    pipeline: wgpu::RenderPipeline,

    fleet_instance_buffer: Option<(usize, wgpu::Buffer)>
}

struct FleetInstance
{
    offset: cgmath::Vector3<Kilometers>,
    origin: cgmath::Vector3<Kilometers>,
    heading: cgmath::Vector2<cgmath::Rad<f32>>
}

#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct FleetInstanceRaw
{
    rotmat: [[f32;3];3],
    origin: [f32;3],
    offset: [f32;3],
}

impl FleetRenderer
{
    pub fn new(
        wgpuctx: &WgpuCtx)
    -> Self {
        let shader = wgpuctx.create_shader(
            wgpu::include_wgsl!(concat!(
                    env!("CARGO_MANIFEST_DIR"),
                    "/assets/shaders/tacmap/fleet.wgsl")
        ));

        let render_pipeline = RenderPipelineBuilder::new(&shader)
            .add_bindgroup(&Camera::bindgroup_layout(wgpuctx))
            .add_vertex_layout(FleetInstanceRaw::descr())
            .cull_mode(None)
            .build(Some("Tactical map fleet render pipeline"), wgpuctx);

        Self {
            last_time: None,
            pipeline: render_pipeline,
            fleet_instance_buffer: None
        }
    }

    pub fn update(
        &mut self,
        wgpuctx: &WgpuCtx,
        fleets_manager: &FleetsManager,
        solar_system: &SolarSystem,
        time: Second)
    -> Result<(), Box<dyn Error>>
    {
        //If the last updated time is the same, we don't need to update
        //the positions of all the bodies.
        if self.last_time.is_some_and(|last_time| { last_time == time }) {
            return Ok(());
        }

        self.last_time = Some(time);

        if let Some((_, buffer)) = &self.fleet_instance_buffer {
            buffer.destroy();
            self.fleet_instance_buffer = None;
        }

        let fleets = solar_system.fleets(fleets_manager);
        let fleets_instances = fleets.iter().filter_map(|id| {
            let fleet = match fleets_manager.fleet(*id) {
                Some(fleet) => fleet,
                None => { return None; }
            };
            let heading = fleet.heading();
            let offset = fleet.offset_position();
            let origin = match fleet.orbit() {
                Some(orbit) => {
                    let origin_body = solar_system.body(orbit.parent());
                    origin_body.absolute_position(solar_system, time)
                },
                None => cgmath::vec3(0.0, 0.0, 0.0)
            };
            Some(
                FleetInstance {
                    heading: *heading,
                    offset: *offset,
                    origin: origin
                }.raw()
            )
        }).collect::<Vec<_>>();
        let fleets_buffer = wgpuctx.create_buffer_init(
            &wgpu::util::BufferInitDescriptor {
                label: None,
                contents: bytemuck::cast_slice(fleets_instances.as_slice()),
                usage: wgpu::BufferUsages::VERTEX
        });

        self.fleet_instance_buffer = Some((fleets.len(), fleets_buffer));

        Ok(())
    }

    pub fn render(
        &self,
        pass: &mut wgpu::RenderPass,
        camera: &Camera)
    {
        let (num_fleets, fleets_buffer) = match &self.fleet_instance_buffer {
            Some(tuple) => tuple,
            None => return
        };
        if *num_fleets == 0 { return; }

        pass.set_pipeline(&self.pipeline);
        pass.set_bind_group(0, camera.bindgroup(), &[]);

        pass.set_vertex_buffer(0, fleets_buffer.slice(..));

        pass.draw(0..6, 0..*num_fleets as _);
    }
} // impl RenderState

impl FleetInstance 
{
    fn raw(&self) -> FleetInstanceRaw
    {
        FleetInstanceRaw {
            rotmat: (cgmath::Matrix3::from_angle_x(self.heading.x) *
                     cgmath::Matrix3::from_angle_y(self.heading.y))
                    .into(),
            origin: [
                self.origin.x as f32,
                self.origin.y as f32,
                self.origin.z as f32
            ],
            offset: [
                self.offset.x as f32,
                self.offset.y as f32,
                self.offset.z as f32
            ]
        }
    }
} // impl BodyInstance

impl FleetInstanceRaw 
{
    const ATTRIBS: [wgpu::VertexAttribute;5] =
        wgpu::vertex_attr_array![
        0 => Float32x3, 1 => Float32x3, 2 => Float32x3,
        3 => Float32x3,
        4 => Float32x3
        ];

    pub fn descr()
    -> wgpu::VertexBufferLayout<'static> {
        use std::mem;

        wgpu::VertexBufferLayout {
            array_stride: mem::size_of::<Self>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Instance,
            attributes: &Self::ATTRIBS
        }
    }
}
