//! WASM related primitives.

mod abi;
mod data;
mod func;
mod host;

pub use self::{
    abi::{ToLSBytes, Type},
    data::Data,
    func::{Function, Functions},
    host::HostFunc,
};
use crate::{Error, Result};
use host::CompilerLabel;
use smallvec::SmallVec;
use std::collections::BTreeMap;
use wasmparser::Operator;
use zabi::Abi;

macro_rules! impl_deref {
    ($doc:literal, $name:ident, $target:ty) => {
        #[derive(Clone, Debug, Default)]
        #[doc = concat!(" ", $doc)]
        pub struct $name($target);

        impl core::ops::Deref for $name {
            type Target = $target;

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

        impl core::ops::DerefMut for $name {
            fn deref_mut(&mut self) -> &mut Self::Target {
                &mut self.0
            }
        }
    };
    ($(($doc:literal, $name:ident, $target:ty)),*) => {
        $( impl_deref!($doc, $name, $target); )*
    };
}

impl_deref! {
    ("WASM import section", Imports, BTreeMap<u32, HostFunc>),
    ("WASM export section", Exports, BTreeMap<u32, String>),
    ("WASM slot registry", Slots, BTreeMap<u32, u32>),
    ("WASM function registry", Funcs, BTreeMap<u32, (u32, u32)>)
}

/// A struct that holds the environment wasm module.
#[derive(Clone, Debug, Default)]
pub struct Env {
    /// WASM imports
    pub imports: Imports,
    /// WASM exports
    pub exports: Exports,
    /// Function memory slots
    pub slots: Slots,
    /// Function params count
    pub funcs: Funcs,
    /// WASM data slots
    pub data: Data,
    /// Current function index
    pub index: Option<u32>,
}

impl Env {
    /// Load abis from functions
    pub fn load_abis(&self, funs: &Functions<'_>) -> Result<Vec<Abi>> {
        let mut abis: Vec<_> = Default::default();
        for (_, fun) in funs.iter() {
            abis.push(self.load_abi(fun)?);
        }

        Ok(abis)
    }

    /// Load abi from function
    pub fn load_abi(&self, fun: &Function<'_>) -> Result<Abi> {
        let mut reader = fun.body.get_operators_reader()?;

        let Operator::I32Const { value: offset } = reader.read()? else {
            return Err(Error::InvalidSelector);
        };
        let Operator::I32Const { value: length } = reader.read()? else {
            return Err(Error::InvalidSelector);
        };

        // Validate zinkc helper `emit_abi`
        let Operator::Call {
            function_index: index,
        } = reader.read()?
        else {
            return Err(Error::InvalidSelector);
        };

        if !self.imports.is_emit_abi(index) {
            return Err(Error::FuncNotImported("emit_abi".into()));
        }

        let abi = self.data.load(offset, length as usize)?;
        Abi::from_hex(String::from_utf8_lossy(&abi)).map_err(Into::into)
    }

    /// Query exported function from selector.
    pub fn query_func(&self, name: &str) -> Result<u32> {
        for (index, export) in self.exports.iter() {
            if export == name {
                return Ok(*index);
            }
        }

        Err(Error::FuncNotImported(name.into()))
    }

    /// Check if the input function is external function
    pub fn is_external(&self, index: u32) -> bool {
        let Some(name) = self.exports.get(&index) else {
            return false;
        };

        let selector = name.to_owned() + "_selector";
        self.exports.iter().any(|(_, n)| **n == selector)
    }

    /// If the present function index is the main function
    ///
    /// NOTE: in wasm the indexes of the imports will be ordered
    /// before the functions
    pub fn is_main(&self, index: u32) -> bool {
        self.imports.len() as u32 == index
    }

    /// Clone a new environment with function index provided
    pub fn with_index(&self, index: u32) -> Self {
        let mut this = self.clone();
        this.index = Some(index);
        this
    }

    /// Get reserved slots
    pub fn reserved(&self) -> u32 {
        let Some(index) = self.index else {
            return 0;
        };

        *self.slots.get(&index).unwrap_or(&0)
    }

    /// Allocate memory slots from local index
    pub fn alloc(&self, index: u32) -> SmallVec<[u8; 4]> {
        let slots = index + self.reserved();
        tracing::debug!(
            "allocating memory for local {index} of function {:?}, slot: {slots}",
            self.index
        );
        (slots * 0x20).to_ls_bytes()
    }
}

impl Imports {
    /// If the function is `emit_abi`.
    pub fn is_emit_abi(&self, index: u32) -> bool {
        self.get(&index) == Some(&HostFunc::EmitABI)
    }

    /// Get reserved slots in memory for storage calculations
    pub fn reserved(&self) -> u32 {
        let mut reserved = 0;
        for host_fn in self.0.values() {
            match *host_fn {
                HostFunc::Label(CompilerLabel::ReserveMemory32) => reserved = 1,
                HostFunc::Label(CompilerLabel::ReserveMemory64) => {
                    return 2;
                }
                _ => {}
            }
        }

        reserved
    }
}

impl Exports {
    /// Get all function selectors
    pub fn selectors(&self) -> Vec<u32> {
        self.iter()
            .filter_map(|(index, export)| {
                if export.ends_with("_selector") {
                    Some(*index)
                } else {
                    None
                }
            })
            .collect::<Vec<_>>()
    }
}