use std::{cell::Cell, ptr::NonNull, rc::Rc};

use altv_sdk::ffi as sdk;
use autocxx::prelude::*;
use mvalue::ConstMValue;

use crate::{
  alt_resource::AltResource,
  base_objects::{
    col_shape, connection_info,
    extra_pools::{AnyEntity, AnyWorldObject},
    player, vehicle, AnyBaseObject, ped,
  },
  events::helpers::{
    base_event_to_specific, get_non_null_base_object_from_event, get_vehicle_from_event,
  },
  helpers::{
    get_entity_by_ptr, get_non_null_entity_by_ptr, get_non_null_player,
    get_non_null_world_object_by_ptr, get_player, read_cpp_str_vec, read_cpp_vector3, Hash,
    get_non_null_ped,
  },
  resource::Resource,
  vector::Vector3,
  VoidResult,
};

use super::{
  cancellable::CancellableEvent,
  helpers::get_connection_info_from_event,
  // connection_queue::{ConnectionQueueController, ConnectionQueueInfo},
  structs,
};

#[derive(Debug)]
pub struct PlayerConnect {
  pub player: player::PlayerContainer,
}

impl PlayerConnect {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPlayerConnectEvent);
    Self {
      player: get_non_null_player(sdk::CPlayerConnectEvent::GetTarget(event), resource),
    }
  }
}

#[derive(Debug)]
pub struct PlayerDisconnect {
  pub player: player::PlayerContainer,
  pub reason: String,
}

impl PlayerDisconnect {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPlayerDisconnectEvent);
    Self {
      player: get_non_null_player(sdk::CPlayerDisconnectEvent::GetTarget(event), resource),
      reason: sdk::CPlayerDisconnectEvent::GetReason(event).to_string(),
    }
  }
}

#[derive(Debug)]
pub struct ServerStarted {}

impl ServerStarted {
  pub(crate) unsafe fn new(_: altv_sdk::CEventPtr, _: &Resource) -> Self {
    Self {}
  }
}

#[derive(Debug)]
pub struct ResourceStart {
  pub resource: Rc<AltResource>,
}

impl ResourceStart {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CResourceStartEvent);

    let resource_ptr = unsafe { sdk::CResourceStartEvent::GetResource(event) };
    let resource_ptr = NonNull::new(resource_ptr).unwrap();
    let resource = resource.alt_resources.borrow_mut().on_start(resource_ptr);

    Self { resource }
  }
}

#[derive(Debug)]
pub struct ResourceStop {
  pub resource: Rc<AltResource>,
}

impl ResourceStop {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CResourceStopEvent);

    let resource_ptr = unsafe { sdk::CResourceStopEvent::GetResource(event) };
    let resource_ptr = NonNull::new(resource_ptr).unwrap();
    let resource = resource.alt_resources.borrow_mut().on_stop(resource_ptr);

    Self { resource }
  }
}

#[derive(Debug)]
pub struct ColshapeEvent {
  pub col_shape: col_shape::ColShapeMutPtr,
  pub world_object: AnyWorldObject,
  pub state: bool,
}

impl ColshapeEvent {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CColShapeEvent);

    let state = sdk::CColShapeEvent::GetState(event);

    let col_shape = sdk::CColShapeEvent::GetTarget(event);
    let col_shape = NonNull::new(col_shape).unwrap();

    Self {
      col_shape,
      world_object: get_non_null_world_object_by_ptr(
        sdk::CColShapeEvent::GetEntity(event),
        resource,
      ),
      state,
    }
  }
}

#[derive(Debug)]
pub struct ServerScriptEvent {
  pub name: String,
  pub args: Vec<ConstMValue>,
}

impl ServerScriptEvent {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, _: &Resource) -> Self {
    let event = base_event_to_specific!(event, CServerScriptEvent);
    let name = sdk::CServerScriptEvent::GetName(event).to_string();
    Self {
      name,
      args: sdk::CServerScriptEvent::GetArgs(event)
        .into_iter()
        .map(ConstMValue::from)
        .collect(),
    }
  }
}

#[derive(Debug)]
pub struct ClientScriptEvent {
  pub name: String,
  pub player: player::PlayerContainer,
  pub args: Vec<ConstMValue>,
}

impl ClientScriptEvent {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CClientScriptEvent);
    let name = sdk::CClientScriptEvent::GetName(event).to_string();
    let player = get_non_null_player(sdk::CClientScriptEvent::GetTarget(event), resource);

    Self {
      name,
      player,
      args: sdk::CClientScriptEvent::GetArgs(event)
        .into_iter()
        .map(ConstMValue::from)
        .collect(),
    }
  }
}

#[derive(Debug)]
pub struct ConsoleCommandEvent {
  pub name: String,
  pub args: Vec<String>,
}

impl ConsoleCommandEvent {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, _: &Resource) -> Self {
    let event = base_event_to_specific!(event, CConsoleCommandEvent);
    Self {
      name: sdk::CConsoleCommandEvent::GetName(event).to_string(),
      args: read_cpp_str_vec(sdk::CConsoleCommandEvent::GetArgs(event)),
    }
  }
}

#[derive(Debug)]
pub struct WeaponDamageEvent {
  pub source: player::PlayerContainer,
  pub source_entity: Option<AnyEntity>,
  pub target: AnyEntity,
  pub weapon_hash: Hash,
  pub body_part: altv_sdk::PlayerBodyPart,
  pub damage: u32,
  pub shot_offset: Vector3,

  event: *mut sdk::alt::CWeaponDamageEvent,
  cancellable: CancellableEvent,
  custom_damage_set: Cell<bool>,
}

impl WeaponDamageEvent {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let weapon_event = base_event_to_specific!(event, CWeaponDamageEvent);

    Self {
      source: get_non_null_player(sdk::CWeaponDamageEvent::GetSource(weapon_event), resource),
      source_entity: get_entity_by_ptr(
        sdk::CWeaponDamageEvent::GetSourceEntity(weapon_event),
        resource,
      ),
      target: get_non_null_entity_by_ptr(
        sdk::CWeaponDamageEvent::GetTarget(weapon_event),
        resource,
      ),
      weapon_hash: sdk::CWeaponDamageEvent::GetWeaponHash(weapon_event),
      body_part: {
        let raw = sdk::CWeaponDamageEvent::GetBodyPart(weapon_event);
        altv_sdk::PlayerBodyPart::try_from(raw).unwrap()
      },
      damage: sdk::CWeaponDamageEvent::GetDamageValue(weapon_event),
      shot_offset: {
        let raw = sdk::CWeaponDamageEvent::GetShotOffset(weapon_event).within_unique_ptr();
        read_cpp_vector3(raw)
      },

      // internal properties
      cancellable: CancellableEvent::new(event),
      event: weapon_event,
      custom_damage_set: Cell::new(false),
    }
  }

  pub fn set_damage(&self, value: u32) -> VoidResult {
    if self.custom_damage_set.get() {
      anyhow::bail!("Damage cannot be set multiple times")
    }
    self.custom_damage_set.set(true);

    unsafe { sdk::CWeaponDamageEvent::SetDamageValue(self.event, value) }
    Ok(())
  }

  pub fn cancel(&self) -> VoidResult {
    self.cancellable.cancel()
  }
}

#[derive(Debug)]
pub struct PlayerDeath {
  pub player: player::PlayerContainer,
  pub killer: Option<AnyEntity>,
  pub weapon_hash: Hash,
}

impl PlayerDeath {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPlayerDeathEvent);

    Self {
      player: get_non_null_player(sdk::CPlayerDeathEvent::GetTarget(event), resource),
      killer: get_entity_by_ptr(sdk::CPlayerDeathEvent::GetKiller(event), resource),
      weapon_hash: sdk::CPlayerDeathEvent::GetWeapon(event),
    }
  }
}

#[derive(Debug)]
pub struct PlayerDamage {
  pub player: player::PlayerContainer,
  pub attacker: Option<AnyEntity>,
  pub health_damage: u16,
  pub armour_damage: u16,
}

impl PlayerDamage {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPlayerDamageEvent);

    Self {
      player: get_non_null_player(sdk::CPlayerDamageEvent::GetTarget(event), resource),
      attacker: get_entity_by_ptr(sdk::CPlayerDamageEvent::GetAttacker(event), resource),
      health_damage: sdk::CPlayerDamageEvent::GetHealthDamage(event),
      armour_damage: sdk::CPlayerDamageEvent::GetArmourDamage(event),
    }
  }
}

macro_rules! player_enter_or_leave_vehicle {
  ($event:ident) => {
    pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
      let event = base_event_to_specific!(event, $event);

      Self {
        player: get_non_null_player(sdk::$event::GetPlayer(event), resource),
        vehicle: get_vehicle_from_event(sdk::$event::GetTarget(event), resource),
        seat: sdk::$event::GetSeat(event),
      }
    }
  };
}

#[derive(Debug)]
pub struct PlayerEnteringVehicle {
  pub player: player::PlayerContainer,
  pub vehicle: vehicle::VehicleContainer,
  pub seat: u8,
}

impl PlayerEnteringVehicle {
  player_enter_or_leave_vehicle!(CPlayerEnteringVehicleEvent);
}

#[derive(Debug)]
pub struct PlayerEnterVehicle {
  pub player: player::PlayerContainer,
  pub vehicle: vehicle::VehicleContainer,
  pub seat: u8,
}

impl PlayerEnterVehicle {
  player_enter_or_leave_vehicle!(CPlayerEnterVehicleEvent);
}

#[derive(Debug)]
pub struct PlayerLeaveVehicle {
  pub player: player::PlayerContainer,
  pub vehicle: vehicle::VehicleContainer,
  pub seat: u8,
}

impl PlayerLeaveVehicle {
  player_enter_or_leave_vehicle!(CPlayerLeaveVehicleEvent);
}

#[derive(Debug)]
pub struct PlayerChangeVehicleSeat {
  pub player: player::PlayerContainer,
  pub vehicle: vehicle::VehicleContainer,
  pub new_seat: u8,
  pub old_seat: u8,
}

impl PlayerChangeVehicleSeat {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPlayerChangeVehicleSeatEvent);

    Self {
      player: get_non_null_player(
        sdk::CPlayerChangeVehicleSeatEvent::GetPlayer(event),
        resource,
      ),
      vehicle: get_vehicle_from_event(
        sdk::CPlayerChangeVehicleSeatEvent::GetTarget(event),
        resource,
      ),
      new_seat: sdk::CPlayerChangeVehicleSeatEvent::GetNewSeat(event),
      old_seat: sdk::CPlayerChangeVehicleSeatEvent::GetOldSeat(event),
    }
  }
}

#[derive(Debug)]
pub struct PlayerWeaponChange {
  pub player: player::PlayerContainer,
  pub new_weapon_hash: Hash,
  pub old_weapon_hash: Hash,
}

impl PlayerWeaponChange {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CPlayerWeaponChangeEvent);

    Self {
      player: get_non_null_player(sdk::CPlayerWeaponChangeEvent::GetTarget(event), resource),
      new_weapon_hash: sdk::CPlayerWeaponChangeEvent::GetNewWeapon(event),
      old_weapon_hash: sdk::CPlayerWeaponChangeEvent::GetOldWeapon(event),
    }
  }
}

#[derive(Debug)]
pub struct PlayerConnectDenied {
  pub reason: altv_sdk::PlayerConnectDeniedReason,
  pub name: String,
  pub ip: String,
  pub password_hash: u64,
  pub is_debug: bool,
  pub branch: String,
  pub major_version: u16,
  pub minor_version: u16,
  pub cdn_url: String,
  pub discord_id: i64,
}

impl PlayerConnectDenied {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, _: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPlayerConnectDeniedEvent);

    use sdk::CPlayerConnectDeniedEvent::*;
    Self {
      reason: {
        let raw = GetReason(event);
        altv_sdk::PlayerConnectDeniedReason::try_from(raw).unwrap()
      },
      name: GetName(event).to_string(),
      ip: GetIp(event).to_string(),
      password_hash: GetPasswordHash(event),
      is_debug: IsDebug(event),
      branch: GetBranch(event).to_string(),
      major_version: GetVersionMajor(event),
      minor_version: GetVersionMinor(event),
      cdn_url: GetCdnUrl(event).to_string(),
      discord_id: GetDiscordId(event),
    }
  }
}

#[derive(Debug)]
pub struct PlayerSpawn {
  pub player: player::PlayerContainer,
}

impl PlayerSpawn {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPlayerSpawnEvent);

    Self {
      player: get_non_null_player(sdk::CPlayerSpawnEvent::GetPlayer(event), resource),
    }
  }
}

#[derive(Debug)]
pub struct PlayerRequestControl {
  pub player: player::PlayerContainer,
  pub entity: AnyEntity,
}

impl PlayerRequestControl {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPlayerRequestControlEvent);

    Self {
      player: get_non_null_player(sdk::CPlayerRequestControlEvent::GetPlayer(event), resource),
      entity: get_non_null_entity_by_ptr(
        sdk::CPlayerRequestControlEvent::GetTarget(event),
        resource,
      ),
    }
  }
}

#[derive(Debug)]
pub struct PlayerDimensionChange {
  pub player: player::PlayerContainer,
  pub new_dimension: i32,
  pub old_dimension: i32,
}

impl PlayerDimensionChange {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPlayerDimensionChangeEvent);

    Self {
      player: get_non_null_player(sdk::CPlayerDimensionChangeEvent::GetTarget(event), resource),
      new_dimension: sdk::CPlayerDimensionChangeEvent::GetNewDimension(event),
      old_dimension: sdk::CPlayerDimensionChangeEvent::GetOldDimension(event),
    }
  }
}

#[derive(Debug)]
pub struct PlayerChangeInteriorEvent {
  pub player: player::PlayerContainer,
  pub new_interior: u32,
  pub old_interior: u32,
}

impl PlayerChangeInteriorEvent {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPlayerChangeInteriorEvent);

    Self {
      player: get_non_null_player(sdk::CPlayerChangeInteriorEvent::GetTarget(event), resource),
      new_interior: sdk::CPlayerChangeInteriorEvent::GetNewInteriorLocation(event),
      old_interior: sdk::CPlayerChangeInteriorEvent::GetOldInteriorLocation(event),
    }
  }
}

#[derive(Debug)]
pub struct StartProjectileEvent {
  pub player: player::PlayerContainer,
  pub weapon_hash: Hash,
  pub ammo_hash: Hash,
  pub pos: Vector3,
  pub dir: Vector3,

  cancellable: CancellableEvent,
}

impl StartProjectileEvent {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CStartProjectileEvent);

    use sdk::CStartProjectileEvent::*;
    Self {
      player: get_non_null_player(GetSource(event), resource),
      weapon_hash: GetWeaponHash(event),
      ammo_hash: GetAmmoHash(event),
      pos: {
        let raw = GetStartPosition(event).within_unique_ptr();
        read_cpp_vector3(raw)
      },
      dir: {
        let raw = GetDirection(event).within_unique_ptr();
        read_cpp_vector3(raw)
      },

      cancellable: CancellableEvent::new(base_event),
    }
  }

  pub fn cancel(&self) -> VoidResult {
    self.cancellable.cancel()
  }
}

#[derive(Debug)]
pub struct FireEvent {
  pub player: player::PlayerContainer,
  pub fires: Vec<structs::FireInfo>,

  cancellable: CancellableEvent,
}

impl FireEvent {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CFireEvent);

    use sdk::CFireEvent::*;
    Self {
      player: get_non_null_player(GetSource(event), resource),
      fires: {
        GetFires(event)
          .iter()
          .map(|fire| {
            let pos = sdk::read_fire_info_pos(fire).within_unique_ptr();
            let pos = read_cpp_vector3(pos);

            structs::FireInfo {
              pos,
              weapon_hash: sdk::read_fire_info_weapon_hash(fire),
            }
          })
          .collect()
      },

      cancellable: CancellableEvent::new(base_event),
    }
  }

  pub fn cancel(&self) -> VoidResult {
    self.cancellable.cancel()
  }
}

#[derive(Debug)]
pub struct ExplosionEvent {
  pub player: player::PlayerContainer,
  pub target: Option<AnyEntity>,
  pub pos: Vector3,
  pub explosion_type: altv_sdk::ExplosionType,
  pub explosion_fx: u32,

  cancellable: CancellableEvent,
}

impl ExplosionEvent {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CExplosionEvent);

    use sdk::CExplosionEvent::*;
    Self {
      player: get_non_null_player(GetSource(event), resource),
      target: get_entity_by_ptr(GetTarget(event), resource),
      pos: {
        let raw = GetPosition(event).within_unique_ptr();
        read_cpp_vector3(raw)
      },
      explosion_type: {
        let raw = GetExplosionType(event);
        altv_sdk::ExplosionType::try_from(raw).unwrap()
      },
      explosion_fx: GetExplosionFX(event),

      cancellable: CancellableEvent::new(base_event),
    }
  }

  pub fn cancel(&self) -> VoidResult {
    self.cancellable.cancel()
  }
}

#[derive(Debug)]
pub struct ConnectionQueueAdd {
  pub info: connection_info::ConnectionInfoContainer,
}

impl ConnectionQueueAdd {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CConnectionQueueAddEvent);

    Self {
      info: get_connection_info_from_event(
        sdk::CConnectionQueueAddEvent::GetConnectionInfo(event),
        resource,
      ),
    }
  }
}

#[derive(Debug)]
pub struct ConnectionQueueRemove {
  pub info: connection_info::ConnectionInfoContainer,
}

impl ConnectionQueueRemove {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CConnectionQueueRemoveEvent);

    Self {
      info: get_connection_info_from_event(
        sdk::CConnectionQueueRemoveEvent::GetConnectionInfo(event),
        resource,
      ),
    }
  }
}

#[derive(Debug)]
pub struct VehicleAttach {
  pub vehicle: vehicle::VehicleContainer,
  pub attached: vehicle::VehicleContainer,
}

impl VehicleAttach {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CVehicleAttachEvent);

    Self {
      vehicle: get_vehicle_from_event(sdk::CVehicleAttachEvent::GetTarget(event), resource),
      attached: get_vehicle_from_event(sdk::CVehicleAttachEvent::GetAttached(event), resource),
    }
  }
}

#[derive(Debug)]
pub struct VehicleDetach {
  pub vehicle: vehicle::VehicleContainer,
  pub detached: vehicle::VehicleContainer,
}

impl VehicleDetach {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CVehicleDetachEvent);

    Self {
      vehicle: get_vehicle_from_event(sdk::CVehicleDetachEvent::GetTarget(event), resource),
      detached: get_vehicle_from_event(sdk::CVehicleDetachEvent::GetDetached(event), resource),
    }
  }
}

#[derive(Debug)]
pub struct VehicleDestroy {
  pub vehicle: vehicle::VehicleContainer,
}

impl VehicleDestroy {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CVehicleDestroyEvent);

    Self {
      vehicle: get_vehicle_from_event(sdk::CVehicleDestroyEvent::GetTarget(event), resource),
    }
  }
}

#[derive(Debug)]
pub struct VehicleDamage {
  pub vehicle: vehicle::VehicleContainer,
  pub damager: Option<AnyEntity>,
  pub body_health_damage: u32,
  pub body_additional_health_damage: u32,
  pub engine_health_damage: u32,
  pub petrol_tank_health_damage: u32,
  pub weapon: Hash,
}

impl VehicleDamage {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CVehicleDamageEvent);

    use sdk::CVehicleDamageEvent::*;
    Self {
      vehicle: get_vehicle_from_event(GetTarget(event), resource),
      damager: get_entity_by_ptr(GetDamager(event), resource),
      body_health_damage: GetBodyHealthDamage(event),
      body_additional_health_damage: GetBodyAdditionalHealthDamage(event),
      engine_health_damage: GetEngineHealthDamage(event),
      petrol_tank_health_damage: GetPetrolTankHealthDamage(event),
      weapon: GetDamagedWith(event),
    }
  }
}

#[derive(Debug)]
pub struct VehicleHorn {
  pub vehicle: vehicle::VehicleContainer,
  pub player: player::PlayerContainer,
  pub state: bool,
}

impl VehicleHorn {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CVehicleHornEvent);

    use sdk::CVehicleHornEvent::*;
    Self {
      vehicle: get_vehicle_from_event(GetTarget(event), resource),
      player: get_non_null_player(GetReporter(event), resource),
      state: GetToggle(event),
    }
  }
}

#[derive(Debug)]
pub struct VehicleSiren {
  pub vehicle: vehicle::VehicleContainer,
  pub state: bool,
}

impl VehicleSiren {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CVehicleSirenEvent);

    Self {
      vehicle: get_vehicle_from_event(sdk::CVehicleSirenEvent::GetTarget(event), resource),
      state: sdk::CVehicleSirenEvent::GetToggle(event),
    }
  }
}

#[derive(Debug)]
pub struct NetownerChange {
  pub entity: AnyEntity,
  pub new_net_owner: Option<player::PlayerContainer>,
  pub old_net_owner: Option<player::PlayerContainer>,
}

impl NetownerChange {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CNetOwnerChangeEvent);

    use sdk::CNetOwnerChangeEvent::*;
    Self {
      entity: get_non_null_entity_by_ptr(GetTarget(event), resource),
      new_net_owner: get_player(GetNewOwner(event), resource),
      old_net_owner: get_player(GetOldOwner(event), resource),
    }
  }
}

#[derive(Debug)]
pub struct GlobalMetaChange {
  pub key: String,
  pub new_value: ConstMValue,
  pub old_value: ConstMValue,
}

impl GlobalMetaChange {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, _: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CGlobalMetaDataChangeEvent);

    use sdk::CGlobalMetaDataChangeEvent::*;
    Self {
      key: GetKey(event).to_string(),
      new_value: ConstMValue::new(GetVal(event).within_unique_ptr()),
      old_value: ConstMValue::new(GetOldVal(event).within_unique_ptr()),
    }
  }
}

#[derive(Debug)]
pub struct GlobalSyncedMetaChange {
  pub key: String,
  pub new_value: ConstMValue,
  pub old_value: ConstMValue,
}

impl GlobalSyncedMetaChange {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, _: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CGlobalSyncedMetaDataChangeEvent);

    use sdk::CGlobalSyncedMetaDataChangeEvent::*;
    Self {
      key: GetKey(event).to_string(),
      new_value: ConstMValue::new(GetVal(event).within_unique_ptr()),
      old_value: ConstMValue::new(GetOldVal(event).within_unique_ptr()),
    }
  }
}

#[derive(Debug)]
pub struct SyncedMetaChange {
  pub key: String,
  pub base_object: AnyBaseObject,
  pub new_value: ConstMValue,
  pub old_value: ConstMValue,
}

impl SyncedMetaChange {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CSyncedMetaDataChangeEvent);

    use sdk::CSyncedMetaDataChangeEvent::*;
    Self {
      key: GetKey(event).to_string(),
      base_object: get_non_null_base_object_from_event(GetTarget(event), resource),
      new_value: ConstMValue::new(GetVal(event).within_unique_ptr()),
      old_value: ConstMValue::new(GetOldVal(event).within_unique_ptr()),
    }
  }
}

#[derive(Debug)]
pub struct StreamSyncedMetaChange {
  pub key: String,
  pub base_object: AnyBaseObject,
  pub new_value: ConstMValue,
  pub old_value: ConstMValue,
}

impl StreamSyncedMetaChange {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CStreamSyncedMetaDataChangeEvent);

    use sdk::CStreamSyncedMetaDataChangeEvent::*;
    Self {
      key: GetKey(event).to_string(),
      base_object: get_non_null_base_object_from_event(GetTarget(event), resource),
      new_value: ConstMValue::new(GetVal(event).within_unique_ptr()),
      old_value: ConstMValue::new(GetOldVal(event).within_unique_ptr()),
    }
  }
}

#[derive(Debug)]
pub struct LocalSyncedMetaChange {
  pub key: String,
  pub player: player::PlayerContainer,
  pub new_value: ConstMValue,
  pub old_value: ConstMValue,
}

impl LocalSyncedMetaChange {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CLocalMetaDataChangeEvent);

    use sdk::CLocalMetaDataChangeEvent::*;
    Self {
      key: GetKey(event).to_string(),
      player: get_non_null_player(GetTarget(event), resource),
      new_value: ConstMValue::new(GetVal(event).within_unique_ptr()),
      old_value: ConstMValue::new(GetOldVal(event).within_unique_ptr()),
    }
  }
}

#[derive(Debug)]
pub struct VoiceConnectionEvent {
  pub state: altv_sdk::VoiceConnectionState,
}

impl VoiceConnectionEvent {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, _: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CVoiceConnectionEvent);
    Self {
      state: altv_sdk::VoiceConnectionState::try_from(sdk::CVoiceConnectionEvent::GetState(event))
        .unwrap(),
    }
  }
}

#[derive(Debug)]
pub struct RequestSyncedScene {
  pub source: player::PlayerContainer,
  pub scene_id: i32,

  cancellable: CancellableEvent,
}

impl RequestSyncedScene {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CRequestSyncedSceneEvent);
    Self {
      source: get_non_null_player(sdk::CRequestSyncedSceneEvent::GetSource(event), resource),
      scene_id: sdk::CRequestSyncedSceneEvent::GetSceneID(event),
      cancellable: CancellableEvent::new(base_event),
    }
  }

  pub fn cancel(&self) -> VoidResult {
    self.cancellable.cancel()
  }
}

#[derive(Debug)]
pub struct StartSyncedScene {
  pub source: player::PlayerContainer,
  pub scene_id: i32,
  pub start_pos: Vector3,
  pub start_rot: Vector3,
  pub anim_dict_hash: Hash,
  pub entity_and_anim_hash_pairs: Vec<(AnyEntity, Hash)>,

  cancellable: CancellableEvent,
}

impl StartSyncedScene {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CStartSyncedSceneEvent);

    use sdk::CStartSyncedSceneEvent::*;
    Self {
      source: get_non_null_player(GetSource(event), resource),
      scene_id: GetSceneID(event),
      start_pos: {
        let raw = GetStartPosition(event).within_unique_ptr();
        read_cpp_vector3(raw)
      },
      start_rot: {
        let raw = GetStartRotation(event).within_unique_ptr();
        read_cpp_vector3(raw)
      },
      anim_dict_hash: GetAnimDictHash(event),
      entity_and_anim_hash_pairs: {
        let raw = GetEntityAndAnimHashPairs(event).within_unique_ptr();
        let raw = sdk::read_entity_anim_hash_pairs(raw.as_ref().unwrap());
        let mut pairs = Vec::new();
        for e in raw.into_iter() {
          let entity =
            get_non_null_entity_by_ptr(sdk::read_entity_anim_hash_pair_entity(e), resource);
          let anim_hash = unsafe { sdk::read_entity_anim_hash_pair_anim_hash(e) };
          pairs.push((entity, anim_hash));
        }
        pairs
      },

      cancellable: CancellableEvent::new(base_event),
    }
  }

  pub fn cancel(&self) -> VoidResult {
    self.cancellable.cancel()
  }
}

#[derive(Debug)]
pub struct StopSyncedScene {
  pub source: player::PlayerContainer,
  pub scene_id: i32,

  cancellable: CancellableEvent,
}

impl StopSyncedScene {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CStopSyncedSceneEvent);
    Self {
      source: get_non_null_player(sdk::CStopSyncedSceneEvent::GetSource(event), resource),
      scene_id: sdk::CStopSyncedSceneEvent::GetSceneID(event),

      cancellable: CancellableEvent::new(base_event),
    }
  }

  pub fn cancel(&self) -> VoidResult {
    self.cancellable.cancel()
  }
}

#[derive(Debug)]
pub struct UpdateSyncedScene {
  pub source: player::PlayerContainer,
  pub scene_id: i32,
  pub start_rate: f32,

  cancellable: CancellableEvent,
}

impl UpdateSyncedScene {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CUpdateSyncedSceneEvent);
    Self {
      source: get_non_null_player(sdk::CUpdateSyncedSceneEvent::GetSource(event), resource),
      scene_id: sdk::CUpdateSyncedSceneEvent::GetSceneID(event),
      start_rate: sdk::CUpdateSyncedSceneEvent::GetStartRate(event),

      cancellable: CancellableEvent::new(base_event),
    }
  }

  pub fn cancel(&self) -> VoidResult {
    self.cancellable.cancel()
  }
}

#[derive(Debug)]
pub struct ClientDeleteObjectEvent {
  pub source: player::PlayerContainer,

  cancellable: CancellableEvent,
}

impl ClientDeleteObjectEvent {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CClientDeleteObjectEvent);
    Self {
      source: get_non_null_player(sdk::CClientDeleteObjectEvent::GetTarget(event), resource),

      cancellable: CancellableEvent::new(base_event),
    }
  }

  pub fn cancel(&self) -> VoidResult {
    self.cancellable.cancel()
  }
}

#[derive(Debug)]
pub struct ClientRequestObjectEvent {
  pub source: player::PlayerContainer,
  pub model: Hash,
  pub pos: Vector3,

  cancellable: CancellableEvent,
}

impl ClientRequestObjectEvent {
  pub(crate) unsafe fn new(base_event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(base_event, CClientRequestObjectEvent);
    Self {
      source: get_non_null_player(sdk::CClientRequestObjectEvent::GetTarget(event), resource),
      model: sdk::CClientRequestObjectEvent::GetModel(event),
      pos: {
        let raw = sdk::CClientRequestObjectEvent::GetPosition(event).within_unique_ptr();
        read_cpp_vector3(raw)
      },

      cancellable: CancellableEvent::new(base_event),
    }
  }

  pub fn cancel(&self) -> VoidResult {
    self.cancellable.cancel()
  }
}

#[derive(Debug)]
pub struct PlayerHeal {
  pub player: player::PlayerContainer,
  pub old_health: u16,
  pub new_health: u16,
  pub old_armour: u16,
  pub new_armour: u16,
}

impl PlayerHeal {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPlayerHealEvent);

    use sdk::CPlayerHealEvent as E;
    Self {
      player: get_non_null_player(E::GetTarget(event), resource),
      old_health: E::GetOldHealth(event),
      new_health: E::GetNewHealth(event),
      old_armour: E::GetOldArmour(event),
      new_armour: E::GetNewArmour(event),
    }
  }
}

#[derive(Debug)]
pub struct PedDeath {
  pub ped: ped::PedContainer,
  pub killer: Option<AnyEntity>,
  pub weapon_hash: Hash,
}

impl PedDeath {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPedDeathEvent);

    use sdk::CPedDeathEvent as E;
    Self {
      ped: get_non_null_ped(E::GetTarget(event), resource),
      killer: get_entity_by_ptr(E::GetKiller(event), resource),
      weapon_hash: E::GetWeapon(event),
    }
  }
}

#[derive(Debug)]
pub struct GivePedScriptedTask {
  pub source: player::PlayerContainer,
  pub ped: ped::PedContainer,
  pub task_type: u32,
}

impl GivePedScriptedTask {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CGivePedScriptedTaskEvent);

    use sdk::CGivePedScriptedTaskEvent as E;
    Self {
      source: get_non_null_player(E::GetSource(event), resource),
      ped: get_non_null_ped(E::GetTarget(event), resource),
      task_type: E::GetTaskType(event),
    }
  }
}

#[derive(Debug)]
pub struct PedDamage {
  pub ped: ped::PedContainer,
  pub attacker: Option<AnyEntity>,
  pub health_damage: u16,
  pub armour_damage: u16,
  pub weapon_hash: Hash,
}

impl PedDamage {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPedDamageEvent);

    use sdk::CPedDamageEvent as E;
    Self {
      ped: get_non_null_ped(E::GetTarget(event), resource),
      attacker: get_entity_by_ptr(E::GetAttacker(event), resource),
      health_damage: E::GetHealthDamage(event),
      armour_damage: E::GetHealthDamage(event),
      weapon_hash: E::GetWeapon(event),
    }
  }
}

#[derive(Debug)]
pub struct PedHeal {
  pub ped: ped::PedContainer,
  pub new_health: u16,
  pub old_health: u16,
  pub new_armour: u16,
  pub old_armour: u16,
}

impl PedHeal {
  pub(crate) unsafe fn new(event: altv_sdk::CEventPtr, resource: &Resource) -> Self {
    let event = base_event_to_specific!(event, CPedHealEvent);

    use sdk::CPedHealEvent as E;
    Self {
      ped: get_non_null_ped(E::GetTarget(event), resource),
      new_health: E::GetNewHealth(event),
      old_health: E::GetOldHealth(event),
      new_armour: E::GetNewArmour(event),
      old_armour: E::GetOldArmour(event),
    }
  }
}
