use std::fs;
use std::time::Instant;

use anyhow::Result;
use wasmtime::{Config, Engine, Linker, Module, Store};

fn load_file(file: &str) -> Result<Vec<u8>> {
    Ok(fs::read(file)?)
}

fn main() -> Result<()> {
    let instant = Instant::now();
    let config = Config::default();
    let engine = Engine::new(&config).unwrap();
    let linker = Linker::new(&engine);
    println!("T1: {}ms", instant.elapsed().as_millis());

    let wasm_file = std::env::var("WASM")?;
    let was_bytes = load_file(&wasm_file)?;
    println!("T1.2: {}ms", instant.elapsed().as_millis());
    let module = Module::new(&engine, &was_bytes)?;
    println!("T2: {}ms", instant.elapsed().as_millis());

    for _ in 0..3 {
        println!(">>>>>>>>>>>>>>>>>>>>>>>>");
        let instant = Instant::now();
        let linker = linker.clone();
        let module = module.clone();

        let mut store = Store::new(&engine, ());
        println!("T3: {}ms", instant.elapsed().as_millis());

        let instance = linker.instantiate(&mut store, &module)?;
        println!("T4: {}ms", instant.elapsed().as_millis());

        let memory = instance.get_memory(&mut store, "memory").expect("SHOULD NOT HAPPEN");
        let malloc = instance.get_typed_func::<i32, i32>(&mut store, "malloc")?;
        let free = instance.get_typed_func::<i32, ()>(&mut store, "free")?;
        let eval = instance.get_typed_func::<i32, i32>(&mut store, "eval")?;
        println!("T5: {}ms", instant.elapsed().as_millis());

        let js = std::env::var("JS").unwrap_or_else(|_| "1+1".into());
        println!("JS: {}", js);
        let js_bytes = js.as_bytes();
        let js_bytes_len = js_bytes.len();
        println!("T5.1: {}ms", instant.elapsed().as_millis());
        let ptr = malloc.call(&mut store, js.as_bytes().len() as i32)?;
        println!("T5.2: {}ms", instant.elapsed().as_millis());
        let memory_data_mut = memory.data_mut(&mut store);
        for i in 0..js_bytes_len {
            memory_data_mut[4 + ptr as usize + i] = js_bytes[i];
        }
        println!("T5.3: {}ms", instant.elapsed().as_millis());
        let eval_result = eval.call(&mut store, ptr)?;
        println!("Eval result: {:?}", eval_result);
        println!("T6: {}ms", instant.elapsed().as_millis());

        let memory = instance.get_memory(&mut store, "memory").expect("SHOULD NOT HAPPEN");
        let memory_data_mut = memory.data_mut(&mut store);
        let mut a = [0_u8; 4];
        a[0] = memory_data_mut[eval_result as usize + 0];
        a[1] = memory_data_mut[eval_result as usize + 1];
        a[2] = memory_data_mut[eval_result as usize + 2];
        a[3] = memory_data_mut[eval_result as usize + 3];
        let aa = u32::from_be_bytes(a);
        println!("Len: {}", aa);
        let mut s = Vec::with_capacity(aa as usize);
        for i in 0..aa {
            s.push(memory_data_mut[eval_result as usize + 4 + i as usize]);
        }
        let ss = unsafe { String::from_utf8_unchecked(s) };
        println!("Result: {}", ss);
        free.call(&mut store, eval_result)?;
        println!("T7: {}ms", instant.elapsed().as_millis());
    }

    Ok(())
}
