use std::{
  cell::RefCell, collections::VecDeque, rc::{Rc, Weak}, sync::Arc
};

use bytemuck::{Pod, Zeroable};
use glam::Mat4;
use wgpu::{naga::FastHashMap, BindGroup, Buffer, PipelineLayout, RenderPipeline, ShaderModule, Texture};

use super::{loaded_file::LoadedFile, AnmVm};

mod loading;
mod location;
mod rendering;

pub use location::VmLocation;
pub type Vm = Rc<RefCell<AnmVm>>;
pub type WeakVm = Weak<RefCell<AnmVm>>;

#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C, packed)]
struct Uniform {
  proj_matrix: Mat4,
}

pub struct Manager {
  anm_files: FastHashMap<String, Arc<LoadedFile>>,
  world_backbuffer_anm: WeakVm,

  ui_vms: VecDeque<WeakVm>,
  world_vms: VecDeque<WeakVm>,

  depth_texture: Texture,
  ui_uniform: Uniform,
  _world_uniform: Uniform,
  uniform_buffer: Buffer,
  render_bind_group: BindGroup,
  render_pipeline_layout: PipelineLayout,
  render_shader: ShaderModule,
  clear_pipeline: RenderPipeline,
  blit_pipeline: RenderPipeline,
}

impl Manager {
  #[allow(unused)]
  pub fn set_world_backbuffer_anm(&mut self, file: Arc<LoadedFile>, script: usize) -> Vm {
    let vm = Rc::new(RefCell::new(AnmVm::new(file, script, 0)));

    self.update_single(&vm);

    self.world_backbuffer_anm = Rc::downgrade(&vm);

    vm
  }

  pub fn new_vm(&mut self, file: Arc<LoadedFile>, interrupt: Option<u32>, script: usize, location: VmLocation) -> Vm {
    let vm = Rc::new(RefCell::new(AnmVm::new(file, script, location.layer)));

    if let Some(interrupt) = interrupt {
      vm.borrow_mut().interrupt(interrupt);
    }

    self.update_single(&vm);

    let mut context = ManagerUpdate::new();
    vm.borrow_mut().tick(&mut context);

    let (vm_list, front) = match location {
      VmLocation {
        world: false, front, ..
      } => (&mut self.ui_vms, front),
      VmLocation { world: true, front, .. } => (&mut self.world_vms, front),
    };

    if front {
      vm_list.push_front(Rc::downgrade(&vm))
    } else {
      vm_list.push_back(Rc::downgrade(&vm));
    }

    vm
  }

  fn update_single(&mut self, vm: &Vm) {
    let mut context = ManagerUpdate::new();
    vm.borrow_mut().tick(&mut context);
    context.apply_lists(&mut self.ui_vms, &mut self.world_vms);
  }

  fn update_list(list: &mut VecDeque<WeakVm>, context: &mut ManagerUpdate) {
    list.retain(|value| {
      if let Some(rc) = value.upgrade() {
        !rc.borrow().deleted()
      } else {
        false
      }
    });

    for vm in list {
      if let Some(vm) = vm.upgrade() {
        if !vm.borrow().ticked_by_parent {
          vm.borrow_mut().tick(context);
        }
      }
    }
  }

  pub fn update(&mut self) {
    let mut context = ManagerUpdate::new();

    Self::update_list(&mut self.world_vms, &mut context);
    Self::update_list(&mut self.ui_vms, &mut context);

    context.apply_lists(&mut self.ui_vms, &mut self.world_vms);
  }
}

#[derive(Default)]
pub(super) struct ManagerUpdate {
  new_ui_vms_front: Vec<WeakVm>,
  new_ui_vms_back: Vec<WeakVm>,
  new_world_vms_front: Vec<WeakVm>,
  new_world_vms_back: Vec<WeakVm>,
}

impl ManagerUpdate {
  fn new() -> Self {
    Self::default()
  }

  pub fn new_vm(&mut self, file: Arc<LoadedFile>, script: usize, location: VmLocation) -> Vm {
    let vm = Rc::new(RefCell::new(AnmVm::new(file, script, location.layer)));
    vm.borrow_mut().ticked_by_parent = location.child;
    vm.borrow_mut().tick(self);

    let vm_list = match location {
      VmLocation {
        world: false, front, ..
      } => {
        if front {
          &mut self.new_ui_vms_front
        } else {
          &mut self.new_ui_vms_back
        }
      }
      VmLocation { world: true, front, .. } => {
        if front {
          &mut self.new_world_vms_front
        } else {
          &mut self.new_world_vms_back
        }
      }
    };

    vm_list.push(Rc::downgrade(&vm));

    vm
  }

  fn apply_to_list(list: &mut VecDeque<WeakVm>, front: Vec<WeakVm>, back: Vec<WeakVm>) {
    for vm in front.into_iter().rev() {
      list.push_front(vm.clone());
    }
    list.extend(back.into_iter());
  }

  fn apply_lists(self, ui_list: &mut VecDeque<WeakVm>, world_list: &mut VecDeque<WeakVm>) {
    Self::apply_to_list(ui_list, self.new_ui_vms_front, self.new_ui_vms_back);
    Self::apply_to_list(world_list, self.new_world_vms_front, self.new_world_vms_back);
  }
}
