use std::fs;
use std::io;
use std::io::Write;

use cita_vm::evm;
use cita_vm::evm::extmock;
use cita_vm::json_tests::common::*;

fn test_json_file(p: &str) {
    let f = fs::File::open(p).unwrap();
    let t = cita_vm::json_tests::vm_test::Test::load(f).unwrap();
    for (name, data) in t.into_iter() {
        io::stderr().write_all(format!("{}::{}\n", p, name).as_bytes()).unwrap();
        let vm: cita_vm::json_tests::vm_test::Vm = data;
        // Init context
        let mut ctx = evm::Context::default();
        ctx.coinbase = vm.env.current_coinbase;
        ctx.difficulty = string_2_u256(vm.env.current_difficulty);
        ctx.gas_limit = string_2_u256(vm.env.current_gas_limit).low_u64();
        ctx.number = string_2_u256(vm.env.current_number);
        ctx.timestamp = string_2_u256(vm.env.current_timestamp).low_u64() as u64;

        // Init Config
        let mut cfg = evm::InterpreterConf::default();
        cfg.gas_exp_byte = 10;
        cfg.gas_sload = 50;
        cfg.gas_self_destruct = 0;
        cfg.gas_self_destruct_new_account = 0;

        // Init params
        let mut params = evm::InterpreterParams::default();
        params.origin = vm.exec.origin;
        params.contract.code_address = vm.exec.address;
        params.address = vm.exec.address;
        params.sender = vm.exec.caller;
        if vm.exec.data.len() > 2 {
            params.input = string_2_bytes(vm.exec.data.clone());
        } else {
            params.input = Vec::new();
        }
        params.gas_limit = string_2_u256(vm.exec.gas.clone()).low_u64();
        params.gas_price = string_2_u256(vm.exec.gas_price);
        params.value = string_2_u256(vm.exec.value);
        params.contract.code_data = string_2_bytes(vm.exec.code);

        let mut it = evm::Interpreter::new(ctx, cfg, Box::new(extmock::DataProviderMock::default()), params);

        // Init state db
        if let Some(data) = vm.pre {
            for (address, account) in data.into_iter() {
                for (k, v) in &account.storage {
                    it.data_provider
                        .set_storage(&address, string_2_h256(k.clone()), string_2_h256(v.clone()));
                    it.data_provider
                        .set_storage_origin(&address, string_2_h256(k.clone()), string_2_h256(v.clone()));
                }
            }
        }

        // Doit
        let r = it.run();

        match r {
            Ok(data) => {
                match data {
                    evm::InterpreterResult::Normal(ret, gas, _) | evm::InterpreterResult::Revert(ret, gas) => {
                        // Check statedb
                        if let Some(data) = vm.post {
                            for (address, account) in data.into_iter() {
                                for (k, v) in &account.storage {
                                    assert_eq!(
                                        it.data_provider.get_storage(&address, &string_2_h256(k.to_string())),
                                        string_2_h256(v.to_string())
                                    );
                                }
                            }
                        }
                        // Check output
                        if let Some(data) = vm.out {
                            assert_eq!(ret, string_2_bytes(data))
                        }
                        // Check gas
                        if let Some(expected_gas) = vm.gas {
                            assert_eq!(gas, string_2_u256(expected_gas).low_u64())
                        }
                    }
                    _ => {}
                }
            }
            Err(_) => assert!(vm.gas.is_none() && vm.post.is_none() && vm.logs.is_none()),
        }
    }
}

fn test_json_path(p: &str) {
    let info = fs::metadata(p).unwrap();
    if info.is_dir() {
        for entry in fs::read_dir(p).unwrap() {
            let entry = entry.unwrap();
            let p = entry.path();
            test_json_path(p.to_str().unwrap());
        }
    } else {
        test_json_file(p);
    }
}

#[test]
fn test_vm() {
    test_json_path(r"../jsondata/VMTests/vmArithmeticTest");
    test_json_path(r"../jsondata/VMTests/vmBitwiseLogicOperation");
    test_json_path(r"../jsondata/VMTests/vmBlockInfoTest");
    test_json_path(r"../jsondata/VMTests/vmEnvironmentalInfo");
    test_json_path(r"../jsondata/VMTests/vmIOandFlowOperations");
    test_json_path(r"../jsondata/VMTests/vmLogTest");
    test_json_path(r"../jsondata/VMTests/vmPushDupSwapTest");
    test_json_path(r"../jsondata/VMTests/vmRandomTest");
    test_json_path(r"../jsondata/VMTests/vmSha3Test");
    test_json_path(r"../jsondata/VMTests/vmSystemOperations");
    test_json_path(r"../jsondata/VMTests/vmTests");
}
