use crate::tunables::Tunables;
use crate::WASM_MAX_PAGES;
use cranelift_codegen::ir;
use cranelift_entity::{EntityRef, PrimaryMap};
use cranelift_wasm::*;
use indexmap::IndexMap;
use more_asserts::assert_ge;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
pub struct TableElements {
    
    pub table_index: TableIndex,
    
    pub base: Option<GlobalIndex>,
    
    pub offset: usize,
    
    pub elements: Box<[FuncIndex]>,
}
#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
pub enum MemoryStyle {
    
    Dynamic,
    
    Static {
        
        bound: u32,
    },
}
impl MemoryStyle {
    
    pub fn for_memory(memory: Memory, tunables: &Tunables) -> (Self, u64) {
        
        
        
        
        let maximum = memory.maximum.unwrap_or(WASM_MAX_PAGES);
        if maximum <= tunables.static_memory_bound {
            assert_ge!(tunables.static_memory_bound, memory.minimum);
            return (
                Self::Static {
                    bound: tunables.static_memory_bound,
                },
                tunables.static_memory_offset_guard_size,
            );
        }
        
        (Self::Dynamic, tunables.dynamic_memory_offset_guard_size)
    }
}
#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
pub struct MemoryPlan {
    
    pub memory: Memory,
    
    pub style: MemoryStyle,
    
    pub offset_guard_size: u64,
}
impl MemoryPlan {
    
    pub fn for_memory(memory: Memory, tunables: &Tunables) -> Self {
        let (style, offset_guard_size) = MemoryStyle::for_memory(memory, tunables);
        Self {
            memory,
            style,
            offset_guard_size,
        }
    }
}
#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
pub enum TableStyle {
    
    CallerChecksSignature,
}
impl TableStyle {
    
    pub fn for_table(_table: Table, _tunables: &Tunables) -> Self {
        Self::CallerChecksSignature
    }
}
#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
pub struct TablePlan {
    
    pub table: cranelift_wasm::Table,
    
    pub style: TableStyle,
}
impl TablePlan {
    
    pub fn for_table(table: Table, tunables: &Tunables) -> Self {
        let style = TableStyle::for_table(table, tunables);
        Self { table, style }
    }
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
#[allow(missing_docs)]
pub enum ModuleType {
    Function(SignatureIndex),
    Module(ModuleTypeIndex),
    Instance(InstanceTypeIndex),
}
impl ModuleType {
    
    
    pub fn unwrap_function(&self) -> SignatureIndex {
        match self {
            ModuleType::Function(f) => *f,
            _ => panic!("not a function type"),
        }
    }
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct Module {
    
    
    
    
    pub parent: Option<usize>,
    
    pub name: Option<String>,
    
    pub initializers: Vec<Initializer>,
    
    pub exports: IndexMap<String, EntityIndex>,
    
    pub start_func: Option<FuncIndex>,
    
    pub table_elements: Vec<TableElements>,
    
    pub passive_elements: HashMap<ElemIndex, Box<[FuncIndex]>>,
    
    #[serde(with = "passive_data_serde")]
    pub passive_data: HashMap<DataIndex, Arc<[u8]>>,
    
    pub func_names: HashMap<FuncIndex, String>,
    
    pub types: PrimaryMap<TypeIndex, ModuleType>,
    
    pub num_imported_funcs: usize,
    
    pub num_imported_tables: usize,
    
    pub num_imported_memories: usize,
    
    pub num_imported_globals: usize,
    
    pub functions: PrimaryMap<FuncIndex, SignatureIndex>,
    
    pub table_plans: PrimaryMap<TableIndex, TablePlan>,
    
    pub memory_plans: PrimaryMap<MemoryIndex, MemoryPlan>,
    
    pub globals: PrimaryMap<GlobalIndex, Global>,
    
    pub instances: PrimaryMap<InstanceIndex, InstanceTypeIndex>,
    
    pub modules: PrimaryMap<ModuleIndex, ModuleTypeIndex>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Initializer {
    
    Import {
        
        module: String,
        
        field: Option<String>,
        
        
        index: EntityIndex,
    },
    
    
    AliasParentModule(ModuleIndex),
    
    
    #[allow(missing_docs)]
    AliasInstanceExport {
        instance: InstanceIndex,
        export: usize,
    },
    
    
    Instantiate {
        
        module: ModuleIndex,
        
        args: Vec<EntityIndex>,
    },
    
    
    DefineModule(usize),
}
impl Module {
    
    pub fn new() -> Self {
        Module::default()
    }
    
    pub fn get_passive_element(&self, index: ElemIndex) -> Option<&[FuncIndex]> {
        self.passive_elements.get(&index).map(|es| &**es)
    }
    
    pub fn func_index(&self, defined_func: DefinedFuncIndex) -> FuncIndex {
        FuncIndex::new(self.num_imported_funcs + defined_func.index())
    }
    
    
    pub fn defined_func_index(&self, func: FuncIndex) -> Option<DefinedFuncIndex> {
        if func.index() < self.num_imported_funcs {
            None
        } else {
            Some(DefinedFuncIndex::new(
                func.index() - self.num_imported_funcs,
            ))
        }
    }
    
    pub fn is_imported_function(&self, index: FuncIndex) -> bool {
        index.index() < self.num_imported_funcs
    }
    
    pub fn table_index(&self, defined_table: DefinedTableIndex) -> TableIndex {
        TableIndex::new(self.num_imported_tables + defined_table.index())
    }
    
    
    pub fn defined_table_index(&self, table: TableIndex) -> Option<DefinedTableIndex> {
        if table.index() < self.num_imported_tables {
            None
        } else {
            Some(DefinedTableIndex::new(
                table.index() - self.num_imported_tables,
            ))
        }
    }
    
    pub fn is_imported_table(&self, index: TableIndex) -> bool {
        index.index() < self.num_imported_tables
    }
    
    pub fn memory_index(&self, defined_memory: DefinedMemoryIndex) -> MemoryIndex {
        MemoryIndex::new(self.num_imported_memories + defined_memory.index())
    }
    
    
    pub fn defined_memory_index(&self, memory: MemoryIndex) -> Option<DefinedMemoryIndex> {
        if memory.index() < self.num_imported_memories {
            None
        } else {
            Some(DefinedMemoryIndex::new(
                memory.index() - self.num_imported_memories,
            ))
        }
    }
    
    pub fn is_imported_memory(&self, index: MemoryIndex) -> bool {
        index.index() < self.num_imported_memories
    }
    
    pub fn global_index(&self, defined_global: DefinedGlobalIndex) -> GlobalIndex {
        GlobalIndex::new(self.num_imported_globals + defined_global.index())
    }
    
    
    pub fn defined_global_index(&self, global: GlobalIndex) -> Option<DefinedGlobalIndex> {
        if global.index() < self.num_imported_globals {
            None
        } else {
            Some(DefinedGlobalIndex::new(
                global.index() - self.num_imported_globals,
            ))
        }
    }
    
    pub fn is_imported_global(&self, index: GlobalIndex) -> bool {
        index.index() < self.num_imported_globals
    }
    
    
    pub fn imports(&self) -> impl Iterator<Item = (&str, Option<&str>, EntityType)> {
        self.initializers.iter().filter_map(move |i| match i {
            Initializer::Import {
                module,
                field,
                index,
            } => Some((module.as_str(), field.as_deref(), self.type_of(*index))),
            _ => None,
        })
    }
    
    pub fn type_of(&self, index: EntityIndex) -> EntityType {
        match index {
            EntityIndex::Global(i) => EntityType::Global(self.globals[i]),
            EntityIndex::Table(i) => EntityType::Table(self.table_plans[i].table),
            EntityIndex::Memory(i) => EntityType::Memory(self.memory_plans[i].memory),
            EntityIndex::Function(i) => EntityType::Function(self.functions[i]),
            EntityIndex::Instance(i) => EntityType::Instance(self.instances[i]),
            EntityIndex::Module(i) => EntityType::Module(self.modules[i]),
        }
    }
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct TypeTables {
    pub wasm_signatures: PrimaryMap<SignatureIndex, WasmFuncType>,
    pub native_signatures: PrimaryMap<SignatureIndex, ir::Signature>,
    pub module_signatures: PrimaryMap<ModuleTypeIndex, ModuleSignature>,
    pub instance_signatures: PrimaryMap<InstanceTypeIndex, InstanceSignature>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleSignature {
    
    
    pub imports: Vec<(String, Option<String>, EntityType)>,
    
    
    pub exports: InstanceTypeIndex,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InstanceSignature {
    
    pub exports: IndexMap<String, EntityType>,
}
mod passive_data_serde {
    use super::{Arc, DataIndex, HashMap};
    use serde::{de::MapAccess, de::Visitor, ser::SerializeMap, Deserializer, Serializer};
    use std::fmt;
    pub(super) fn serialize<S>(
        data: &HashMap<DataIndex, Arc<[u8]>>,
        ser: S,
    ) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut map = ser.serialize_map(Some(data.len()))?;
        for (k, v) in data {
            map.serialize_entry(k, v.as_ref())?;
        }
        map.end()
    }
    struct PassiveDataVisitor;
    impl<'de> Visitor<'de> for PassiveDataVisitor {
        type Value = HashMap<DataIndex, Arc<[u8]>>;
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("a passive_data map")
        }
        fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
        where
            M: MapAccess<'de>,
        {
            let mut map = HashMap::with_capacity(access.size_hint().unwrap_or(0));
            while let Some((key, value)) = access.next_entry::<_, Vec<u8>>()? {
                map.insert(key, value.into());
            }
            Ok(map)
        }
    }
    pub(super) fn deserialize<'de, D>(de: D) -> Result<HashMap<DataIndex, Arc<[u8]>>, D::Error>
    where
        D: Deserializer<'de>,
    {
        de.deserialize_map(PassiveDataVisitor)
    }
}