use glam::{Vec2, Vec3};
use num_traits::{FloatConst, FromPrimitive};
use rand::Rng;

use crate::{
  game::anm::{manager::ManagerUpdate, VmLocation}, interp::Mode
};

use super::{
  opcodes::{Instruction, Op, Param, PrimOp, PrimSetOp}, AnmVm
};

impl AnmVm {
  fn next_instruction(&mut self) -> Option<Instruction> {
    if self.waiting_countdown > 0 {
      return None;
    }

    let instructions = &self.script.instructions;
    if self.pc >= instructions.len() {
      return None;
    }
    let instruction = instructions[self.pc];
    if self.time < instruction.time {
      return None;
    }

    self.pc += 1;
    Some(instruction)
  }

  pub(super) fn advance_time(&mut self) {
    if self.waiting_countdown == 0 {
      self.time += 1;
    }

    self.waiting_countdown = self.waiting_countdown.saturating_sub(1);
  }

  fn store_int(&mut self, var: i32, value: i32) {
    match var {
      10000..=10003 => self.int_vars[var as usize - 10000] = value,
      10004..=10007 => self.float_vars[var as usize - 10004] = value as f32,
      10008..=10009 => self.int_vars[var as usize - 10008] = value,
      10010..=10012 | 10022 => {} // rand
      10013..=10015 => {}
      // 10013 => self.position.current().x = value as f32,
      // 10014 => self.position.current().y = value as f32,
      // 10015 => self.position.current().z = value as f32,
      _ => unimplemented!("unimplemented variable {var}"),
    }
  }

  fn store_float(&mut self, var: i32, value: f32) {
    match var {
      10000..=10003 => self.int_vars[var as usize - 10000] = value as i32,
      10004..=10007 => self.float_vars[var as usize - 10004] = value,
      10008..=10009 => self.int_vars[var as usize - 10008] = value as i32,
      10010..=10012 | 10022 => {} // rand
      10013..=10015 => {}
      // 10013 => self.position.current().x = value as f32,
      // 10014 => self.position.current().y = value as f32,
      // 10015 => self.position.current().z = value as f32,
      _ => unimplemented!("unimplemented variable {var}"),
    }
  }

  fn param_int(&self, param: Param<i32>) -> i32 {
    match param {
      Param::Variable(var) => match var {
        10000..=10003 => self.int_vars[var as usize - 10000],
        10004..=10007 => self.float_vars[var as usize - 10004] as i32,
        10008..=10009 => self.int_vars[var as usize - 10008],
        10010 => rand::thread_rng().gen_range((-f32::PI() as i32)..(f32::PI() as i32)),
        10011 => rand::thread_rng().gen_range(0..=1),
        10012 => rand::thread_rng().gen_range(-1..=1),
        10013 => self.position_interpolator.current().x as i32,
        10014 => self.position_interpolator.current().y as i32,
        10015 => self.position_interpolator.current().z as i32,
        10016..=10021 => unimplemented!("global rocking vectors not implemented"),
        10022 => rand::thread_rng().gen::<i32>(),
        _ => unimplemented!("unimplemented variable {var}"),
      },
      Param::Value(value) => value,
    }
  }

  fn param_float(&self, param: Param<f32>) -> f32 {
    match param {
      Param::Variable(var) => {
        match var {
          10000..=10003 => self.int_vars[var as usize - 10000] as f32,
          10004..=10007 => self.float_vars[var as usize - 10004],
          10008..=10009 => self.int_vars[var as usize - 10008] as f32,
          10010 => rand::thread_rng().gen_range(-f32::PI()..=f32::PI()), // rand
          10011 => rand::thread_rng().gen_range(0.0..=1.0),
          10012 => rand::thread_rng().gen_range(-1.0..=1.0),
          10013 => self.position_interpolator.current().x,
          10014 => self.position_interpolator.current().y,
          10015 => self.position_interpolator.current().z,
          10016..=10021 => unimplemented!("global rocking vectors not implemented"),
          10022 => rand::thread_rng().gen::<u32>() as f32,
          _ => unimplemented!("unimplemented variable {var}"),
        }
      }
      Param::Value(value) => value,
    }
  }

  pub(super) fn execute(&mut self, manager: &mut ManagerUpdate) {
    if let Some(interrupt) = self.pending_interrupt {
      
    }
    while let Some(inst) = self.next_instruction() {
      if self.debug {
        log::info!("executing {inst:?}");
      }
      match inst.op {
        Op::Nop => {}
        Op::Delete => {
          self.delete();
          return;
        }
        Op::Sprite(sprite) => self.sprite = sprite,
        Op::Jump { index, time } => {
          self.pc = index;
          self.time = time;
        }
        Op::IntOp { op, dest, input } => {
          let op: fn(i32, i32) -> i32 = match op {
            PrimOp::Set => |_, input| input,
            PrimOp::Add => std::ops::Add::add,
            PrimOp::Sub => std::ops::Sub::sub,
            PrimOp::Mul => std::ops::Mul::mul,
            PrimOp::Div => std::ops::Div::div,
            PrimOp::Mod => std::ops::Rem::rem,
            PrimOp::Rand => |_, end| rand::thread_rng().gen_range(0..end),
          };

          self.store_int(dest, op(self.param_int(Param::Variable(dest)), self.param_int(input)));
        }
        Op::FloatOp { op, dest, input } => {
          let op_func: fn(f32, f32) -> f32 = match op {
            PrimOp::Set => |_, input| input,
            PrimOp::Add => std::ops::Add::add,
            PrimOp::Sub => std::ops::Sub::sub,
            PrimOp::Mul => std::ops::Mul::mul,
            PrimOp::Div => std::ops::Div::div,
            PrimOp::Mod => std::ops::Rem::rem,
            PrimOp::Rand => |_, end| rand::thread_rng().gen_range(0.0..end),
          };

          if self.debug {
            log::info!(
              "{dest} {op:?}Assign {input:?} = {}",
              op_func(
                self.param_float(Param::Variable(dest)),
                self.param_float(input)
              )
            );
          }

          self.store_float(
            dest,
            op_func(self.param_float(Param::Variable(dest)), self.param_float(input)),
          );
        }
        Op::IntSetOp { op, dest, left, right } => {
          let op: fn(i32, i32) -> i32 = match op {
            PrimSetOp::Add => std::ops::Add::add,
            PrimSetOp::Sub => std::ops::Sub::sub,
            PrimSetOp::Mul => std::ops::Mul::mul,
            PrimSetOp::Div => std::ops::Div::div,
            PrimSetOp::Mod => std::ops::Rem::rem,
          };

          self.store_int(dest, op(self.param_int(left), self.param_int(right)));
        }
        Op::FloatSetOp { op, dest, left, right } => {
          let op_func: fn(f32, f32) -> f32 = match op {
            PrimSetOp::Add => std::ops::Add::add,
            PrimSetOp::Sub => std::ops::Sub::sub,
            PrimSetOp::Mul => std::ops::Mul::mul,
            PrimSetOp::Div => std::ops::Div::div,
            PrimSetOp::Mod => std::ops::Rem::rem,
          };

          if self.debug {
            log::info!(
              "{dest} = {left:?} {op:?} {right:?} = {}",
              op_func(self.param_float(left), self.param_float(right))
            );
          }
          self.store_float(dest, op_func(self.param_float(left), self.param_float(right)));
        }
        Op::Pos { x, y, z } => {
          if self.debug {
            log::warn!(
              "pos: {} {} {}",
              self.param_float(x),
              self.param_float(y),
              self.param_float(z)
            )
          }
          self.position_interpolator.reset_immediate(Vec3::new(
            self.param_float(x),
            self.param_float(y),
            self.param_float(z),
          ))
        }
        Op::Rotate { x, y, z } => self.rotation_interpolator.reset_immediate(Vec3::new(
          self.param_float(x),
          self.param_float(y),
          self.param_float(z),
        )),
        Op::Scale { x, y } => {
          self.scale_interpolator.reset_immediate(Vec2::new(self.param_float(x), self.param_float(y)));
        }
        Op::Alpha(value) => self.alpha_interpolator.reset_immediate(self.param_int(value) as f32 / 255.0),
        Op::Color { red, green, blue } => self.color_interpolator.reset_immediate(Vec3::new(
          self.param_int(red) as f32,
          self.param_int(green) as f32,
          self.param_int(blue) as f32,
        )),
        Op::AngleVel { x, y, z } => {
          self.rotation_interpolator.reset(
            Mode::Constant,
            i32::MAX,
            Vec3::new(self.param_float(x), self.param_float(y), self.param_float(z)),
            None,
            None,
          );
        }
        Op::PosTime { time, mode, x, y, z } => {
          let mode = Mode::from_i32(self.param_int(mode)).expect("bad mode passed");
          let pos = Vec3::new(self.param_float(x), self.param_float(y), self.param_float(z));
          // println!("mode set {mode:?} {pos} {}", self.position_interpolator.current());
          self.position_interpolator.reset(mode, self.param_int(time), pos, None, None)
        }
        Op::ColorTime {
          red,
          green,
          blue,
          time,
          mode,
        } => self.color_interpolator.reset(
          Mode::from_i32(self.param_int(mode)).expect("bad mode passed"),
          self.param_int(time),
          Vec3::new(
            self.param_int(red) as f32,
            self.param_int(green) as f32,
            self.param_int(blue) as f32,
          ),
          None,
          None,
        ),
        Op::AlphaTime { time, mode, alpha } => self.alpha_interpolator.reset(
          Mode::from_i32(self.param_int(mode)).expect("bad mode passed"),
          self.param_int(time),
          self.param_int(alpha) as f32,
          None,
          None,
        ),
        Op::RotateTime { time, mode, x, y, z } => self.rotation_interpolator.reset(
          Mode::from_i32(self.param_int(mode)).expect("bad mode passed"),
          self.param_int(time),
          Vec3::new(self.param_float(x), self.param_float(y), self.param_float(z)),
          None,
          None,
        ),
        Op::ScaleTime { time, mode, x, y } => self.scale_interpolator.reset(
          Mode::from_i32(self.param_int(mode)).expect("bad mode passed"),
          self.param_int(time),
          Vec2::new(self.param_float(x), self.param_float(y)),
          None,
          None,
        ),
        Op::Stop => self.waiting_countdown = u32::MAX,
        Op::InterruptLabel(_) => {}
        Op::Anchor { horizontal, vertical } => {
          let axis = |value: i16| match value {
            0 => 0.5,
            1 => 0.0,
            2 => 1.0,
            _ => panic!("invalid anchor point"),
          };

          self.anchor_offset = Vec2::new(axis(horizontal), axis(vertical))
        }
        Op::BlendMode { mode } => self.set_blend_mode(self.param_int(mode)),
        Op::Type { mode } => {
          // todo: rotation modes
          self.sprite_mode = mode;
        }
        Op::Layer { layer } => self.layer = layer as u32,
        Op::Wait { time } => self.waiting_countdown = time as u32,
        Op::CaseReturn => {}
        Op::UnknownBitflag { .. } => {}
        Op::RandMode { mode } => self.random_mode = mode,
        Op::ScriptNew { script } => {
          self.children.push(manager.new_vm(
            self.file.clone(),
            Some(self.position()),
            true,
            false,
            script as usize,
            VmLocation::new_child(self.layer),
          ));
        }
        Op::ScriptNewUI { script } => {
          self.children.push(manager.new_vm(
            self.file.clone(),
            Some(self.position()),
            true,
            false,
            script as usize,
            VmLocation::new_child_ui(self.layer),
          ));
        }
        Op::ScriptNewFront { script } => {
          self.children.push(manager.new_vm(
            self.file.clone(),
            Some(self.position()),
            true,
            false,
            script as usize,
            VmLocation::new_child_front(self.layer),
          ));
        }
        Op::ScriptNewUIFront { script } => {
          self.children.push(manager.new_vm(
            self.file.clone(),
            Some(self.position()),
            true,
            false,
            script as usize,
            VmLocation::new_child_ui_front(self.layer),
          ));
        }
        op => unimplemented!("op not supported: {op:?}"),
      }
    }
  }
}
