use std::{marker::PhantomData, sync::atomic::{AtomicBool, AtomicUsize}};

use mm::{aslr::aslr_vm_layout, traits::SlotExtra, MemoryManager};
use mmtk::{MMTKBuilder, MMTK};
use threading::{initialize_threading, ThreadManager};

pub mod machine_context;
pub mod mm;
pub mod object_model;
pub mod options;
pub mod semaphore;
pub mod sync;
pub mod threading;
pub mod platform;

#[cfg(feature="uncooperative")]
pub mod bdwgc_shim;

pub trait VirtualMachine: Sized + 'static + Send + Sync {
    type ThreadContext: threading::ThreadContext<Self>;
    type BlockAdapterList: threading::BlockAdapterList<Self>;
    type Metadata: object_model::metadata::Metadata<Self>;
    type Slot: SlotExtra;

    const MAX_ALIGNMENT: usize = 16;
    const MIN_ALIGNMENT: usize = 8;

    /// Does this VM use conservative tracing? If `true` then VM can
    /// query VO-bit (valid-object bit) to check if an object is live
    /// during tracing work.
    ///
    /// Note that this is distinct from conservative stack scanning. When
    /// collecting roots VO-bits are always available.
    ///
    /// Read more: [ObjectModel::NEED_VO_BITS_DURING_TRACING](mmtk::vm::ObjectModel::NEED_VO_BITS_DURING_TRACING).
    ///
    /// # Note
    ///
    /// - [`InternalPointer`](mm::conservative_roots::InternalPointer) can only be used when this is `true`.
    #[cfg(feature = "cooperative")]
    const CONSERVATIVE_TRACING: bool = false;

    /// Get currently active VM instance.
    ///
    /// # Notes
    ///
    /// At the moment we assume only one active VM per process. This can be changed in the future once MMTk supports
    /// instances. In that case this function can return active VM for current thread instead of one global instance.
    fn get() -> &'static Self;

    fn vmkit(&self) -> &VMKit<Self>;

    /// Prepare for another round of root scanning in the same GC.
    ///
    /// For details: [Scanning::prepare_for_roots_re_scanning](mmtk::vm::Scanning::prepare_for_roots_re_scanning)
    fn prepare_for_roots_re_scanning();

    /// MMTk calls this method at the first time during a collection that thread's stacks have been scanned. This can be used (for example) to clean up obsolete compiled methods that are no longer being executed.
    fn notify_initial_thread_scan_complete(partial_scan: bool, tls: mmtk::util::VMWorkerThread);
    /// Process weak references.
    ///
    /// This function is called after a transitive closure is completed.
    ///
    /// For details: [Scanning::process_weak_refs](mmtk::vm::Scanning::process_weak_refs)
    fn process_weak_refs(
        _worker: &mut mmtk::scheduler::GCWorker<MemoryManager<Self>>,
        _tracer_context: impl mmtk::vm::ObjectTracerContext<MemoryManager<Self>>,
    ) -> bool {
        false
    }

    fn forward_weak_refs(
        _worker: &mut mmtk::scheduler::GCWorker<MemoryManager<Self>>,
        _tracer_context: impl mmtk::vm::ObjectTracerContext<MemoryManager<Self>>,
    );
    /// Scan one mutator for stack roots.
    ///
    /// For details: [Scanning::scan_roots_in_mutator_thread](mmtk::vm::Scanning::scan_roots_in_mutator_thread)
    fn scan_roots_in_mutator_thread(
        tls: mmtk::util::VMWorkerThread,
        mutator: &'static mut mmtk::Mutator<MemoryManager<Self>>,
        factory: impl mmtk::vm::RootsWorkFactory<<MemoryManager<Self> as mmtk::vm::VMBinding>::VMSlot>,
    );

    /// Scan VM-specific roots.
    ///
    /// For details: [Scanning::scan_vm_specific_roots](mmtk::vm::Scanning::scan_vm_specific_roots)
    fn scan_vm_specific_roots(
        tls: mmtk::util::VMWorkerThread,
        factory: impl mmtk::vm::RootsWorkFactory<<MemoryManager<Self> as mmtk::vm::VMBinding>::VMSlot>,
    );

    /// A hook for the VM to do work after forwarding objects.
    fn post_forwarding(tls: mmtk::util::VMWorkerThread) {
        let _ = tls;
    }

    fn schedule_finalization(tls: mmtk::util::VMWorkerThread) {
        let _ = tls;
    }

    fn vm_live_bytes() -> usize {
        0
    }

    fn out_of_memory(tls: mmtk::util::VMThread, err_kind: mmtk::util::alloc::AllocationError) {
        let _ = tls;
        let _ = err_kind;
        eprintln!("Out of memory: {:?}", err_kind);
        std::process::exit(1);
    }
}

pub struct VMKit<VM: VirtualMachine> {
    thread_manager: ThreadManager<VM>,
    pub mmtk: MMTK<MemoryManager<VM>>,
    pub(crate) collector_started: AtomicBool,
    marker: PhantomData<VM>,
    gc_disabled_depth: AtomicUsize,
}

impl<VM: VirtualMachine> VMKit<VM> {
    pub fn new(builder: &mut MMTKBuilder) -> Self {
        initialize_threading::<VM>();
        let vm_layout = aslr_vm_layout(&mut builder.options);
        builder.set_vm_layout(vm_layout);
        VMKit {
            mmtk: builder.build(),
            marker: PhantomData,
            collector_started: AtomicBool::new(false),
            thread_manager: ThreadManager::new(),
            gc_disabled_depth: AtomicUsize::new(0),
        }
    }

    pub(crate) fn are_collector_threads_spawned(&self) -> bool {
        self.collector_started.load(atomic::Ordering::Relaxed)
    }

    pub fn thread_manager(&self) -> &ThreadManager<VM> {
        &self.thread_manager
    }
}