use std::debug_assert_matches;

use rustc_abi::IntegerType;
use rustc_data_structures::stable_hash::StableHasher;
use rustc_hashes::Hash128;
use rustc_hir::def::DefKind;
use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
use rustc_span::symbol::{Symbol, sym};

trait AbiStableHash<'tcx> {
    fn abi_stable_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher);
}
macro_rules! default_hash_impl {
    ($($t:ty,)+) => {
        $(impl<'tcx> AbiStableHash<'tcx> for $t {
            #[inline]
            fn abi_stable_hash(&self, _tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
                ::std::hash::Hash::hash(self, hasher);
            }
        })*
    };
}

default_hash_impl! { u8, u64, usize, }

impl<'tcx> AbiStableHash<'tcx> for bool {
    #[inline]
    fn abi_stable_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
        (if *self { 1u8 } else { 0u8 }).abi_stable_hash(tcx, hasher);
    }
}

impl<'tcx> AbiStableHash<'tcx> for str {
    #[inline]
    fn abi_stable_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
        self.as_bytes().abi_stable_hash(tcx, hasher);
    }
}

impl<'tcx> AbiStableHash<'tcx> for Symbol {
    #[inline]
    fn abi_stable_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
        self.as_str().abi_stable_hash(tcx, hasher);
    }
}

impl<'tcx, T: AbiStableHash<'tcx>> AbiStableHash<'tcx> for [T] {
    fn abi_stable_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
        self.len().abi_stable_hash(tcx, hasher);
        for item in self {
            item.abi_stable_hash(tcx, hasher);
        }
    }
}

impl<'tcx> AbiStableHash<'tcx> for Ty<'tcx> {
    fn abi_stable_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
        match self.kind() {
            ty::Bool => sym::bool.abi_stable_hash(tcx, hasher),
            ty::Char => sym::char.abi_stable_hash(tcx, hasher),
            ty::Int(int_ty) => int_ty.name_str().abi_stable_hash(tcx, hasher),
            ty::Uint(uint_ty) => uint_ty.name_str().abi_stable_hash(tcx, hasher),
            ty::Float(float_ty) => float_ty.name_str().abi_stable_hash(tcx, hasher),

            ty::Adt(adt_def, args) => {
                adt_def.is_struct().abi_stable_hash(tcx, hasher);
                adt_def.is_enum().abi_stable_hash(tcx, hasher);
                adt_def.is_union().abi_stable_hash(tcx, hasher);

                if let Some(align) = adt_def.repr().align {
                    align.bits().abi_stable_hash(tcx, hasher);
                }

                if let Some(integer) = adt_def.repr().int {
                    match integer {
                        IntegerType::Pointer(sign) => sign.abi_stable_hash(tcx, hasher),
                        IntegerType::Fixed(integer, sign) => {
                            integer.int_ty_str().abi_stable_hash(tcx, hasher);
                            sign.abi_stable_hash(tcx, hasher);
                        }
                    }
                }

                if let Some(pack) = adt_def.repr().pack {
                    pack.bits().abi_stable_hash(tcx, hasher);
                }

                adt_def.repr().c().abi_stable_hash(tcx, hasher);

                for variant in adt_def.variants() {
                    variant.name.abi_stable_hash(tcx, hasher);
                    for field in &variant.fields {
                        field.name.abi_stable_hash(tcx, hasher);
                        let field_ty =
                            tcx.type_of(field.did).instantiate_identity().skip_norm_wip();
                        field_ty.abi_stable_hash(tcx, hasher);
                    }
                }
                args.abi_stable_hash(tcx, hasher);
            }

            ty::Tuple(args) if args.len() == 0 => {}

            // FIXME: Not yet supported.
            ty::Foreign(_)
            | ty::Ref(_, _, _)
            | ty::Str
            | ty::Array(_, _)
            | ty::Pat(_, _)
            | ty::Slice(_)
            | ty::RawPtr(_, _)
            | ty::FnDef(_, _)
            | ty::FnPtr(_, _)
            | ty::Dynamic(_, _)
            | ty::Closure(_, _)
            | ty::CoroutineClosure(_, _)
            | ty::Coroutine(_, _)
            | ty::CoroutineWitness(_, _)
            | ty::Never
            | ty::Tuple(_)
            | ty::Alias(_, _)
            | ty::Param(_)
            | ty::Bound(_, _)
            | ty::Placeholder(_)
            | ty::Infer(_)
            | ty::UnsafeBinder(_) => unreachable!(),

            ty::Error(_) => {}
        }
    }
}

impl<'tcx> AbiStableHash<'tcx> for ty::FnSig<'tcx> {
    fn abi_stable_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
        for ty in self.inputs_and_output {
            ty.abi_stable_hash(tcx, hasher);
        }
        self.safety().is_safe().abi_stable_hash(tcx, hasher);
    }
}

impl<'tcx> AbiStableHash<'tcx> for ty::GenericArg<'tcx> {
    fn abi_stable_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
        self.kind().abi_stable_hash(tcx, hasher);
    }
}

impl<'tcx> AbiStableHash<'tcx> for ty::GenericArgKind<'tcx> {
    fn abi_stable_hash(&self, tcx: TyCtxt<'tcx>, hasher: &mut StableHasher) {
        match self {
            ty::GenericArgKind::Type(t) => t.abi_stable_hash(tcx, hasher),
            ty::GenericArgKind::Lifetime(_) | ty::GenericArgKind::Const(_) => unimplemented!(),
        }
    }
}

pub(crate) fn compute_hash_of_export_fn<'tcx>(
    tcx: TyCtxt<'tcx>,
    instance: Instance<'tcx>,
) -> String {
    let def_id = instance.def_id();
    debug_assert_matches!(tcx.def_kind(def_id), DefKind::Fn | DefKind::AssocFn);

    let args = instance.args;
    let sig_ty = tcx.fn_sig(def_id).instantiate(tcx, args).skip_norm_wip();
    let sig_ty = tcx.instantiate_bound_regions_with_erased(sig_ty);

    let hash = {
        let mut hasher = StableHasher::new();
        sig_ty.abi_stable_hash(tcx, &mut hasher);
        hasher.finish::<Hash128>()
    };

    hash.as_u128().to_string()
}
