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

use glam::{Quat, Vec2, Vec3};
use log::warn;
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: Option<RenderPipeline>,
}

pub struct AnmVm {
  debug: bool,

  pub(super) deleted: bool,
  fully_static: bool,
  frozen: bool,
  pub(super) ticked_by_parent: bool,

  time: i32,
  pc: usize,
  pub(super) file: Arc<LoadedFile>,
  script: Arc<LoadedScript>,
  int_vars: [i32; 6],
  float_vars: [f32; 4],
  waiting_countdown: u32,
  pending_interrupt: Option<usize>,
  interrupt_return: Option<(usize, i32)>,

  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: Arc<LoadedFile>,
    origin: Option<Vec3>,
    ticked_by_parent: bool,
    debug: bool,
    script: usize,
    default_layer: u32,
  ) -> AnmVm {
    let mut new = AnmVm {
      deleted: false,
      fully_static: false,
      frozen: false,
      ticked_by_parent,
      debug,

      time: 0,
      pc: 0,
      script: file.scripts[script].clone(),
      file,
      int_vars: [0; 6],
      float_vars: [0.0; 4],
      waiting_countdown: 0,
      pending_interrupt: None,
      interrupt_return: 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::new(255.0, 255.0, 255.0)),
      alpha_interpolator: FloatInterpolator::new(255.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::REPLACE,
      random_mode: 0,

      rendering_state: Default::default(),

      children: vec![],
    };

    new.set_blend_mode(0);

    new
  }

  pub(super) fn sprite_entry_pair(&self) -> Option<(LoadedSprite, Arc<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 debug_freeze(&mut self) {
    self.frozen = true;
  }

  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 raw_position(&self) -> Vec3 {
    self.position_interpolator.current()
  }

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

    if color.length() > 1.75 {
      panic!("large color {color:?}")
    }

    color
  }

  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())
  }

  pub fn deleted(&self) -> bool {
    self.deleted
  }

  pub 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);
    }
    self.children.retain(|vm| !vm.borrow().deleted);
  }

  pub fn set_blend_mode(&mut self, mode: i32) {
    let mode = match mode {
      0 => BlendState {
        // alpha blending
        color: BlendComponent {
          src_factor: BlendFactor::SrcAlpha,
          dst_factor: BlendFactor::OneMinusSrcAlpha,
          operation: BlendOperation::Add,
        },
        alpha: BlendComponent::REPLACE,
      },
      1 => BlendState {
        // additive blending
        color: BlendComponent {
          src_factor: BlendFactor::SrcAlpha,
          dst_factor: BlendFactor::One,
          operation: BlendOperation::Add,
        },
        alpha: BlendComponent::REPLACE,
      },
      3 => BlendState {
        color: BlendComponent::REPLACE,
        alpha: BlendComponent::REPLACE,
      },
      _ => unimplemented!("blend mode {mode} not supported"),
    };

    self.blend_state = mode;
    self.reset_render_pipeline();
  }

  fn reset_render_pipeline(&self) {
    let mut state = self.rendering_state.borrow_mut();
    if let Some(state) = state.as_mut() {
      state.pipeline = None;
    }
  }
}
