use std::ptr::null;

use crate::gb::GameBoy;

/// Static mutable reference to the global instance of the
/// Game Boy emulator, going to be used for global diagnostics.
static mut GLOBAL_INSTANCE: *const GameBoy = null();

impl GameBoy {
    /// Sets the current instance as the one going to be used
    /// in panic diagnostics.
    pub fn set_diag(&self) {
        self.set_global();
    }

    /// Dumps the diagnostics for the global instance of the
    /// Boytacean emulator into stdout.
    pub fn dump_diagnostics() {
        if let Some(gb) = Self::global() {
            gb.dump_diagnostics_s();
        }
    }

    /// Obtains the global instance of the Game Boy emulator
    /// ready to be used in diagnostics.
    ///
    /// If the global instance is not set, it will return `None`.
    fn global() -> Option<&'static Self> {
        unsafe {
            if GLOBAL_INSTANCE.is_null() {
                None
            } else {
                Some(&*GLOBAL_INSTANCE)
            }
        }
    }

    /// Sets the current instance as the global one going to
    /// be used in panic diagnostics.
    fn set_global(&self) {
        unsafe {
            GLOBAL_INSTANCE = self;
        }
    }

    fn dump_diagnostics_s(&self) {
        println!("Dumping Boytacean diagnostics:");
        println!("{}", self.description_debug());
    }
}

#[macro_export]
macro_rules! panic_gb {
    ($msg:expr) => {
        {
            $crate::gb::GameBoy::dump_diagnostics();
            panic!($msg);
        }
    };
    ($fmt:expr, $($arg:tt)*) => {
        {
            $crate::gb::GameBoy::dump_diagnostics();
            panic!($fmt, $($arg)*);
        }
    };
}

#[macro_export]
macro_rules! assert_gb {
    ($cond:expr, $fmt:expr, $($arg:tt)*) => {
        if !$cond {
            if !$cond {
                $crate::gb::GameBoy::dump_diagnostics();
                panic!($fmt, $($arg)*);
            }
        }
    };
    ($cond:expr) => {
        assert_gb!($cond, stringify!($cond));
    };
}

#[cfg(feature = "pedantic")]
#[macro_export]
macro_rules! assert_pedantic_gb {
    ($cond:expr, $fmt:expr, $($arg:tt)*) => {
        $crate::assert_gb!($cond, $fmt, $($arg)*);
    };
    ($cond:expr) => {
        $crate::assert_gb!($cond);
    };
}

#[cfg(not(feature = "pedantic"))]
#[macro_export]
macro_rules! assert_pedantic_gb {
    ($cond:expr, $fmt:expr, $($arg:tt)*) => {
        ()
    };
    ($cond:expr) => {
        ()
    };
}