use super::ModuleRef;
use crate::{throw_error, ContextRef, Error, Result};
use oden_js_sys as sys;
use std::path::Path;

pub enum ModuleSource<'a> {
    Native(&'a ModuleRef),
    JavaScript(String),
}

pub trait ModuleLoader {
    fn load(&self, context: &ContextRef, name: &str) -> Result<ModuleSource>;
}

pub struct DefaultModuleLoader {}

impl DefaultModuleLoader {
    pub fn new() -> DefaultModuleLoader {
        DefaultModuleLoader {}
    }
}

impl ModuleLoader for DefaultModuleLoader {
    fn load(&self, _context: &ContextRef, name: &str) -> Result<ModuleSource> {
        // Attempt to open the file.
        let path = Path::new(name);
        match std::fs::read_to_string(path) {
            Ok(str) => Ok(ModuleSource::JavaScript(str)),
            Err(e) => Err(Error::IOError(e)),
        }
    }
}

pub(crate) fn load_module(
    context: &ContextRef,
    name: &str,
    loader: &Box<dyn ModuleLoader>,
) -> *mut sys::JSModuleDef {
    match loader.load(context, name) {
        Ok(ModuleSource::Native(native)) => native.module,
        Ok(ModuleSource::JavaScript(js)) => match context.eval_module(&js, name) {
            Ok(v) => v.module,
            Err(e) => {
                throw_error(context, e);
                return std::ptr::null_mut();
            }
        },
        Err(e) => {
            throw_error(context, e);
            return std::ptr::null_mut();
        }
    }
}
