use bytemuck::bytes_of;
use glam::{Mat4, Vec3};
use wgpu::{
  BufferDescriptor, BufferUsages, Color, ColorTargetState, ColorWrites, FragmentState, LoadOp, Operations, PrimitiveState, RenderPass, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, RenderPipelineDescriptor, VertexAttribute, VertexBufferLayout, VertexState
};

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

use super::{Instance, Manager};

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,
            },
          ],
          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: None,
      multisample: Default::default(),
      fragment: Some(FragmentState {
        targets: &[Some(ColorTargetState {
          format: engine.config.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) {
    for vm in &self.ui_vms {
      if let Some(vm) = vm.upgrade() {
        self.render_vm(engine, pass, &vm.borrow(), &Vec3::ZERO);
      }
    }
  }

  fn render_vm(&self, engine: &Engine, pass: &mut RenderPass, vm: &AnmVm, parent_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: self.new_pipeline(engine, vm),
        });
      }

      let rendering_state = &rendering_state.as_ref().unwrap();

      let anchor_offset = (sprite.size * vm.anchor_offset).extend(0.0);
      let translation_mat = Mat4::from_translation(vm.position() - anchor_offset);

      let rest_mat =
        Mat4::from_scale_rotation_translation((sprite.size * vm.scale()).extend(1.0), vm.rotation(), Vec3::ZERO);

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

      pass.set_pipeline(&rendering_state.pipeline);
      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);
    }

    // let origin_vec = parent_origin + vm.origin_vec;
    for child in vm.children.iter() {
      let child = child.borrow();
      self.render_vm(engine, pass, &child, &parent_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()
        },
      })],
      ..Default::default()
    });

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

  pub fn render(&self, engine: &Engine, surface: &wgpu::Texture, encoder: &mut wgpu::CommandEncoder) {
    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()
          },
        })],
        ..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);
    } else {
      // encoder.clear_texture(surface, &Default::default());
    }

    self.render_ui(engine, encoder, surface);
  }
}
