use core::{
  marker::PhantomData,
  sync::atomic::{AtomicBool, Ordering},
};

use alloc::sync::Arc;
use fermium::prelude::*;

use crate::error::{get_error, SdlError};

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct InitFlags(SDL_InitFlags);
impl InitFlags {
  pub const AUDIO: Self = Self(SDL_INIT_AUDIO);
  pub const EVENTS: Self = Self(SDL_INIT_EVENTS);
  pub const EVERYTHING: Self = Self(SDL_INIT_EVERYTHING);
  pub const GAMECONTROLLER: Self = Self(SDL_INIT_GAMECONTROLLER);
  pub const HAPTIC: Self = Self(SDL_INIT_HAPTIC);
  pub const JOYSTICK: Self = Self(SDL_INIT_JOYSTICK);
  pub const SENSOR: Self = Self(SDL_INIT_SENSOR);
  pub const TIMER: Self = Self(SDL_INIT_TIMER);
  pub const VIDEO: Self = Self(SDL_INIT_VIDEO);
}
impl core::ops::BitOr for InitFlags {
  type Output = Self;
  #[inline]
  fn bitor(self, rhs: Self) -> Self::Output {
    Self(self.0 | rhs.0)
  }
}

static SDL_IS_ACTIVE: AtomicBool = AtomicBool::new(false);

#[repr(transparent)]
pub(crate) struct SdlInit(PhantomData<*mut ()>);
impl SdlInit {
  #[inline]
  pub fn try_new_arc(flags: InitFlags) -> Result<Arc<Self>, SdlError> {
    #[cfg(any(target_os = "macos", target_os = "ios"))]
    {
      use objc::{class, msg_send, sel, sel_impl};
      let is_main: bool = unsafe { msg_send![class!(NSThread), isMainThread] };
      if !is_main {
        return Err(SdlError::new("beryllium: can only be init on the main thread."));
      }
    }
    match SDL_IS_ACTIVE.compare_exchange(false, true, Ordering::Acquire, Ordering::Acquire) {
      Ok(_) => {
        let ret = unsafe { SDL_Init(flags.0) };
        if ret == 0 {
          Ok(Arc::new(Self(PhantomData)))
        } else {
          Err(get_error())
        }
      }
      Err(_) => Err(SdlError::new("beryllium: Double initialization.")),
    }
  }
}
impl Drop for SdlInit {
  #[inline]
  fn drop(&mut self) {
    unsafe { SDL_Quit() }
    SDL_IS_ACTIVE.store(false, Ordering::Release);
  }
}
