use std::{cell::RefCell, rc::Rc};

use glam::{Quat, Vec2, Vec3};
use opcodes::{Instruction, SpriteType};
use wgpu::{BlendComponent, BlendFactor, BlendOperation, BlendState, Buffer, RenderPipeline};

use crate::interp::{FloatInterpolator, Vec2Interpolator, Vec3Interpolator};

use super::{
  loaded_file::{LoadedEntry, LoadedFile, LoadedScript, LoadedSprite}, manager::ManagerUpdate, Vm
};

pub mod execute;
pub(super) mod opcodes;

pub(super) struct RenderingState {
  pub instance_buffer: Buffer,
  pub pipeline: RenderPipeline,
}

pub struct AnmVm {
  pub(super) deleted: bool,
  fully_static: bool,
  frozen: bool,
  pub(super) ticked_by_parent: bool,

  time: i32,
  pc: usize,
  pub(super) file: Rc<LoadedFile>,
  script: Rc<LoadedScript>,
  gp_vars: [i32; 10],
  waiting: Option<Instruction>,

  origin: Option<Vec3>,
  position_interpolator: Vec3Interpolator,
  rotation_interpolator: Vec3Interpolator,
  scale_interpolator: Vec2Interpolator,
  color_interpolator: Vec3Interpolator,
  alpha_interpolator: FloatInterpolator,
  pub(super) anchor_offset: Vec2,
  scroll_x: FloatInterpolator,
  scroll_y: FloatInterpolator,
  pub(super) visible: bool,
  pub(super) sprite: Option<u32>,
  pub(super) layer: u32,
  random_mode: i32,
  sprite_mode: SpriteType,
  pub(super) blend_state: BlendState,

  pub(super) rendering_state: RefCell<Option<RenderingState>>,

  pub(super) children: Vec<Vm>,
}

impl AnmVm {
  pub(super) fn new(
    file: Rc<LoadedFile>,
    origin: Option<Vec3>,
    ticked_by_parent: bool,
    script: usize,
    default_layer: u32,
  ) -> AnmVm {
    AnmVm {
      deleted: false,
      fully_static: false,
      frozen: false,
      ticked_by_parent,

      time: 0,
      pc: 0,
      script: file.scripts[script].clone(),
      file,
      gp_vars: [Default::default(); 10],
      waiting: None,

      origin,
      position_interpolator: Vec3Interpolator::new(Vec3::ZERO),
      rotation_interpolator: Vec3Interpolator::new(Vec3::ZERO),
      scale_interpolator: Vec2Interpolator::new(Vec2::ONE),
      color_interpolator: Vec3Interpolator::new(Vec3::ONE),
      alpha_interpolator: FloatInterpolator::new(1.0),
      anchor_offset: Vec2::new(0.5, 0.5),
      scroll_x: FloatInterpolator::new(0.0),
      scroll_y: FloatInterpolator::new(0.0),
      visible: true,
      sprite: None,
      layer: default_layer,
      sprite_mode: SpriteType::NoRotate,
      blend_state: BlendState::ALPHA_BLENDING,
      random_mode: 0,

      rendering_state: Default::default(),

      children: vec![],
    }
  }

  pub(super) fn sprite_entry_pair(&self) -> Option<(LoadedSprite, Rc<LoadedEntry>)> {
    if !self.visible {
      return None;
    }
    self.sprite.and_then(|sprite| {
      self.file.sprite_entries.get(&sprite).map(|(start, entry)| {
        let sprite = sprite - start;
        (entry.sprites[sprite as usize], entry.clone())
      })
    })
  }

  pub fn scale(&self) -> Vec2 {
    self.scale_interpolator.current()
  }

  pub fn rotation(&self) -> Quat {
    let euler = self.rotation_interpolator.current();
    Quat::from_euler(glam::EulerRot::XYZ, euler.x, euler.y, euler.z)
  }

  pub fn position(&self) -> Vec3 {
    self.origin.unwrap_or(Vec3::ZERO) + self.position_interpolator.current()
  }

  pub fn color(&self) -> Vec3 {
    self.color_interpolator.current() / 255.0
  }

  pub fn alpha(&self) -> f32 {
    self.alpha_interpolator.current() / 255.0
  }

  pub fn scroll(&self) -> Vec2 {
    Vec2::new(self.scroll_x.current(), self.scroll_y.current())
  }

  fn tick_interpolators(&mut self) {
    self.position_interpolator.tick();
    self.rotation_interpolator.tick();
    self.scale_interpolator.tick();
    self.color_interpolator.tick();
    self.alpha_interpolator.tick();
  }

  fn delete(&mut self) {
    self.deleted = true;
    self.visible = false;
  }

  pub(super) fn tick(&mut self, manager: &mut ManagerUpdate) {
    if self.deleted {
      return;
    }
    if !self.fully_static && !self.frozen {
      self.tick_interpolators();
      self.execute(manager);
      self.advance_time();
    }

    for child in &self.children {
      let mut child = child.borrow_mut();
      child.origin = Some(self.position());
      child.tick(manager);
    }
  }

  pub fn set_blend_mode(&mut self, mode: i32) {
    let mode = match mode {
      0 => BlendState::ALPHA_BLENDING,
      1 => BlendState {
        color: BlendComponent {
          src_factor: BlendFactor::SrcAlpha,
          dst_factor: BlendFactor::One,
          operation: BlendOperation::Add,
        },
        alpha: BlendComponent::OVER,
      },
      _ => unimplemented!("blend mode {mode} not added"),
    };

    self.blend_state = mode;
  }
}
