use crate::{ContextRef, Result, Runtime, Value};
use oden_js_sys as sys;
use std::ffi::CString;
use std::ops::{Deref, DerefMut};

pub mod loader;
pub mod native;

#[derive(Debug, Clone)]
pub struct ModuleRef {
    pub(crate) module: *mut sys::JSModuleDef,
}

impl ModuleRef {
    pub(crate) fn from_raw(module: *mut sys::JSModuleDef) -> ModuleRef {
        ModuleRef { module }
    }

    fn eval_self(&self, context: &ContextRef) -> Result<()> {
        let result = unsafe {
            let v = sys::JSValue {
                u: sys::JSValueUnion {
                    ptr: self.module as *mut _,
                },
                tag: sys::JS_TAG_MODULE as i64,
            };

            sys::JS_DupValue(context.ctx, v);
            sys::JS_EvalFunction(context.ctx, v)
        };
        context.check_exception(result)?;
        Ok(())
    }

    pub fn get_export(&self, context: &ContextRef, export: &str) -> Result<Value> {
        self.eval_self(context)?;
        let c_value = CString::new(export)?;
        unsafe {
            context.check_exception(sys::JS_GetModuleExport(
                context.ctx,
                self.module,
                c_value.as_ptr(),
            ))
        }
    }
}

pub struct Module {
    m: ModuleRef,

    #[allow(dead_code)]
    runtime: Runtime,
}

impl Module {
    pub(crate) fn from_raw(module: *mut sys::JSModuleDef, runtime: Runtime) -> Module {
        Module {
            m: ModuleRef::from_raw(module),
            runtime,
        }
    }
}

impl Deref for Module {
    type Target = ModuleRef;

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

impl DerefMut for Module {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.m
    }
}
