use crate::{ContextRef, Runtime, ValueResult};
use oden_js_sys as sys;
use std::ops::Deref;

#[derive(Eq, PartialEq, Debug, Hash)]
pub struct AtomRef {
    pub(crate) atom: sys::JSAtom,
}

impl AtomRef {
    pub(crate) fn from_raw(atom: sys::JSAtom, _ctx: &ContextRef) -> AtomRef {
        AtomRef { atom }
    }

    pub fn dup(&self, context: &ContextRef) -> Atom {
        unsafe {
            sys::JS_DupAtom(context.ctx, self.atom);
        }
        Atom::from_raw(self.atom, context)
    }

    pub fn to_value(&self, context: &ContextRef) -> ValueResult {
        context.check_exception(unsafe { sys::JS_AtomToValue(context.ctx, self.atom) })
    }

    pub fn to_string_value(&self, context: &ContextRef) -> ValueResult {
        context.check_exception(unsafe { sys::JS_AtomToString(context.ctx, self.atom) })
    }
}

pub struct Atom {
    atom: AtomRef,
    rt: Runtime,
}

impl Atom {
    pub(crate) fn from_raw(atom: sys::JSAtom, ctx: &ContextRef) -> Self {
        Atom {
            atom: AtomRef::from_raw(atom, ctx),
            rt: Runtime::from_raw(unsafe { sys::JS_GetRuntime(ctx.ctx) }),
        }
    }

    pub(crate) fn consume(atom: Atom) -> u32 {
        let atom = std::mem::ManuallyDrop::new(atom);
        atom.atom.atom
    }
}

impl Deref for Atom {
    type Target = AtomRef;

    fn deref(&self) -> &Self::Target {
        &self.atom
    }
}

impl Drop for Atom {
    fn drop(&mut self) {
        unsafe {
            sys::JS_FreeAtomRT(self.rt.rt, self.atom.atom);
        }
    }
}

impl PartialEq for Atom {
    fn eq(&self, other: &Self) -> bool {
        self.atom == other.atom
    }
}

impl Eq for Atom {}

impl std::hash::Hash for Atom {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.atom.hash(state)
    }
}
