use crate::{
    hint_processor::hint_processor_definition::HintProcessor,
    types::program::Program,
    vm::{
        errors::{cairo_run_errors::CairoRunError, vm_exception::VmException},
        runners::cairo_runner::CairoRunner,
        security::verify_secure_runner,
        vm_core::VirtualMachine,
    },
};
use bincode::enc::write::Writer;
use felt::Felt252;
use thiserror_no_std::Error;
#[cfg(feature = "arbitrary")]
use arbitrary::{self, Arbitrary, Unstructured};
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct CairoRunConfig<'a> {
    #[cfg_attr(feature = "arbitrary", arbitrary(value = "main"))]
    pub entrypoint: &'a str,
    pub trace_enabled: bool,
    pub relocate_mem: bool,
    #[cfg_attr(feature = "arbitrary", arbitrary(with = arbitrary_layout))]
    pub layout: &'a str,
    pub proof_mode: bool,
    pub secure_run: Option<bool>,
    pub disable_trace_padding: bool,
}
#[cfg(feature = "arbitrary")]
fn arbitrary_layout<'a>(u: &mut Unstructured) -> arbitrary::Result<&'a str> {
    let layouts = [
        "plain",
        "small",
        "dex",
        "starknet",
        "starknet_with_keccak",
        "recursive_large_output",
        "all_cairo",
        "all_solidity",
        "dynamic",
    ];
    Ok(u.choose(&layouts)?)
}
impl<'a> Default for CairoRunConfig<'a> {
    fn default() -> Self {
        CairoRunConfig {
            entrypoint: "main",
            trace_enabled: false,
            relocate_mem: false,
            layout: "plain",
            proof_mode: false,
            secure_run: None,
            disable_trace_padding: false,
        }
    }
}
pub fn cairo_run(
    program_content: &[u8],
    cairo_run_config: &CairoRunConfig,
    hint_executor: &mut dyn HintProcessor,
) -> Result<(CairoRunner, VirtualMachine), CairoRunError> {
    let program = Program::from_bytes(program_content, Some(cairo_run_config.entrypoint))?;
    let secure_run = cairo_run_config
        .secure_run
        .unwrap_or(!cairo_run_config.proof_mode);
    let mut cairo_runner = CairoRunner::new(
        &program,
        cairo_run_config.layout,
        cairo_run_config.proof_mode,
    )?;
    let mut vm = VirtualMachine::new(cairo_run_config.trace_enabled);
    let end = cairo_runner.initialize(&mut vm)?;
    cairo_runner
        .run_until_pc(end, &mut vm, hint_executor)
        .map_err(|err| VmException::from_vm_error(&cairo_runner, &vm, err))?;
    cairo_runner.end_run(
        cairo_run_config.disable_trace_padding,
        false,
        &mut vm,
        hint_executor,
    )?;
    vm.verify_auto_deductions()?;
    cairo_runner.read_return_values(&mut vm)?;
    if cairo_run_config.proof_mode {
        cairo_runner.finalize_segments(&mut vm)?;
    }
    if secure_run {
        verify_secure_runner(&cairo_runner, true, None, &mut vm)?;
    }
    cairo_runner.relocate(&mut vm, cairo_run_config.relocate_mem)?;
    Ok((cairo_runner, vm))
}
#[cfg(feature = "arbitrary")]
pub fn cairo_run_fuzzed_program(
    program: Program,
    cairo_run_config: &CairoRunConfig,
    hint_executor: &mut dyn HintProcessor,
    steps_limit: usize,
) -> Result<(CairoRunner, VirtualMachine), CairoRunError> {
    use crate::vm::errors::vm_errors::VirtualMachineError;
    let secure_run = cairo_run_config
        .secure_run
        .unwrap_or(!cairo_run_config.proof_mode);
    let mut cairo_runner = CairoRunner::new(
        &program,
        cairo_run_config.layout,
        cairo_run_config.proof_mode,
    )?;
    let mut vm = VirtualMachine::new(cairo_run_config.trace_enabled);
    let _end = cairo_runner.initialize(&mut vm)?;
    let res = match cairo_runner.run_until_steps(steps_limit, &mut vm, hint_executor) {
        Err(VirtualMachineError::EndOfProgram(_remaining)) => Ok(()), res => res,
    };
    res.map_err(|err| VmException::from_vm_error(&cairo_runner, &vm, err))?;
    cairo_runner.end_run(false, false, &mut vm, hint_executor)?;
    vm.verify_auto_deductions()?;
    cairo_runner.read_return_values(&mut vm)?;
    if cairo_run_config.proof_mode {
        cairo_runner.finalize_segments(&mut vm)?;
    }
    if secure_run {
        verify_secure_runner(&cairo_runner, true, None, &mut vm)?;
    }
    cairo_runner.relocate(&mut vm, cairo_run_config.relocate_mem)?;
    Ok((cairo_runner, vm))
}
#[derive(Debug, Error)]
#[error("Failed to encode trace at position {0}, serialize error: {1}")]
pub struct EncodeTraceError(usize, bincode::error::EncodeError);
pub fn write_encoded_trace(
    relocated_trace: &[crate::vm::trace::trace_entry::TraceEntry],
    dest: &mut impl Writer,
) -> Result<(), EncodeTraceError> {
    for (i, entry) in relocated_trace.iter().enumerate() {
        dest.write(&((entry.ap as u64).to_le_bytes()))
            .map_err(|e| EncodeTraceError(i, e))?;
        dest.write(&((entry.fp as u64).to_le_bytes()))
            .map_err(|e| EncodeTraceError(i, e))?;
        dest.write(&((entry.pc as u64).to_le_bytes()))
            .map_err(|e| EncodeTraceError(i, e))?;
    }
    Ok(())
}
pub fn write_encoded_memory(
    relocated_memory: &[Option<Felt252>],
    dest: &mut impl Writer,
) -> Result<(), EncodeTraceError> {
    for (i, memory_cell) in relocated_memory.iter().enumerate() {
        match memory_cell {
            None => continue,
            Some(unwrapped_memory_cell) => {
                dest.write(&(i as u64).to_le_bytes())
                    .map_err(|e| EncodeTraceError(i, e))?;
                dest.write(&unwrapped_memory_cell.to_le_bytes())
                    .map_err(|e| EncodeTraceError(i, e))?;
            }
        }
    }
    Ok(())
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::stdlib::prelude::*;
    use crate::{
        hint_processor::{
            builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor,
            hint_processor_definition::HintProcessor,
        },
        utils::test_utils::*,
    };
    use bincode::enc::write::SliceWriter;
    use felt::Felt252;
    #[cfg(target_arch = "wasm32")]
    use wasm_bindgen_test::*;
    fn run_test_program(
        program_content: &[u8],
        hint_processor: &mut dyn HintProcessor,
    ) -> Result<(CairoRunner, VirtualMachine), CairoRunError> {
        let program = Program::from_bytes(program_content, Some("main")).unwrap();
        let mut cairo_runner = cairo_runner!(program);
        let mut vm = vm!(true);
        let end = cairo_runner
            .initialize(&mut vm)
            .map_err(CairoRunError::Runner)?;
        assert!(cairo_runner
            .run_until_pc(end, &mut vm, hint_processor)
            .is_ok());
        Ok((cairo_runner, vm))
    }
    #[test]
    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
    fn cairo_run_custom_entry_point() {
        let program = Program::from_bytes(
            include_bytes!("../../cairo_programs/not_main.json"),
            Some("not_main"),
        )
        .unwrap();
        let mut vm = vm!();
        let mut hint_processor = BuiltinHintProcessor::new_empty();
        let mut cairo_runner = cairo_runner!(program);
        let end = cairo_runner.initialize(&mut vm).unwrap();
        assert!(cairo_runner
            .run_until_pc(end, &mut vm, &mut hint_processor)
            .is_ok());
        assert!(cairo_runner.relocate(&mut vm, true).is_ok());
        assert_eq!(cairo_runner.relocated_memory[2], Some(Felt252::new(123)));
    }
    #[test]
    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
    fn cairo_run_with_no_data_program() {
        let mut hint_processor = BuiltinHintProcessor::new_empty();
        let no_data_program_path =
            include_bytes!("../../cairo_programs/manually_compiled/no_data_program.json");
        let cairo_run_config = CairoRunConfig::default();
        assert!(cairo_run(no_data_program_path, &cairo_run_config, &mut hint_processor,).is_err());
    }
    #[test]
    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
    fn cairo_run_with_no_main_program() {
        let mut hint_processor = BuiltinHintProcessor::new_empty();
        let no_main_program =
            include_bytes!("../../cairo_programs/manually_compiled/no_main_program.json");
        let cairo_run_config = CairoRunConfig::default();
        assert!(cairo_run(no_main_program, &cairo_run_config, &mut hint_processor,).is_err());
    }
    #[test]
    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
    fn cairo_run_with_invalid_memory() {
        let mut hint_processor = BuiltinHintProcessor::new_empty();
        let invalid_memory =
            include_bytes!("../../cairo_programs/manually_compiled/invalid_memory.json");
        let cairo_run_config = CairoRunConfig::default();
        assert!(cairo_run(invalid_memory, &cairo_run_config, &mut hint_processor,).is_err());
    }
    #[test]
    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
    fn write_output_program() {
        let program_content = include_bytes!("../../cairo_programs/bitwise_output.json");
        let mut hint_processor = BuiltinHintProcessor::new_empty();
        let (_, mut vm) = run_test_program(program_content, &mut hint_processor)
            .expect("Couldn't initialize cairo runner");
        let mut output_buffer = String::new();
        vm.write_output(&mut output_buffer).unwrap();
        assert_eq!(&output_buffer, "0\n");
    }
    #[test]
    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
    fn write_binary_trace_file() {
        let program_content = include_bytes!("../../cairo_programs/struct.json");
        let expected_encoded_trace =
            include_bytes!("../../cairo_programs/trace_memory/cairo_trace_struct");
        let mut hint_processor = BuiltinHintProcessor::new_empty();
        let cairo_runner_result = run_test_program(program_content, &mut hint_processor);
        let (mut cairo_runner, mut vm) = cairo_runner_result.unwrap();
        assert!(cairo_runner.relocate(&mut vm, false).is_ok());
        let trace_entries = vm.get_relocated_trace().unwrap();
        let mut buffer = [0; 24];
        let mut buff_writer = SliceWriter::new(&mut buffer);
        write_encoded_trace(trace_entries, &mut buff_writer).unwrap();
        assert_eq!(buffer, *expected_encoded_trace);
    }
    #[test]
    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
    fn write_binary_memory_file() {
        let program_content = include_bytes!("../../cairo_programs/struct.json");
        let expected_encoded_memory =
            include_bytes!("../../cairo_programs/trace_memory/cairo_memory_struct");
        let mut hint_processor = BuiltinHintProcessor::new_empty();
        let cairo_runner_result = run_test_program(program_content, &mut hint_processor);
        let (mut cairo_runner, mut vm) = cairo_runner_result.unwrap();
        assert!(cairo_runner.relocate(&mut vm, true).is_ok());
        let mut buffer = [0; 120];
        let mut buff_writer = SliceWriter::new(&mut buffer);
        write_encoded_memory(&cairo_runner.relocated_memory, &mut buff_writer).unwrap();
        assert_eq!(*expected_encoded_memory, buffer);
    }
    #[test]
    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
    fn run_with_no_trace() {
        let program = Program::from_bytes(
            include_bytes!("../../cairo_programs/struct.json"),
            Some("main"),
        )
        .unwrap();
        let mut hint_processor = BuiltinHintProcessor::new_empty();
        let mut cairo_runner = cairo_runner!(program);
        let mut vm = vm!();
        let end = cairo_runner.initialize(&mut vm).unwrap();
        assert!(cairo_runner
            .run_until_pc(end, &mut vm, &mut hint_processor)
            .is_ok());
        assert!(cairo_runner.relocate(&mut vm, false).is_ok());
        assert!(vm.get_relocated_trace().is_err());
    }
}