use std::collections::VecDeque;

use bytemuck::{bytes_of, Pod, Zeroable};
use glam::{Mat4, Vec2, Vec3, Vec4};
use wgpu::{
  BufferDescriptor, BufferUsages, Color, ColorTargetState, ColorWrites, DepthStencilState, Device, Extent3d, FragmentState, LoadOp, Operations, PrimitiveState, RenderPass, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, StoreOp, TextureDescriptor, TextureFormat, TextureUsages, VertexAttribute, VertexBufferLayout, VertexState
};
use winit::dpi::PhysicalSize;

use crate::{
  engine::Engine, game::anm::{loaded_file::SpriteUvs, vm::RenderingState, AnmVm}
};

use super::{Manager, WeakVm};

#[derive(Default, Clone, Copy, Pod, Zeroable)]
#[repr(C, packed)]
pub(super) struct Instance {
  matrix: Mat4,
  sprite_uv: SpriteUvs,
  color: Vec4,
  anchor_offset: Vec2,
}

impl Manager {
  fn new_pipeline(&self, engine: &Engine, vm: &AnmVm) -> RenderPipeline {
    let render_pipeline = engine.device.create_render_pipeline(&RenderPipelineDescriptor {
      label: Some("anm"),
      layout: Some(&self.render_pipeline_layout),
      vertex: VertexState {
        buffers: &[VertexBufferLayout {
          attributes: &[
            VertexAttribute {
              format: wgpu::VertexFormat::Float32x4,
              offset: 16 * 0,
              shader_location: 0,
            },
            VertexAttribute {
              format: wgpu::VertexFormat::Float32x4,
              offset: 16 * 1,
              shader_location: 1,
            },
            VertexAttribute {
              format: wgpu::VertexFormat::Float32x4,
              offset: 16 * 2,
              shader_location: 2,
            },
            VertexAttribute {
              format: wgpu::VertexFormat::Float32x4,
              offset: 16 * 3,
              shader_location: 3,
            },
            VertexAttribute {
              format: wgpu::VertexFormat::Float32x4,
              offset: 16 * 4,
              shader_location: 4,
            },
            VertexAttribute {
              format: wgpu::VertexFormat::Float32x4,
              offset: 16 * 5,
              shader_location: 5,
            },
            VertexAttribute {
              format: wgpu::VertexFormat::Float32x2,
              offset: 16 * 6,
              shader_location: 6,
            },
          ],
          array_stride: std::mem::size_of::<Instance>() as u64,
          step_mode: wgpu::VertexStepMode::Instance,
        }],
        entry_point: "vs_main",
        compilation_options: Default::default(),
        module: &self.render_shader,
      },
      primitive: PrimitiveState {
        topology: wgpu::PrimitiveTopology::TriangleStrip,
        ..Default::default()
      },
      depth_stencil: Some(DepthStencilState {
        format: TextureFormat::Depth32Float,
        depth_write_enabled: false,
        depth_compare: wgpu::CompareFunction::Less,
        stencil: wgpu::StencilState::default(),
        bias: wgpu::DepthBiasState::default(),
      }),
      multisample: Default::default(),
      fragment: Some(FragmentState {
        targets: &[Some(ColorTargetState {
          format: engine.config.lock().unwrap().format,
          blend: Some(vm.blend_state),
          write_mask: ColorWrites::ALL,
        })],
        compilation_options: Default::default(),
        entry_point: "fs_main",
        module: &self.render_shader,
      }),
      multiview: Default::default(),
      cache: Default::default(),
    });

    render_pipeline
  }

  pub fn render_vms(&self, engine: &Engine, pass: &mut RenderPass) {
    self.render_layer(engine, pass, &self.ui_vms, 21);
    self.render_layer(engine, pass, &self.ui_vms, 22);
    self.render_layer(engine, pass, &self.ui_vms, 23);
    self.render_layer(engine, pass, &self.ui_vms, 29);
  }

  fn render_layer(&self, engine: &Engine, pass: &mut RenderPass, list: &VecDeque<WeakVm>, layer: u32) {
    for vm in list {
      if let Some(vm) = vm.upgrade() {
        let vm = vm.borrow();
        if vm.layer != layer {
          self.render_vm(engine, pass, &vm, &Vec3::new(0.0, 0.0, layer as f32));
        }
      }
    }
  }

  fn render_vm(&self, engine: &Engine, pass: &mut RenderPass, vm: &AnmVm, layer_origin: &Vec3) {
    if !vm.visible {
      return;
    }

    if let Some((sprite, entry)) = vm.sprite_entry_pair() {
      let mut rendering_state = vm.rendering_state.borrow_mut();
      if rendering_state.is_none() {
        let instance_buffer = engine.device.create_buffer(&BufferDescriptor {
          label: Some("instances"),
          usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
          mapped_at_creation: false,
          size: std::mem::size_of::<Instance>() as u64,
        });

        *rendering_state = Some(RenderingState {
          instance_buffer: instance_buffer,
          pipeline: Some(self.new_pipeline(engine, vm)),
        });
      }

      let rendering_state = rendering_state.as_mut().unwrap();

      if rendering_state.pipeline.is_none() {
        rendering_state.pipeline = Some(self.new_pipeline(engine, vm))
      }

      // let mut matrix = Mat4::from_translation(layer_origin + vm.position());
      let offset = sprite.size.extend(1.0) * vm.anchor_offset.extend(0.0);
      let mut matrix = Mat4::IDENTITY;
      matrix *= Mat4::from_translation(layer_origin + vm.position());
      matrix *= Mat4::from_rotation_translation(vm.rotation(), Vec3::ZERO);
      // matrix *= Mat4::from_translation(-offset);
      matrix *= Mat4::from_scale((sprite.size * vm.scale()).extend(1.0));

      engine.queue.write_buffer(
        &rendering_state.instance_buffer,
        0,
        bytes_of(&Instance {
          matrix,
          sprite_uv: sprite.uvs + vm.scroll(),
          color: vm.color().extend(vm.alpha()),
          anchor_offset: vm.anchor_offset,
        }),
      );

      pass.set_pipeline(rendering_state.pipeline.as_ref().unwrap());
      pass.set_bind_group(0, &entry.bind_group, &[]);
      pass.set_bind_group(1, &self.render_bind_group, &[]);
      pass.set_vertex_buffer(0, rendering_state.instance_buffer.slice(..));
      pass.draw(0..4, 0..1 as u32);
    }

    for child in vm.children.iter() {
      let child = child.borrow();
      self.render_vm(engine, pass, &child, &layer_origin);
    }
  }

  fn render_ui(&self, engine: &Engine, encoder: &mut wgpu::CommandEncoder, surface: &wgpu::Texture) {
    engine.queue.write_buffer(&self.uniform_buffer, 0, bytes_of(&self.ui_uniform));
    let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
      label: Some("anm"),
      color_attachments: &[Some(RenderPassColorAttachment {
        view: &surface.create_view(&Default::default()),
        resolve_target: None,
        ops: Operations {
          // load: LoadOp::Clear(Color {
          //   r: 100. / 255.,
          //   g: 149. / 255.,
          //   b: 237. / 255.,
          //   a: 1.0,
          // }),
          load: LoadOp::Load,
          ..Default::default()
        },
      })],
      depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
        view: &self.depth_texture.create_view(&Default::default()),
        depth_ops: Some(Operations {
          load: LoadOp::Clear(1.0),
          store: StoreOp::Store,
        }),
        stencil_ops: None,
      }),
      ..Default::default()
    });

    self.render_vms(engine, &mut pass);
  }

  fn clear(&self, encoder: &mut wgpu::CommandEncoder, surface: &wgpu::Texture) {
    let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
      label: Some("clear"),
      color_attachments: &[Some(RenderPassColorAttachment {
        view: &surface.create_view(&Default::default()),
        resolve_target: None,
        ops: Operations {
          load: LoadOp::Clear(Color {
            r: 100. / 255.,
            g: 149. / 255.,
            b: 0. / 255.,
            a: 1.0,
          }),
          ..Default::default()
        },
      })],
      depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
        view: &self.depth_texture.create_view(&Default::default()),
        depth_ops: Some(Operations {
          load: LoadOp::Clear(1.0),
          store: StoreOp::Store,
        }),
        stencil_ops: None,
      }),
      ..Default::default()
    });

    pass.set_pipeline(&self.clear_pipeline);
    pass.draw(0..4, 0..1);
  }

  pub fn render(&self, engine: &Engine, surface: &wgpu::Texture, encoder: &mut wgpu::CommandEncoder) {
    self.clear(encoder, surface);
    self.render_ui(engine, encoder, surface);

    if let Some(backbuffer_anm) = self.world_backbuffer_anm.upgrade() {
      let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
        label: Some("blit"),
        color_attachments: &[Some(RenderPassColorAttachment {
          view: &surface.create_view(&Default::default()),
          resolve_target: None,
          ops: Operations {
            load: LoadOp::Clear(Color {
              r: 100. / 255.,
              g: 149. / 255.,
              b: 0. / 255.,
              a: 1.0,
            }),
            ..Default::default()
          },
        })],
        depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
          view: &self.depth_texture.create_view(&Default::default()),
          depth_ops: Some(Operations {
            load: LoadOp::Load,
            store: StoreOp::Store,
          }),
          stencil_ops: None,
        }),
        ..Default::default()
      });

      pass.set_pipeline(&self.blit_pipeline);
      pass.set_bind_group(
        0,
        &backbuffer_anm.borrow().sprite_entry_pair().unwrap().1.bind_group,
        &[],
      );
      pass.set_bind_group(1, &self.render_bind_group, &[]);
      pass.draw(0..4, 0..1);
    }
  }

  pub(super) fn create_depth_texture(device: &Device, size: PhysicalSize<u32>) -> wgpu::Texture {
    device.create_texture(&TextureDescriptor {
      label: Some("depth texture"),
      size: Extent3d {
        width: size.width.max(1),
        height: size.height.max(1),
        depth_or_array_layers: 1,
      },
      mip_level_count: 1,
      sample_count: 1,
      dimension: wgpu::TextureDimension::D2,
      format: wgpu::TextureFormat::Depth32Float,
      usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
      view_formats: &[],
    })
  }

  pub fn resize(&mut self, device: &Device, new_size: PhysicalSize<u32>) {
    self.depth_texture = Self::create_depth_texture(device, new_size)
  }
}
