use rustc_middle::mir::interpret::{AllocId, ConstAllocation, InterpResult};
use rustc_middle::mir::*;
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::Ty;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::{bug, span_bug, ty};
use rustc_span::def_id::DefId;
use rustc_target::callconv::FnAbi;

use crate::interpret::{
    self, HasStaticRootDefId, ImmTy, Immediate, InterpCx, PointerArithmetic, interp_ok,
    throw_machine_stop,
};

/// Macro for machine-specific `InterpError` without allocation.
/// (These will never be shown to the user, but they help diagnose ICEs.)
pub macro throw_machine_stop_str($($tt:tt)*) {{
    // We make a new local type for it. The type itself does not carry any information,
    // but its vtable (for the `MachineStopType` trait) does.
    #[derive(Debug)]
    struct Zst;
    // Printing this type shows the desired string.
    impl std::fmt::Display for Zst {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, $($tt)*)
        }
    }
    impl rustc_middle::mir::interpret::MachineStopType for Zst {}

    throw_machine_stop!(Zst)
}}

pub struct DummyMachine;

impl HasStaticRootDefId for DummyMachine {
    fn static_def_id(&self) -> Option<rustc_hir::def_id::LocalDefId> {
        None
    }
}

impl<'tcx> interpret::Machine<'tcx> for DummyMachine {
    interpret::compile_time_machine!(<'tcx>);
    const PANIC_ON_ALLOC_FAIL: bool = true;

    // We want to just eval random consts in the program, so `eval_mir_const` can fail.
    const ALL_CONSTS_ARE_PRECHECKED: bool = false;

    #[inline(always)]
    fn enforce_alignment(_ecx: &InterpCx<'tcx, Self>) -> bool {
        false // no reason to enforce alignment
    }

    fn enforce_validity(_ecx: &InterpCx<'tcx, Self>, _layout: TyAndLayout<'tcx>) -> bool {
        false
    }

    fn before_access_global(
        _tcx: TyCtxtAt<'tcx>,
        _machine: &Self,
        _alloc_id: AllocId,
        alloc: ConstAllocation<'tcx>,
        _static_def_id: Option<DefId>,
        is_write: bool,
    ) -> InterpResult<'tcx> {
        if is_write {
            throw_machine_stop_str!("can't write to global");
        }

        // If the static allocation is mutable, then we can't const prop it as its content
        // might be different at runtime.
        if alloc.inner().mutability.is_mut() {
            throw_machine_stop_str!("can't access mutable globals in ConstProp");
        }

        interp_ok(())
    }

    fn find_mir_or_eval_fn(
        _ecx: &mut InterpCx<'tcx, Self>,
        _instance: ty::Instance<'tcx>,
        _abi: &FnAbi<'tcx, Ty<'tcx>>,
        _args: &[interpret::FnArg<'tcx, Self::Provenance>],
        _destination: &interpret::PlaceTy<'tcx, Self::Provenance>,
        _target: Option<BasicBlock>,
        _unwind: UnwindAction,
    ) -> interpret::InterpResult<'tcx, Option<(&'tcx Body<'tcx>, ty::Instance<'tcx>)>> {
        unimplemented!()
    }

    fn panic_nounwind(
        _ecx: &mut InterpCx<'tcx, Self>,
        _msg: &str,
    ) -> interpret::InterpResult<'tcx> {
        unimplemented!()
    }

    fn call_intrinsic(
        _ecx: &mut InterpCx<'tcx, Self>,
        _instance: ty::Instance<'tcx>,
        _args: &[interpret::OpTy<'tcx, Self::Provenance>],
        _destination: &interpret::PlaceTy<'tcx, Self::Provenance>,
        _target: Option<BasicBlock>,
        _unwind: UnwindAction,
    ) -> interpret::InterpResult<'tcx, Option<ty::Instance<'tcx>>> {
        unimplemented!()
    }

    fn assert_panic(
        _ecx: &mut InterpCx<'tcx, Self>,
        _msg: &rustc_middle::mir::AssertMessage<'tcx>,
        _unwind: UnwindAction,
    ) -> interpret::InterpResult<'tcx> {
        unimplemented!()
    }

    #[inline(always)]
    fn runtime_checks(_ecx: &InterpCx<'tcx, Self>, r: RuntimeChecks) -> InterpResult<'tcx, bool> {
        // Runtime checks have different value depending on the crate they are codegenned in.
        // Verify we aren't trying to evaluate them in mir-optimizations.
        panic!("compiletime machine evaluated {r:?}")
    }

    fn binary_ptr_op(
        ecx: &InterpCx<'tcx, Self>,
        bin_op: BinOp,
        left: &interpret::ImmTy<'tcx, Self::Provenance>,
        right: &interpret::ImmTy<'tcx, Self::Provenance>,
    ) -> interpret::InterpResult<'tcx, ImmTy<'tcx, Self::Provenance>> {
        use rustc_middle::mir::BinOp::*;
        interp_ok(match bin_op {
            Eq | Ne | Lt | Le | Gt | Ge => {
                // Types can differ, e.g. fn ptrs with different `for`.
                assert_eq!(left.layout.backend_repr, right.layout.backend_repr);
                let size = ecx.pointer_size();
                // Just compare the bits. ScalarPairs are compared lexicographically.
                // We thus always compare pairs and simply fill scalars up with 0.
                // If the pointer has provenance, `to_bits` will return `Err` and we bail out.
                let left = match **left {
                    Immediate::Scalar(l) => (l.to_bits(size)?, 0),
                    Immediate::ScalarPair(l1, l2) => (l1.to_bits(size)?, l2.to_bits(size)?),
                    Immediate::Uninit => panic!("we should never see uninit data here"),
                };
                let right = match **right {
                    Immediate::Scalar(r) => (r.to_bits(size)?, 0),
                    Immediate::ScalarPair(r1, r2) => (r1.to_bits(size)?, r2.to_bits(size)?),
                    Immediate::Uninit => panic!("we should never see uninit data here"),
                };
                let res = match bin_op {
                    Eq => left == right,
                    Ne => left != right,
                    Lt => left < right,
                    Le => left <= right,
                    Gt => left > right,
                    Ge => left >= right,
                    _ => bug!(),
                };
                ImmTy::from_bool(res, *ecx.tcx)
            }

            // Some more operations are possible with atomics.
            // The return value always has the provenance of the *left* operand.
            Add | Sub | BitOr | BitAnd | BitXor => {
                throw_machine_stop_str!("pointer arithmetic is not handled")
            }

            _ => span_bug!(ecx.cur_span(), "Invalid operator on pointers: {:?}", bin_op),
        })
    }

    fn expose_provenance(
        _ecx: &InterpCx<'tcx, Self>,
        _provenance: Self::Provenance,
    ) -> interpret::InterpResult<'tcx> {
        unimplemented!()
    }

    fn init_frame(
        _ecx: &mut InterpCx<'tcx, Self>,
        _frame: interpret::Frame<'tcx, Self::Provenance>,
    ) -> interpret::InterpResult<'tcx, interpret::Frame<'tcx, Self::Provenance, Self::FrameExtra>>
    {
        unimplemented!()
    }

    fn stack<'a>(
        _ecx: &'a InterpCx<'tcx, Self>,
    ) -> &'a [interpret::Frame<'tcx, Self::Provenance, Self::FrameExtra>] {
        // Return an empty stack instead of panicking, as `cur_span` uses it to evaluate constants.
        &[]
    }

    fn stack_mut<'a>(
        _ecx: &'a mut InterpCx<'tcx, Self>,
    ) -> &'a mut Vec<interpret::Frame<'tcx, Self::Provenance, Self::FrameExtra>> {
        unimplemented!()
    }

    fn get_default_alloc_params(
        &self,
    ) -> <Self::Bytes as rustc_middle::mir::interpret::AllocBytes>::AllocParams {
    }
}
