use crate::code_translator::{bitcast_arguments, translate_operator, wasm_param_types};
use crate::environ::{FuncEnvironment, ReturnMode, WasmResult};
use crate::state::FuncTranslationState;
use crate::translation_utils::get_vmctx_value_label;
use crate::wasm_unsupported;
use core::convert::TryInto;
use cranelift_codegen::entity::EntityRef;
use cranelift_codegen::ir::{self, Block, InstBuilder, ValueLabel};
use cranelift_codegen::timing;
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
use wasmparser::{self, BinaryReader, FuncValidator, FunctionBody, WasmModuleResources};
pub struct FuncTranslator {
    func_ctx: FunctionBuilderContext,
    state: FuncTranslationState,
}
impl FuncTranslator {
    
    pub fn new() -> Self {
        Self {
            func_ctx: FunctionBuilderContext::new(),
            state: FuncTranslationState::new(),
        }
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    pub fn translate<FE: FuncEnvironment + ?Sized>(
        &mut self,
        validator: &mut FuncValidator<impl WasmModuleResources>,
        code: &[u8],
        code_offset: usize,
        func: &mut ir::Function,
        environ: &mut FE,
    ) -> WasmResult<()> {
        self.translate_body(
            validator,
            FunctionBody::new(code_offset, code),
            func,
            environ,
        )
    }
    
    pub fn translate_body<FE: FuncEnvironment + ?Sized>(
        &mut self,
        validator: &mut FuncValidator<impl WasmModuleResources>,
        body: FunctionBody<'_>,
        func: &mut ir::Function,
        environ: &mut FE,
    ) -> WasmResult<()> {
        let _tt = timing::wasm_translate_function();
        let mut reader = body.get_binary_reader();
        log::debug!(
            "translate({} bytes, {}{})",
            reader.bytes_remaining(),
            func.name,
            func.signature
        );
        debug_assert_eq!(func.dfg.num_blocks(), 0, "Function must be empty");
        debug_assert_eq!(func.dfg.num_insts(), 0, "Function must be empty");
        
        let mut builder = FunctionBuilder::new(func, &mut self.func_ctx);
        builder.set_srcloc(cur_srcloc(&reader));
        let entry_block = builder.create_block();
        builder.append_block_params_for_function_params(entry_block);
        builder.switch_to_block(entry_block); 
        builder.seal_block(entry_block); 
        
        
        builder.ensure_inserted_block();
        let num_params = declare_wasm_parameters(&mut builder, entry_block, environ);
        
        
        let exit_block = builder.create_block();
        builder.append_block_params_for_function_returns(exit_block);
        self.state.initialize(&builder.func.signature, exit_block);
        parse_local_decls(&mut reader, &mut builder, num_params, environ, validator)?;
        parse_function_body(validator, reader, &mut builder, &mut self.state, environ)?;
        builder.finalize();
        Ok(())
    }
}
fn declare_wasm_parameters<FE: FuncEnvironment + ?Sized>(
    builder: &mut FunctionBuilder,
    entry_block: Block,
    environ: &FE,
) -> usize {
    let sig_len = builder.func.signature.params.len();
    let mut next_local = 0;
    for i in 0..sig_len {
        let param_type = builder.func.signature.params[i];
        
        
        if environ.is_wasm_parameter(&builder.func.signature, i) {
            
            let local = Variable::new(next_local);
            builder.declare_var(local, param_type.value_type);
            next_local += 1;
            let param_value = builder.block_params(entry_block)[i];
            builder.def_var(local, param_value);
        }
        if param_type.purpose == ir::ArgumentPurpose::VMContext {
            let param_value = builder.block_params(entry_block)[i];
            builder.set_val_label(param_value, get_vmctx_value_label());
        }
    }
    next_local
}
fn parse_local_decls<FE: FuncEnvironment + ?Sized>(
    reader: &mut BinaryReader,
    builder: &mut FunctionBuilder,
    num_params: usize,
    environ: &mut FE,
    validator: &mut FuncValidator<impl WasmModuleResources>,
) -> WasmResult<()> {
    let mut next_local = num_params;
    let local_count = reader.read_var_u32()?;
    for _ in 0..local_count {
        builder.set_srcloc(cur_srcloc(reader));
        let pos = reader.original_position();
        let count = reader.read_var_u32()?;
        let ty = reader.read_type()?;
        validator.define_locals(pos, count, ty)?;
        declare_locals(builder, count, ty, &mut next_local, environ)?;
    }
    Ok(())
}
fn declare_locals<FE: FuncEnvironment + ?Sized>(
    builder: &mut FunctionBuilder,
    count: u32,
    wasm_type: wasmparser::Type,
    next_local: &mut usize,
    environ: &mut FE,
) -> WasmResult<()> {
    
    use wasmparser::Type::*;
    let zeroval = match wasm_type {
        I32 => builder.ins().iconst(ir::types::I32, 0),
        I64 => builder.ins().iconst(ir::types::I64, 0),
        F32 => builder.ins().f32const(ir::immediates::Ieee32::with_bits(0)),
        F64 => builder.ins().f64const(ir::immediates::Ieee64::with_bits(0)),
        V128 => {
            let constant_handle = builder.func.dfg.constants.insert([0; 16].to_vec().into());
            builder.ins().vconst(ir::types::I8X16, constant_handle)
        }
        ExternRef | FuncRef => {
            environ.translate_ref_null(builder.cursor(), wasm_type.try_into()?)?
        }
        ty => return Err(wasm_unsupported!("unsupported local type {:?}", ty)),
    };
    let ty = builder.func.dfg.value_type(zeroval);
    for _ in 0..count {
        let local = Variable::new(*next_local);
        builder.declare_var(local, ty);
        builder.def_var(local, zeroval);
        builder.set_val_label(zeroval, ValueLabel::new(*next_local));
        *next_local += 1;
    }
    Ok(())
}
fn parse_function_body<FE: FuncEnvironment + ?Sized>(
    validator: &mut FuncValidator<impl WasmModuleResources>,
    mut reader: BinaryReader,
    builder: &mut FunctionBuilder,
    state: &mut FuncTranslationState,
    environ: &mut FE,
) -> WasmResult<()> {
    
    debug_assert_eq!(state.control_stack.len(), 1, "State not initialized");
    environ.before_translate_function(builder, state)?;
    while !reader.eof() {
        let pos = reader.original_position();
        builder.set_srcloc(cur_srcloc(&reader));
        let op = reader.read_operator()?;
        validator.op(pos, &op)?;
        environ.before_translate_operator(&op, builder, state)?;
        translate_operator(validator, &op, builder, state, environ)?;
        environ.after_translate_operator(&op, builder, state)?;
    }
    environ.after_translate_function(builder, state)?;
    let pos = reader.original_position();
    validator.finish(pos)?;
    
    
    
    
    
    if state.reachable {
        if !builder.is_unreachable() {
            match environ.return_mode() {
                ReturnMode::NormalReturns => {
                    let return_types = wasm_param_types(&builder.func.signature.returns, |i| {
                        environ.is_wasm_return(&builder.func.signature, i)
                    });
                    bitcast_arguments(&mut state.stack, &return_types, builder);
                    builder.ins().return_(&state.stack)
                }
                ReturnMode::FallthroughReturn => builder.ins().fallthrough_return(&state.stack),
            };
        }
    }
    
    
    state.stack.clear();
    Ok(())
}
fn cur_srcloc(reader: &BinaryReader) -> ir::SourceLoc {
    
    
    ir::SourceLoc::new(reader.original_position() as u32)
}
#[cfg(test)]
mod tests {
    use super::{FuncTranslator, ReturnMode};
    use crate::environ::DummyEnvironment;
    use cranelift_codegen::ir::types::I32;
    use cranelift_codegen::{ir, isa, settings, Context};
    use log::debug;
    use target_lexicon::PointerWidth;
    use wasmparser::{
        FuncValidator, FunctionBody, Parser, ValidPayload, Validator, ValidatorResources,
    };
    #[test]
    fn small1() {
        
        let wasm = wat::parse_str(
            "
                (module
                    (func $small2 (param i32) (result i32)
                        (i32.add (get_local 0) (i32.const 1))
                    )
                )
            ",
        )
        .unwrap();
        let mut trans = FuncTranslator::new();
        let flags = settings::Flags::new(settings::builder());
        let runtime = DummyEnvironment::new(
            isa::TargetFrontendConfig {
                default_call_conv: isa::CallConv::Fast,
                pointer_width: PointerWidth::U64,
            },
            ReturnMode::NormalReturns,
            false,
        );
        let mut ctx = Context::new();
        ctx.func.name = ir::ExternalName::testcase("small1");
        ctx.func.signature.params.push(ir::AbiParam::new(I32));
        ctx.func.signature.returns.push(ir::AbiParam::new(I32));
        let (body, mut validator) = extract_func(&wasm);
        trans
            .translate_body(&mut validator, body, &mut ctx.func, &mut runtime.func_env())
            .unwrap();
        debug!("{}", ctx.func.display(None));
        ctx.verify(&flags).unwrap();
    }
    #[test]
    fn small2() {
        
        let wasm = wat::parse_str(
            "
                (module
                    (func $small2 (param i32) (result i32)
                        (return (i32.add (get_local 0) (i32.const 1)))
                    )
                )
            ",
        )
        .unwrap();
        let mut trans = FuncTranslator::new();
        let flags = settings::Flags::new(settings::builder());
        let runtime = DummyEnvironment::new(
            isa::TargetFrontendConfig {
                default_call_conv: isa::CallConv::Fast,
                pointer_width: PointerWidth::U64,
            },
            ReturnMode::NormalReturns,
            false,
        );
        let mut ctx = Context::new();
        ctx.func.name = ir::ExternalName::testcase("small2");
        ctx.func.signature.params.push(ir::AbiParam::new(I32));
        ctx.func.signature.returns.push(ir::AbiParam::new(I32));
        let (body, mut validator) = extract_func(&wasm);
        trans
            .translate_body(&mut validator, body, &mut ctx.func, &mut runtime.func_env())
            .unwrap();
        debug!("{}", ctx.func.display(None));
        ctx.verify(&flags).unwrap();
    }
    #[test]
    fn infloop() {
        
        let wasm = wat::parse_str(
            "
                (module
                    (func $infloop (result i32)
                        (local i32)
                        (loop (result i32)
                            (i32.add (get_local 0) (i32.const 1))
                            (set_local 0)
                            (br 0)
                        )
                    )
                )
            ",
        )
        .unwrap();
        let mut trans = FuncTranslator::new();
        let flags = settings::Flags::new(settings::builder());
        let runtime = DummyEnvironment::new(
            isa::TargetFrontendConfig {
                default_call_conv: isa::CallConv::Fast,
                pointer_width: PointerWidth::U64,
            },
            ReturnMode::NormalReturns,
            false,
        );
        let mut ctx = Context::new();
        ctx.func.name = ir::ExternalName::testcase("infloop");
        ctx.func.signature.returns.push(ir::AbiParam::new(I32));
        let (body, mut validator) = extract_func(&wasm);
        trans
            .translate_body(&mut validator, body, &mut ctx.func, &mut runtime.func_env())
            .unwrap();
        debug!("{}", ctx.func.display(None));
        ctx.verify(&flags).unwrap();
    }
    fn extract_func(wat: &[u8]) -> (FunctionBody<'_>, FuncValidator<ValidatorResources>) {
        let mut validator = Validator::new();
        for payload in Parser::new(0).parse_all(wat) {
            match validator.payload(&payload.unwrap()).unwrap() {
                ValidPayload::Func(validator, body) => return (body, validator),
                _ => {}
            }
        }
        panic!("failed to find function");
    }
}