use evm_interpreter::uint::{H160, H256, U256};
use evm_interpreter::{
	Capture, Control, EtableInterpreter, ExitError, ExitSucceed, Interpreter, Machine, Opcode,
	etable::{DispatchEtable, MultiEfn, MultiEtable},
	runtime::{
		Context, Log, RuntimeBackend, RuntimeBaseBackend, RuntimeEnvironment, RuntimeState,
		RuntimeStateAndConfig, SetCodeOrigin, TouchKind, TransactionContext,
	},
	trap::CallCreateTrap,
};
use std::rc::Rc;

const CODE1: &str = "60e060020a6000350480632839e92814601e57806361047ff414603457005b602a6004356024356047565b8060005260206000f35b603d6004356099565b8060005260206000f35b600082600014605457605e565b8160010190506093565b81600014606957607b565b60756001840360016047565b90506093565b609060018403608c85600186036047565b6047565b90505b92915050565b6000816000148060a95750816001145b60b05760b7565b81905060cf565b60c1600283036099565b60cb600184036099565b0190505b91905056";
const DATA1: &str = "2839e92800000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001";
const RET1: &str = "000000000000000000000000000000000000000000000000000000000000000d";

#[test]
fn etable_wrap() {
	let code = hex::decode(CODE1).unwrap();
	let data = hex::decode(DATA1).unwrap();

	let wrapped_etable = DispatchEtable::<_, _, ()>::core().wrap(|f, opcode_t| {
		move |machine, handle, position| {
			let opcode = Opcode(machine.code()[position]);
			assert_eq!(opcode_t, opcode);
			println!("opcode: {opcode:?}");
			f(machine, handle, position)
		}
	});

	let machine = Machine::new(Rc::new(code), Rc::new(data), 1024, 10000, ());
	let mut vm = EtableInterpreter::new(machine, &wrapped_etable);
	let result = vm.run(&mut ());
	assert_eq!(result, Capture::Exit(Ok(ExitSucceed::Returned)));
	assert_eq!(vm.retval, hex::decode(RET1).unwrap());
}

#[test]
#[allow(clippy::type_complexity)]
fn etable_wrap2() {
	let code = hex::decode(CODE1).unwrap();
	let data = hex::decode(DATA1).unwrap();

	let wrapped_etable = DispatchEtable::core().wrap(
		|f, opcode_t| -> Box<dyn Fn(&mut Machine<()>, &mut (), usize) -> Control<()>> {
			if opcode_t != Opcode(0x50) {
				Box::new(move |machine, handle, position| {
					let opcode = Opcode(machine.code()[position]);
					assert_eq!(opcode_t, opcode);
					println!("opcode: {opcode:?}");
					f(machine, handle, position)
				})
			} else {
				Box::new(|_machine, _handle, _position| {
					println!("disabled!");
					Control::Trap(Box::new(()))
				})
			}
		},
	);

	let machine = Machine::new(Rc::new(code), Rc::new(data), 1024, 10000, ());
	let mut vm = EtableInterpreter::new(machine, &wrapped_etable);
	let result = vm.run(&mut ());
	assert_eq!(result, Capture::Trap(Box::new(())));
}

pub struct UnimplementedHandler;

impl RuntimeEnvironment for UnimplementedHandler {
	fn block_hash(&self, _number: U256) -> H256 {
		unimplemented!()
	}
	fn block_number(&self) -> U256 {
		unimplemented!()
	}
	fn block_coinbase(&self) -> H160 {
		unimplemented!()
	}
	fn block_timestamp(&self) -> U256 {
		unimplemented!()
	}
	fn block_difficulty(&self) -> U256 {
		unimplemented!()
	}
	fn block_randomness(&self) -> Option<H256> {
		unimplemented!()
	}
	fn block_gas_limit(&self) -> U256 {
		unimplemented!()
	}
	fn block_base_fee_per_gas(&self) -> U256 {
		unimplemented!()
	}
	fn blob_base_fee_per_gas(&self) -> U256 {
		unimplemented!()
	}
	fn blob_versioned_hash(&self, _index: U256) -> H256 {
		unimplemented!()
	}
	fn chain_id(&self) -> U256 {
		unimplemented!()
	}
}

impl RuntimeBaseBackend for UnimplementedHandler {
	fn balance(&self, _address: H160) -> U256 {
		unimplemented!()
	}
	fn code_size(&self, _address: H160) -> U256 {
		unimplemented!()
	}
	fn code_hash(&self, _address: H160) -> H256 {
		unimplemented!()
	}
	fn code(&self, _address: H160) -> Vec<u8> {
		unimplemented!()
	}
	fn storage(&self, _address: H160, _index: H256) -> H256 {
		unimplemented!()
	}
	fn transient_storage(&self, _address: H160, _index: H256) -> H256 {
		unimplemented!()
	}

	fn exists(&self, _address: H160) -> bool {
		unimplemented!()
	}

	fn nonce(&self, _address: H160) -> U256 {
		unimplemented!()
	}
}

impl RuntimeBackend for UnimplementedHandler {
	fn original_storage(&self, _address: H160, _index: H256) -> H256 {
		unimplemented!()
	}

	fn deleted(&self, _address: H160) -> bool {
		unimplemented!()
	}

	fn created(&self, _address: H160) -> bool {
		unimplemented!()
	}

	fn is_cold(&self, _address: H160, _index: Option<H256>) -> bool {
		unimplemented!()
	}

	fn mark_hot(&mut self, _address: H160, _kind: TouchKind) {
		unimplemented!()
	}

	fn mark_storage_hot(&mut self, _address: H160, _index: H256) {
		unimplemented!()
	}

	fn set_storage(&mut self, _address: H160, _index: H256, _value: H256) -> Result<(), ExitError> {
		unimplemented!()
	}
	fn set_transient_storage(
		&mut self,
		_address: H160,
		_index: H256,
		_value: H256,
	) -> Result<(), ExitError> {
		unimplemented!()
	}
	fn log(&mut self, _log: Log) -> Result<(), ExitError> {
		unimplemented!()
	}
	fn mark_delete_reset(&mut self, _address: H160) {
		unimplemented!()
	}

	fn mark_create(&mut self, _address: H160) {
		unimplemented!()
	}

	fn reset_storage(&mut self, _address: H160) {
		unimplemented!()
	}

	fn set_code(
		&mut self,
		_address: H160,
		_code: Vec<u8>,
		_origin: SetCodeOrigin,
	) -> Result<(), ExitError> {
		unimplemented!()
	}

	fn deposit(&mut self, _address: H160, _value: U256) {
		unimplemented!()
	}
	fn withdrawal(&mut self, _address: H160, _value: U256) -> Result<(), ExitError> {
		unimplemented!()
	}

	fn inc_nonce(&mut self, _address: H160) -> Result<(), ExitError> {
		unimplemented!()
	}
}

static RUNTIME_ETABLE: DispatchEtable<RuntimeStateAndConfig, UnimplementedHandler, CallCreateTrap> =
	DispatchEtable::runtime();

#[test]
fn etable_runtime() {
	let code = hex::decode(CODE1).unwrap();
	let data = hex::decode(DATA1).unwrap();
	let mut handler = UnimplementedHandler;

	let machine = Machine::new(
		Rc::new(code),
		Rc::new(data),
		1024,
		10000,
		RuntimeStateAndConfig::with_default_config(RuntimeState {
			context: Context {
				address: H160::default(),
				caller: H160::default(),
				apparent_value: U256::default(),
			},
			transaction_context: TransactionContext {
				gas_price: U256::default(),
				origin: H160::default(),
			}
			.into(),
			retbuf: Vec::new(),
		}),
	);
	let mut vm = EtableInterpreter::new(machine, &RUNTIME_ETABLE);

	let res = vm.run(&mut handler).exit().unwrap();
	assert_eq!(res, Ok(ExitSucceed::Returned));
	assert_eq!(vm.retval, hex::decode(RET1).unwrap());
}

#[test]
fn etable_multi() {
	let prefix = Opcode(0xc1);
	let orig_code = hex::decode("600d60005260206000f3").unwrap();
	let mut code = Vec::new();
	let mut skip = 0;
	for c in orig_code {
		if skip > 0 {
			code.push(c);
			skip -= 1;
		} else {
			code.push(prefix.0);
			code.push(c);
			if let Some(p) = Opcode(c).is_push() {
				skip += p as usize;
			}
		}
	}

	let data = hex::decode(DATA1).unwrap();
	let mut handler = UnimplementedHandler;

	let etable: DispatchEtable<RuntimeStateAndConfig, UnimplementedHandler, CallCreateTrap> =
		DispatchEtable::runtime();
	let mut multi_etable: MultiEtable<RuntimeStateAndConfig, UnimplementedHandler, CallCreateTrap> =
		etable.into();
	let prefix_etable = MultiEtable::from(DispatchEtable::<
		RuntimeStateAndConfig,
		UnimplementedHandler,
		CallCreateTrap,
	>::runtime());
	multi_etable[prefix.as_usize()] = MultiEfn::Node(Box::new(prefix_etable));

	let machine = Machine::new(
		Rc::new(code),
		Rc::new(data),
		1024,
		10000,
		RuntimeStateAndConfig::with_default_config(RuntimeState {
			context: Context {
				address: H160::default(),
				caller: H160::default(),
				apparent_value: U256::default(),
			},
			transaction_context: TransactionContext {
				gas_price: U256::default(),
				origin: H160::default(),
			}
			.into(),
			retbuf: Vec::new(),
		}),
	);
	let mut vm = EtableInterpreter::new(machine, &multi_etable);

	let res = vm.run(&mut handler).exit().unwrap();
	assert_eq!(res, Ok(ExitSucceed::Returned));
	assert_eq!(vm.retval, hex::decode(RET1).unwrap());
}
