use crate::stdlib::{any::Any, boxed::Box, collections::HashMap, prelude::*};
use crate::any_box;
use crate::serde::deserialize_program::ApTracking;
use crate::serde::deserialize_program::OffsetValue;
use crate::serde::deserialize_program::Reference;
use crate::types::exec_scope::ExecutionScopes;
use crate::types::instruction::Register;
use crate::vm::errors::hint_errors::HintError;
use crate::vm::errors::vm_errors::VirtualMachineError;
use crate::vm::runners::cairo_runner::ResourceTracker;
use crate::vm::vm_core::VirtualMachine;
use super::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData;
use felt::Felt252;
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
pub trait HintProcessorLogic {
    fn execute_hint(
        &mut self,
        vm: &mut VirtualMachine,
        exec_scopes: &mut ExecutionScopes,
        hint_data: &Box<dyn Any>,
        constants: &HashMap<String, Felt252>,
    ) -> Result<(), HintError>;
    fn compile_hint(
        &self,
        hint_code: &str,
        ap_tracking_data: &ApTracking,
        reference_ids: &HashMap<String, usize>,
        references: &[HintReference],
    ) -> Result<Box<dyn Any>, VirtualMachineError> {
        Ok(any_box!(HintProcessorData {
            code: hint_code.to_string(),
            ap_tracking: ap_tracking_data.clone(),
            ids_data: get_ids_data(reference_ids, references)?,
        }))
    }
}
pub trait HintProcessor: HintProcessorLogic + ResourceTracker {}
impl<T> HintProcessor for T where T: HintProcessorLogic + ResourceTracker {}
fn get_ids_data(
    reference_ids: &HashMap<String, usize>,
    references: &[HintReference],
) -> Result<HashMap<String, HintReference>, VirtualMachineError> {
    let mut ids_data = HashMap::<String, HintReference>::new();
    for (path, ref_id) in reference_ids {
        let name = path
            .rsplit('.')
            .next()
            .ok_or(VirtualMachineError::Unexpected)?;
        ids_data.insert(
            name.to_string(),
            references
                .get(*ref_id)
                .ok_or(VirtualMachineError::Unexpected)?
                .clone(),
        );
    }
    Ok(ids_data)
}
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct HintReference {
    pub offset1: OffsetValue,
    pub offset2: OffsetValue,
    pub dereference: bool,
    pub ap_tracking_data: Option<ApTracking>,
    pub cairo_type: Option<String>,
}
impl HintReference {
    pub fn new_simple(offset1: i32) -> Self {
        HintReference {
            offset1: OffsetValue::Reference(Register::FP, offset1, false),
            offset2: OffsetValue::Value(0),
            ap_tracking_data: None,
            dereference: true,
            cairo_type: None,
        }
    }
    pub fn new(offset1: i32, offset2: i32, inner_dereference: bool, dereference: bool) -> Self {
        HintReference {
            offset1: OffsetValue::Reference(Register::FP, offset1, inner_dereference),
            offset2: OffsetValue::Value(offset2),
            ap_tracking_data: None,
            dereference,
            cairo_type: None,
        }
    }
}
impl From<Reference> for HintReference {
    fn from(reference: Reference) -> Self {
        HintReference {
            offset1: reference.value_address.offset1.clone(),
            offset2: reference.value_address.offset2.clone(),
            dereference: reference.value_address.dereference,
            ap_tracking_data: match (
                &reference.value_address.offset1,
                &reference.value_address.offset2,
            ) {
                (OffsetValue::Reference(Register::AP, _, _), _)
                | (_, OffsetValue::Reference(Register::AP, _, _)) => {
                    Some(reference.ap_tracking_data.clone())
                }
                _ => None,
            },
            cairo_type: Some(reference.value_address.value_type.clone()),
        }
    }
}