// Copyright 2015-2020 Parity Technologies (UK) Ltd.
// This file is part of OpenEthereum.

// OpenEthereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// OpenEthereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with OpenEthereum.  If not, see <http://www.gnu.org/licenses/>.

//! Env module glue for wasmi interpreter

use std::cell::RefCell;
use vm::WasmCosts;
use wasmi::{
    self, memory_units, Error, FuncInstance, FuncRef, MemoryDescriptor, MemoryInstance, MemoryRef,
    Signature,
};

/// Internal ids all functions runtime supports. This is just a glue for wasmi interpreter
/// that lacks high-level api and later will be factored out
pub mod ids {
    pub const STORAGE_WRITE_FUNC: usize = 0;
    pub const STORAGE_READ_FUNC: usize = 10;
    pub const RET_FUNC: usize = 20;
    pub const GAS_FUNC: usize = 30;
    pub const FETCH_INPUT_FUNC: usize = 40;
    pub const INPUT_LENGTH_FUNC: usize = 50;
    pub const CCALL_FUNC: usize = 60;
    pub const SCALL_FUNC: usize = 70;
    pub const DCALL_FUNC: usize = 80;
    pub const VALUE_FUNC: usize = 90;
    pub const CREATE_FUNC: usize = 100;
    pub const SUICIDE_FUNC: usize = 110;
    pub const BLOCKHASH_FUNC: usize = 120;
    pub const BLOCKNUMBER_FUNC: usize = 130;
    pub const COINBASE_FUNC: usize = 140;
    pub const DIFFICULTY_FUNC: usize = 150;
    pub const GASLIMIT_FUNC: usize = 160;
    pub const TIMESTAMP_FUNC: usize = 170;
    pub const ADDRESS_FUNC: usize = 180;
    pub const SENDER_FUNC: usize = 190;
    pub const ORIGIN_FUNC: usize = 200;
    pub const ELOG_FUNC: usize = 210;
    pub const CREATE2_FUNC: usize = 220;
    pub const GASLEFT_FUNC: usize = 230;

    pub const PANIC_FUNC: usize = 1000;
    pub const DEBUG_FUNC: usize = 1010;
}

/// Signatures of all functions runtime supports. The actual dispatch happens at
/// impl runtime::Runtime methods.
pub mod signatures {
    use wasmi::{self, ValueType, ValueType::*};

    pub struct StaticSignature(pub &'static [ValueType], pub Option<ValueType>);

    pub const STORAGE_READ: StaticSignature = StaticSignature(&[I32, I32], None);

    pub const STORAGE_WRITE: StaticSignature = StaticSignature(&[I32, I32], None);

    pub const RET: StaticSignature = StaticSignature(&[I32, I32], None);

    pub const GAS: StaticSignature = StaticSignature(&[I32], None);

    pub const FETCH_INPUT: StaticSignature = StaticSignature(&[I32], None);

    pub const INPUT_LENGTH: StaticSignature = StaticSignature(&[], Some(I32));

    pub const CCALL: StaticSignature =
        StaticSignature(&[I64, I32, I32, I32, I32, I32, I32], Some(I32));

    pub const DCALL: StaticSignature = StaticSignature(&[I64, I32, I32, I32, I32, I32], Some(I32));

    pub const SCALL: StaticSignature = StaticSignature(&[I64, I32, I32, I32, I32, I32], Some(I32));

    pub const PANIC: StaticSignature = StaticSignature(&[I32, I32], None);

    pub const DEBUG: StaticSignature = StaticSignature(&[I32, I32], None);

    pub const VALUE: StaticSignature = StaticSignature(&[I32], None);

    pub const CREATE: StaticSignature = StaticSignature(&[I32, I32, I32, I32], Some(I32));

    pub const CREATE2: StaticSignature = StaticSignature(&[I32, I32, I32, I32, I32], Some(I32));

    pub const SUICIDE: StaticSignature = StaticSignature(&[I32], None);

    pub const BLOCKHASH: StaticSignature = StaticSignature(&[I64, I32], None);

    pub const BLOCKNUMBER: StaticSignature = StaticSignature(&[], Some(I64));

    pub const COINBASE: StaticSignature = StaticSignature(&[I32], None);

    pub const DIFFICULTY: StaticSignature = StaticSignature(&[I32], None);

    pub const GASLEFT: StaticSignature = StaticSignature(&[], Some(I64));

    pub const GASLIMIT: StaticSignature = StaticSignature(&[I32], None);

    pub const TIMESTAMP: StaticSignature = StaticSignature(&[], Some(I64));

    pub const ADDRESS: StaticSignature = StaticSignature(&[I32], None);

    pub const SENDER: StaticSignature = StaticSignature(&[I32], None);

    pub const ORIGIN: StaticSignature = StaticSignature(&[I32], None);

    pub const ELOG: StaticSignature = StaticSignature(&[I32, I32, I32, I32], None);

    impl Into<wasmi::Signature> for StaticSignature {
        fn into(self) -> wasmi::Signature {
            wasmi::Signature::new(self.0, self.1)
        }
    }
}

fn host(signature: signatures::StaticSignature, idx: usize) -> FuncRef {
    FuncInstance::alloc_host(signature.into(), idx)
}

/// Import resolver for wasmi
/// Maps all functions that runtime support to the corresponding contract import
/// entries.
/// Also manages initial memory request from the runtime.
pub struct ImportResolver {
    max_memory: u32,
    memory: RefCell<Option<MemoryRef>>,

    have_create2: bool,
    have_gasleft: bool,
}

impl ImportResolver {
    /// New import resolver with specifed maximum amount of inital memory (in wasm pages = 64kb)
    pub fn with_limit(max_memory: u32, schedule: &WasmCosts) -> ImportResolver {
        ImportResolver {
            max_memory: max_memory,
            memory: RefCell::new(None),

            have_create2: schedule.have_create2,
            have_gasleft: schedule.have_gasleft,
        }
    }

    /// Returns memory that was instantiated during the contract module
    /// start. If contract does not use memory at all, the dummy memory of length (0, 0)
    /// will be created instead. So this method always returns memory instance
    /// unless errored.
    pub fn memory_ref(&self) -> MemoryRef {
        {
            let mut mem_ref = self.memory.borrow_mut();
            if mem_ref.is_none() {
                *mem_ref = Some(
                    MemoryInstance::alloc(memory_units::Pages(0), Some(memory_units::Pages(0)))
                        .expect("Memory allocation (0, 0) should not fail; qed"),
                );
            }
        }

        self.memory
            .borrow()
            .clone()
            .expect("it is either existed or was created as (0, 0) above; qed")
    }

    /// Returns memory size module initially requested
    pub fn memory_size(&self) -> Result<u32, Error> {
        Ok(self.memory_ref().current_size().0 as u32)
    }
}

impl wasmi::ModuleImportResolver for ImportResolver {
    fn resolve_func(&self, field_name: &str, _signature: &Signature) -> Result<FuncRef, Error> {
        let func_ref = match field_name {
            "storage_read" => host(signatures::STORAGE_READ, ids::STORAGE_READ_FUNC),
            "storage_write" => host(signatures::STORAGE_WRITE, ids::STORAGE_WRITE_FUNC),
            "ret" => host(signatures::RET, ids::RET_FUNC),
            "gas" => host(signatures::GAS, ids::GAS_FUNC),
            "input_length" => host(signatures::INPUT_LENGTH, ids::INPUT_LENGTH_FUNC),
            "fetch_input" => host(signatures::FETCH_INPUT, ids::FETCH_INPUT_FUNC),
            "panic" => host(signatures::PANIC, ids::PANIC_FUNC),
            "debug" => host(signatures::DEBUG, ids::DEBUG_FUNC),
            "ccall" => host(signatures::CCALL, ids::CCALL_FUNC),
            "dcall" => host(signatures::DCALL, ids::DCALL_FUNC),
            "scall" => host(signatures::SCALL, ids::SCALL_FUNC),
            "value" => host(signatures::VALUE, ids::VALUE_FUNC),
            "create" => host(signatures::CREATE, ids::CREATE_FUNC),
            "suicide" => host(signatures::SUICIDE, ids::SUICIDE_FUNC),
            "blockhash" => host(signatures::BLOCKHASH, ids::BLOCKHASH_FUNC),
            "blocknumber" => host(signatures::BLOCKNUMBER, ids::BLOCKNUMBER_FUNC),
            "coinbase" => host(signatures::COINBASE, ids::COINBASE_FUNC),
            "difficulty" => host(signatures::DIFFICULTY, ids::DIFFICULTY_FUNC),
            "gaslimit" => host(signatures::GASLIMIT, ids::GASLIMIT_FUNC),
            "timestamp" => host(signatures::TIMESTAMP, ids::TIMESTAMP_FUNC),
            "address" => host(signatures::ADDRESS, ids::ADDRESS_FUNC),
            "sender" => host(signatures::SENDER, ids::SENDER_FUNC),
            "origin" => host(signatures::ORIGIN, ids::ORIGIN_FUNC),
            "elog" => host(signatures::ELOG, ids::ELOG_FUNC),
            "create2" if self.have_create2 => host(signatures::CREATE2, ids::CREATE2_FUNC),
            "gasleft" if self.have_gasleft => host(signatures::GASLEFT, ids::GASLEFT_FUNC),
            _ => {
                return Err(wasmi::Error::Instantiation(format!(
                    "Export {} not found",
                    field_name
                )))
            }
        };

        Ok(func_ref)
    }

    fn resolve_memory(
        &self,
        field_name: &str,
        descriptor: &MemoryDescriptor,
    ) -> Result<MemoryRef, Error> {
        if field_name == "memory" {
            let effective_max = descriptor.maximum().unwrap_or(self.max_memory + 1);
            if descriptor.initial() > self.max_memory || effective_max > self.max_memory {
                Err(Error::Instantiation(
                    "Module requested too much memory".to_owned(),
                ))
            } else {
                let mem = MemoryInstance::alloc(
                    memory_units::Pages(descriptor.initial() as usize),
                    descriptor
                        .maximum()
                        .map(|x| memory_units::Pages(x as usize)),
                )?;
                *self.memory.borrow_mut() = Some(mem.clone());
                Ok(mem)
            }
        } else {
            Err(Error::Instantiation(
                "Memory imported under unknown name".to_owned(),
            ))
        }
    }
}
