use crate::{ImportError, LinkError};
use more_asserts::assert_ge;
use wasmer_types::entity::{BoxedSlice, EntityRef, PrimaryMap};
use wasmer_types::{ExternType, FunctionIndex, ImportIndex, MemoryIndex, TableIndex};
use wasmer_vm::{
    Export, FunctionBodyPtr, Imports, MemoryStyle, ModuleInfo, TableStyle, VMFunctionBody,
    VMFunctionImport, VMFunctionKind, VMGlobalImport, VMMemoryImport, VMTableImport,
};
pub trait Resolver {
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    fn resolve(&self, _index: u32, module: &str, field: &str) -> Option<Export>;
}
pub trait NamedResolver {
    
    
    
    
    fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export>;
}
impl<T: NamedResolver> Resolver for T {
    
    
    fn resolve(&self, _index: u32, module: &str, field: &str) -> Option<Export> {
        self.resolve_by_name(module, field)
    }
}
impl<T: NamedResolver> NamedResolver for &T {
    fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export> {
        (**self).resolve_by_name(module, field)
    }
}
impl NamedResolver for Box<dyn NamedResolver> {
    fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export> {
        (**self).resolve_by_name(module, field)
    }
}
pub struct NullResolver {}
impl Resolver for NullResolver {
    fn resolve(&self, _idx: u32, _module: &str, _field: &str) -> Option<Export> {
        None
    }
}
fn get_extern_from_import(module: &ModuleInfo, import_index: &ImportIndex) -> ExternType {
    match import_index {
        ImportIndex::Function(index) => {
            let func = module.signatures[module.functions[*index]].clone();
            ExternType::Function(func)
        }
        ImportIndex::Table(index) => {
            let table = module.tables[*index];
            ExternType::Table(table)
        }
        ImportIndex::Memory(index) => {
            let memory = module.memories[*index];
            ExternType::Memory(memory)
        }
        ImportIndex::Global(index) => {
            let global = module.globals[*index];
            ExternType::Global(global)
        }
    }
}
fn get_extern_from_export(_module: &ModuleInfo, export: &Export) -> ExternType {
    match export {
        Export::Function(ref f) => ExternType::Function(f.signature.clone()),
        Export::Table(ref t) => ExternType::Table(t.ty().clone()),
        Export::Memory(ref m) => ExternType::Memory(m.ty().clone()),
        Export::Global(ref g) => {
            let global = g.from.ty();
            ExternType::Global(*global)
        }
    }
}
pub fn resolve_imports(
    module: &ModuleInfo,
    resolver: &dyn Resolver,
    finished_dynamic_function_trampolines: &BoxedSlice<FunctionIndex, FunctionBodyPtr>,
    memory_styles: &PrimaryMap<MemoryIndex, MemoryStyle>,
    _table_styles: &PrimaryMap<TableIndex, TableStyle>,
) -> Result<Imports, LinkError> {
    let mut function_imports = PrimaryMap::with_capacity(module.num_imported_functions);
    let mut table_imports = PrimaryMap::with_capacity(module.num_imported_tables);
    let mut memory_imports = PrimaryMap::with_capacity(module.num_imported_memories);
    let mut global_imports = PrimaryMap::with_capacity(module.num_imported_globals);
    for ((module_name, field, import_idx), import_index) in module.imports.iter() {
        let resolved = resolver.resolve(*import_idx, module_name, field);
        let import_extern = get_extern_from_import(module, import_index);
        let resolved = match resolved {
            None => {
                return Err(LinkError::Import(
                    module_name.to_string(),
                    field.to_string(),
                    ImportError::UnknownImport(import_extern),
                ));
            }
            Some(r) => r,
        };
        let export_extern = get_extern_from_export(module, &resolved);
        if !export_extern.is_compatible_with(&import_extern) {
            return Err(LinkError::Import(
                module_name.to_string(),
                field.to_string(),
                ImportError::IncompatibleType(import_extern, export_extern),
            ));
        }
        match resolved {
            Export::Function(ref f) => {
                let address = match f.kind {
                    VMFunctionKind::Dynamic => {
                        
                        
                        
                        let index = FunctionIndex::new(function_imports.len());
                        finished_dynamic_function_trampolines[index].0 as *mut VMFunctionBody as _
                        
                        
                    }
                    VMFunctionKind::Static => f.address,
                };
                function_imports.push(VMFunctionImport {
                    body: address,
                    vmctx: f.vmctx,
                });
            }
            Export::Table(ref t) => {
                table_imports.push(VMTableImport {
                    definition: t.from.vmtable(),
                    from: t.from.clone(),
                });
            }
            Export::Memory(ref m) => {
                match import_index {
                    ImportIndex::Memory(index) => {
                        
                        
                        let export_memory_style = m.style();
                        let import_memory_style = &memory_styles[*index];
                        if let (
                            MemoryStyle::Static { bound, .. },
                            MemoryStyle::Static {
                                bound: import_bound,
                                ..
                            },
                        ) = (export_memory_style.clone(), &import_memory_style)
                        {
                            assert_ge!(bound, *import_bound);
                        }
                        assert_ge!(
                            export_memory_style.offset_guard_size(),
                            import_memory_style.offset_guard_size()
                        );
                    }
                    _ => {
                        
                        
                        panic!("Memory resolution didn't matched");
                    }
                }
                memory_imports.push(VMMemoryImport {
                    definition: m.from.vmmemory(),
                    from: m.from.clone(),
                });
            }
            Export::Global(ref g) => {
                global_imports.push(VMGlobalImport {
                    definition: g.from.vmglobal(),
                    from: g.from.clone(),
                });
            }
        }
    }
    Ok(Imports::new(
        function_imports,
        table_imports,
        memory_imports,
        global_imports,
    ))
}
pub struct NamedResolverChain<A: NamedResolver, B: NamedResolver> {
    a: A,
    b: B,
}
pub trait ChainableNamedResolver: NamedResolver + Sized {
    
    
    
    
    
    
    
    
    
    
    
    
    
    fn chain_front<U>(self, other: U) -> NamedResolverChain<U, Self>
    where
        U: NamedResolver,
    {
        NamedResolverChain { a: other, b: self }
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    fn chain_back<U>(self, other: U) -> NamedResolverChain<Self, U>
    where
        U: NamedResolver,
    {
        NamedResolverChain { a: self, b: other }
    }
}
impl<T: NamedResolver> ChainableNamedResolver for T {}
impl<A, B> NamedResolver for NamedResolverChain<A, B>
where
    A: NamedResolver,
    B: NamedResolver,
{
    fn resolve_by_name(&self, module: &str, field: &str) -> Option<Export> {
        self.a
            .resolve_by_name(module, field)
            .or_else(|| self.b.resolve_by_name(module, field))
    }
}
impl<A, B> Clone for NamedResolverChain<A, B>
where
    A: NamedResolver + Clone,
    B: NamedResolver + Clone,
{
    fn clone(&self) -> Self {
        Self {
            a: self.a.clone(),
            b: self.b.clone(),
        }
    }
}