use std::cell::{Cell, RefCell};
use std::collections::HashMap;

use gccjit::{Block, CType, Context, Function, FunctionType, LValue, Location, RValue, Type};
use rustc_abi::{Align, HasDataLayout, PointeeInfo, Size, TargetDataLayout, VariantIdx};
use rustc_codegen_ssa::base::wants_msvc_seh;
use rustc_codegen_ssa::errors as ssa_errors;
use rustc_codegen_ssa::traits::{
    BackendTypes, BaseTypeCodegenMethods, MiscCodegenMethods, PacMetadata,
};
use rustc_data_structures::base_n::{ALPHANUMERIC_ONLY, ToBaseN};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_middle::mir::interpret::Allocation;
use rustc_middle::mono::CodegenUnit;
use rustc_middle::span_bug;
use rustc_middle::ty::layout::{
    FnAbiError, FnAbiOf, FnAbiOfHelpers, FnAbiRequest, HasTyCtxt, HasTypingEnv, LayoutError,
    LayoutOfHelpers,
};
use rustc_middle::ty::{self, ExistentialTraitRef, Instance, Ty, TyCtxt};
use rustc_session::Session;
#[cfg(feature = "master")]
use rustc_session::config::DebugInfo;
use rustc_span::{DUMMY_SP, Span, Symbol, respan};
use rustc_target::spec::{HasTargetSpec, HasX86AbiOpt, Target, TlsModel, X86Abi};

#[cfg(feature = "master")]
use crate::abi::conv_to_fn_attribute;
use crate::callee::get_fn;
use crate::common::SignType;

#[cfg_attr(not(feature = "master"), expect(dead_code))]
pub struct CodegenCx<'gcc, 'tcx> {
    /// A cache of converted ConstAllocs
    pub const_cache: RefCell<HashMap<Allocation, RValue<'gcc>>>,
    pub codegen_unit: &'tcx CodegenUnit<'tcx>,
    pub context: &'gcc Context<'gcc>,

    // FIXME(bjorn3): Can this field be removed?
    pub current_func: RefCell<Option<Function<'gcc>>>,
    pub normal_function_addresses: RefCell<FxHashSet<RValue<'gcc>>>,
    pub function_address_names: RefCell<FxHashMap<RValue<'gcc>, String>>,

    pub functions: RefCell<FxHashMap<String, Function<'gcc>>>,
    pub intrinsics: RefCell<FxHashMap<String, Function<'gcc>>>,

    pub tls_model: gccjit::TlsModel,

    pub bool_type: Type<'gcc>,
    pub i8_type: Type<'gcc>,
    pub i16_type: Type<'gcc>,
    pub i32_type: Type<'gcc>,
    pub i64_type: Type<'gcc>,
    pub i128_type: Type<'gcc>,
    pub isize_type: Type<'gcc>,

    pub u8_type: Type<'gcc>,
    pub u16_type: Type<'gcc>,
    pub u32_type: Type<'gcc>,
    pub u64_type: Type<'gcc>,
    pub u128_type: Type<'gcc>,
    pub usize_type: Type<'gcc>,

    pub char_type: Type<'gcc>,
    pub uchar_type: Type<'gcc>,
    pub short_type: Type<'gcc>,
    pub ushort_type: Type<'gcc>,
    pub int_type: Type<'gcc>,
    pub uint_type: Type<'gcc>,
    pub long_type: Type<'gcc>,
    pub ulong_type: Type<'gcc>,
    pub longlong_type: Type<'gcc>,
    pub ulonglong_type: Type<'gcc>,
    pub sizet_type: Type<'gcc>,

    pub supports_128bit_integers: bool,
    pub supports_f16_type: bool,
    pub supports_f32_type: bool,
    pub supports_f64_type: bool,
    pub supports_f128_type: bool,

    pub float_type: Type<'gcc>,
    pub double_type: Type<'gcc>,

    pub linkage: Cell<FunctionType>,
    pub scalar_types: RefCell<FxHashMap<Ty<'tcx>, Type<'gcc>>>,
    pub types: RefCell<FxHashMap<(Ty<'tcx>, Option<VariantIdx>), Type<'gcc>>>,
    pub tcx: TyCtxt<'tcx>,

    pub struct_types: RefCell<FxHashMap<Vec<Type<'gcc>>, Type<'gcc>>>,

    /// Cache instances of monomorphic and polymorphic items
    pub instances: RefCell<FxHashMap<Instance<'tcx>, LValue<'gcc>>>,
    /// Cache function instances of monomorphic and polymorphic items
    pub function_instances: RefCell<FxHashMap<Instance<'tcx>, Function<'gcc>>>,
    /// Cache function instances of intrinsics
    pub intrinsic_instances: RefCell<FxHashMap<Instance<'tcx>, Function<'gcc>>>,
    /// Cache generated vtables
    pub vtables:
        RefCell<FxHashMap<(Ty<'tcx>, Option<ty::ExistentialTraitRef<'tcx>>), RValue<'gcc>>>,

    // FIXME(antoyo): improve the SSA API to not require those.
    /// Mapping from function to indexes of on stack parameters.
    pub on_stack_function_params: RefCell<FxHashMap<Function<'gcc>, FxHashSet<usize>>>,

    /// Cache of emitted const globals (value -> global)
    pub const_globals: RefCell<FxHashMap<RValue<'gcc>, RValue<'gcc>>>,

    /// Map from the address of a global variable (rvalue) to the global variable itself (lvalue).
    /// FIXME(antoyo): remove when the rustc API is fixed.
    pub global_lvalues: RefCell<FxHashMap<RValue<'gcc>, LValue<'gcc>>>,

    /// Cache of constant strings,
    pub const_str_cache: RefCell<FxHashMap<String, LValue<'gcc>>>,

    /// Cache of globals.
    pub globals: RefCell<FxHashMap<String, RValue<'gcc>>>,

    /// A counter that is used for generating local symbol names
    local_gen_sym_counter: Cell<usize>,

    eh_personality: Cell<Option<Function<'gcc>>>,
    #[cfg(feature = "master")]
    pub rust_try_fn: Cell<Option<(Type<'gcc>, Function<'gcc>)>>,

    pub pointee_infos: RefCell<FxHashMap<(Ty<'tcx>, Size), Option<PointeeInfo>>>,

    #[cfg(feature = "master")]
    pub cleanup_blocks: RefCell<FxHashSet<Block<'gcc>>>,
    /// The alignment of a u128/i128 type.
    // We cache this, since it is needed for alignment checks during loads.
    pub int128_align: Align,
}

impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> {
    #[expect(clippy::too_many_arguments)]
    pub fn new(
        context: &'gcc Context<'gcc>,
        codegen_unit: &'tcx CodegenUnit<'tcx>,
        tcx: TyCtxt<'tcx>,
        supports_128bit_integers: bool,
        supports_f16_type: bool,
        supports_f32_type: bool,
        supports_f64_type: bool,
        supports_f128_type: bool,
    ) -> Self {
        #[cfg(feature = "master")]
        if tcx.sess.opts.debuginfo != DebugInfo::None {
            context.set_filename(codegen_unit.name().as_str());
        }

        let create_type = |ctype, rust_type| {
            let layout = tcx
                .layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(rust_type))
                .unwrap();
            let align = layout.align.bytes();
            // For types with size 1, the alignment can be 1 and only 1
            // So, we can skip the call to ``get_aligned`.
            // In the future, we can add a GCC API to query the type align,
            // and call `get_aligned` if and only if that differs from Rust's expectations.
            if layout.size.bytes() == 1 {
                return context.new_c_type(ctype);
            }
            #[cfg(feature = "master")]
            {
                context.new_c_type(ctype).get_aligned(align)
            }
            #[cfg(not(feature = "master"))]
            {
                // Since libgccjit 12 doesn't contain the fix to compare aligned integer types,
                // only align u128 and i128.
                if layout.ty.int_size_and_signed(tcx).0.bytes() == 16 {
                    context.new_c_type(ctype).get_aligned(align)
                } else {
                    context.new_c_type(ctype)
                }
            }
        };

        let i8_type = create_type(CType::Int8t, tcx.types.i8);
        let i16_type = create_type(CType::Int16t, tcx.types.i16);
        let i32_type = create_type(CType::Int32t, tcx.types.i32);
        let i64_type = create_type(CType::Int64t, tcx.types.i64);
        let u8_type = create_type(CType::UInt8t, tcx.types.u8);
        let u16_type = create_type(CType::UInt16t, tcx.types.u16);
        let u32_type = create_type(CType::UInt32t, tcx.types.u32);
        let u64_type = create_type(CType::UInt64t, tcx.types.u64);

        let (i128_type, u128_type) = if supports_128bit_integers {
            let i128_type = create_type(CType::Int128t, tcx.types.i128);
            let u128_type = create_type(CType::UInt128t, tcx.types.u128);
            (i128_type, u128_type)
        } else {
            /*let layout = tcx.layout_of(ParamEnv::reveal_all().and(tcx.types.i128)).unwrap();
            let i128_align = layout.align.bytes();
            let layout = tcx.layout_of(ParamEnv::reveal_all().and(tcx.types.u128)).unwrap();
            let u128_align = layout.align.bytes();*/

            // FIXME(antoyo): re-enable the alignment when libgccjit fixed the issue in
            // gcc_jit_context_new_array_constructor (it should not use reinterpret_cast).
            let i128_type = new_array_type(context, None, i64_type, 2)/*.get_aligned(i128_align)*/;
            let u128_type = new_array_type(context, None, u64_type, 2)/*.get_aligned(u128_align)*/;
            (i128_type, u128_type)
        };

        let tls_model = to_gcc_tls_mode(tcx.sess.tls_model());

        // FIXME(antoyo): set alignment on those types as well.
        let float_type = context.new_type::<f32>();
        let double_type = context.new_type::<f64>();

        let char_type = context.new_c_type(CType::Char);
        let uchar_type = context.new_c_type(CType::UChar);
        let short_type = context.new_c_type(CType::Short);
        let ushort_type = context.new_c_type(CType::UShort);
        let int_type = context.new_c_type(CType::Int);
        let uint_type = context.new_c_type(CType::UInt);
        let long_type = context.new_c_type(CType::Long);
        let ulong_type = context.new_c_type(CType::ULong);
        let longlong_type = context.new_c_type(CType::LongLong);
        let ulonglong_type = context.new_c_type(CType::ULongLong);
        let sizet_type = context.new_c_type(CType::SizeT);

        let usize_type = sizet_type;
        let isize_type = usize_type;
        let bool_type = context.new_type::<bool>();

        let mut functions = FxHashMap::default();
        let builtins = ["abort"];

        for builtin in builtins.iter() {
            functions.insert(builtin.to_string(), context.get_builtin_function(builtin));
        }

        let mut cx = Self {
            int128_align: tcx
                .layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(tcx.types.i128))
                .expect("Can't get the layout of `i128`")
                .align
                .abi,
            const_cache: Default::default(),
            codegen_unit,
            context,
            current_func: RefCell::new(None),
            normal_function_addresses: Default::default(),
            function_address_names: Default::default(),
            functions: RefCell::new(functions),
            intrinsics: RefCell::new(FxHashMap::default()),

            tls_model,

            bool_type,
            i8_type,
            i16_type,
            i32_type,
            i64_type,
            i128_type,
            isize_type,
            usize_type,
            u8_type,
            u16_type,
            u32_type,
            u64_type,
            u128_type,
            char_type,
            uchar_type,
            short_type,
            ushort_type,
            int_type,
            uint_type,
            long_type,
            ulong_type,
            longlong_type,
            ulonglong_type,
            sizet_type,

            supports_128bit_integers,
            supports_f16_type,
            supports_f32_type,
            supports_f64_type,
            supports_f128_type,

            float_type,
            double_type,

            linkage: Cell::new(FunctionType::Internal),
            instances: Default::default(),
            function_instances: Default::default(),
            intrinsic_instances: Default::default(),
            on_stack_function_params: Default::default(),
            vtables: Default::default(),
            const_globals: Default::default(),
            global_lvalues: Default::default(),
            const_str_cache: Default::default(),
            globals: Default::default(),
            scalar_types: Default::default(),
            types: Default::default(),
            tcx,
            struct_types: Default::default(),
            local_gen_sym_counter: Cell::new(0),
            eh_personality: Cell::new(None),
            #[cfg(feature = "master")]
            rust_try_fn: Cell::new(None),
            pointee_infos: Default::default(),
            #[cfg(feature = "master")]
            cleanup_blocks: Default::default(),
        };
        // FIXME(antoyo): instead of doing this, add SsizeT to libgccjit.
        cx.isize_type = usize_type.to_signed(&cx);
        cx
    }

    pub fn rvalue_as_function(&self, value: RValue<'gcc>) -> Function<'gcc> {
        let function: Function<'gcc> = unsafe { std::mem::transmute(value) };
        debug_assert!(
            self.functions.borrow().values().any(|value| *value == function),
            "{:?} ({:?}) is not a function",
            value,
            value.get_type()
        );
        function
    }

    pub fn is_native_int_type(&self, typ: Type<'gcc>) -> bool {
        let types = [
            self.u8_type,
            self.u16_type,
            self.u32_type,
            self.u64_type,
            self.i8_type,
            self.i16_type,
            self.i32_type,
            self.i64_type,
        ];

        for native_type in types {
            if native_type.is_compatible_with(typ) {
                return true;
            }
        }

        self.supports_128bit_integers
            && (self.u128_type.is_compatible_with(typ) || self.i128_type.is_compatible_with(typ))
    }

    pub fn is_non_native_int_type(&self, typ: Type<'gcc>) -> bool {
        !self.supports_128bit_integers
            && (self.u128_type.is_compatible_with(typ) || self.i128_type.is_compatible_with(typ))
    }

    pub fn is_native_int_type_or_bool(&self, typ: Type<'gcc>) -> bool {
        self.is_native_int_type(typ) || typ.is_compatible_with(self.bool_type)
    }

    pub fn is_int_type_or_bool(&self, typ: Type<'gcc>) -> bool {
        self.is_native_int_type(typ)
            || self.is_non_native_int_type(typ)
            || typ.is_compatible_with(self.bool_type)
    }

    pub fn sess(&self) -> &'tcx Session {
        self.tcx.sess
    }

    pub fn bitcast_if_needed(
        &self,
        value: RValue<'gcc>,
        expected_type: Type<'gcc>,
    ) -> RValue<'gcc> {
        if value.get_type() != expected_type {
            self.context.new_bitcast(None, value, expected_type)
        } else {
            value
        }
    }
}

impl<'gcc, 'tcx> BackendTypes for CodegenCx<'gcc, 'tcx> {
    type Function = Function<'gcc>;
    type BasicBlock = Block<'gcc>;
    type Funclet = (); // FIXME(antoyo)

    type Value = RValue<'gcc>;
    type Type = Type<'gcc>;
    type FunctionSignature = Type<'gcc>;

    type DIScope = (); // FIXME(antoyo)
    type DILocation = Location<'gcc>;
    type DIVariable = (); // FIXME(antoyo)
}

impl<'gcc, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> {
    fn vtables(
        &self,
    ) -> &RefCell<FxHashMap<(Ty<'tcx>, Option<ExistentialTraitRef<'tcx>>), RValue<'gcc>>> {
        &self.vtables
    }

    fn get_fn(&self, instance: Instance<'tcx>) -> Function<'gcc> {
        get_fn(self, instance)
    }

    fn get_fn_addr(&self, instance: Instance<'tcx>, _pac: Option<PacMetadata>) -> RValue<'gcc> {
        let func_name = self.tcx.symbol_name(instance).name;

        let func = if let Some(variable) = self.get_declared_value(func_name) {
            return variable;
        } else {
            get_fn(self, instance)
        };
        let ptr = func.get_address(None);

        // FIXME(antoyo): don't do this twice: i.e. in declare_fn and here.
        // FIXME(antoyo): the rustc API seems to call get_fn_addr() when not needed (e.g. for FFI).

        self.normal_function_addresses.borrow_mut().insert(ptr);
        self.function_address_names.borrow_mut().insert(ptr, func_name.to_string());

        ptr
    }

    fn eh_personality(&self) -> Function<'gcc> {
        // The exception handling personality function.
        //
        // If our compilation unit has the `eh_personality` lang item somewhere
        // within it, then we just need to codegen that. Otherwise, we're
        // building an rlib which will depend on some upstream implementation of
        // this function, so we just codegen a generic reference to it. We don't
        // specify any of the types for the function, we just make it a symbol
        // that LLVM can later use.
        //
        // Note that MSVC is a little special here in that we don't use the
        // `eh_personality` lang item at all. Currently LLVM has support for
        // both Dwarf and SEH unwind mechanisms for MSVC targets and uses the
        // *name of the personality function* to decide what kind of unwind side
        // tables/landing pads to emit. It looks like Dwarf is used by default,
        // injecting a dependency on the `_Unwind_Resume` symbol for resuming
        // an "exception", but for MSVC we want to force SEH. This means that we
        // can't actually have the personality function be our standard
        // `rust_eh_personality` function, but rather we wired it up to the
        // CRT's custom personality function, which forces LLVM to consider
        // landing pads as "landing pads for SEH".
        if let Some(personality_func) = self.eh_personality.get() {
            return personality_func;
        }
        let tcx = self.tcx;
        let func = match tcx.lang_items().eh_personality() {
            Some(def_id) if !wants_msvc_seh(self.sess()) => {
                let instance = ty::Instance::expect_resolve(
                    tcx,
                    self.typing_env(),
                    def_id,
                    ty::List::empty(),
                    DUMMY_SP,
                );

                let symbol_name = tcx.symbol_name(instance).name;
                let fn_abi = self.fn_abi_of_instance(instance, ty::List::empty());
                self.linkage.set(FunctionType::Extern);
                self.declare_fn(symbol_name, fn_abi)
            }
            _ => {
                let name = if wants_msvc_seh(self.sess()) {
                    "__CxxFrameHandler3"
                } else {
                    "rust_eh_personality"
                };
                self.declare_func(name, self.type_i32(), &[], true)
            }
        };
        // FIXME(antoyo): apply target cpu attributes.
        self.eh_personality.set(Some(func));
        func
    }

    fn sess(&self) -> &Session {
        self.tcx.sess
    }

    fn set_frame_pointer_type(&self, _llfn: Function<'gcc>) {
        // FIXME(antoyo)
    }

    fn apply_target_cpu_attr(&self, _llfn: Function<'gcc>) {
        // FIXME(antoyo)
    }

    fn declare_c_main(&self, fn_type: Self::Type) -> Option<Self::Function> {
        let entry_name = self.sess().target.entry_name.as_ref();
        if !self.functions.borrow().contains_key(entry_name) {
            let conv = cfg_select! {
                feature = "master" => conv_to_fn_attribute(self.sess(), self.sess().target.entry_abi),
                _ => None,
            };
            Some(self.declare_entry_fn(entry_name, fn_type, conv))
        } else {
            // If the symbol already exists, it is an error: for example, the user wrote
            // #[no_mangle] extern "C" fn main(..) {..}
            None
        }
    }

    fn intrinsic_call_expects_place_always(&self, _name: Symbol) -> bool {
        true
    }
}

impl<'gcc, 'tcx> HasTyCtxt<'tcx> for CodegenCx<'gcc, 'tcx> {
    fn tcx(&self) -> TyCtxt<'tcx> {
        self.tcx
    }
}

impl<'gcc, 'tcx> HasDataLayout for CodegenCx<'gcc, 'tcx> {
    fn data_layout(&self) -> &TargetDataLayout {
        &self.tcx.data_layout
    }
}

impl<'gcc, 'tcx> HasTargetSpec for CodegenCx<'gcc, 'tcx> {
    fn target_spec(&self) -> &Target {
        &self.tcx.sess.target
    }
}

impl<'gcc, 'tcx> HasX86AbiOpt for CodegenCx<'gcc, 'tcx> {
    fn x86_abi_opt(&self) -> X86Abi {
        X86Abi {
            regparm: self.tcx.sess.opts.unstable_opts.regparm,
            reg_struct_return: self.tcx.sess.opts.unstable_opts.reg_struct_return,
        }
    }
}

impl<'gcc, 'tcx> LayoutOfHelpers<'tcx> for CodegenCx<'gcc, 'tcx> {
    #[inline]
    fn handle_layout_err(&self, err: LayoutError<'tcx>, span: Span, ty: Ty<'tcx>) -> ! {
        if let LayoutError::SizeOverflow(_)
        | LayoutError::InvalidSimd { .. }
        | LayoutError::ReferencesError(_) = err
        {
            self.tcx.dcx().span_fatal(span, err.to_string())
        } else {
            self.tcx.dcx().emit_fatal(ssa_errors::FailedToGetLayout { span, ty, err })
        }
    }
}

impl<'gcc, 'tcx> FnAbiOfHelpers<'tcx> for CodegenCx<'gcc, 'tcx> {
    #[inline]
    fn handle_fn_abi_err(
        &self,
        err: FnAbiError<'tcx>,
        span: Span,
        fn_abi_request: FnAbiRequest<'tcx>,
    ) -> ! {
        if let FnAbiError::Layout(LayoutError::SizeOverflow(_) | LayoutError::InvalidSimd { .. }) =
            err
        {
            self.tcx.dcx().emit_fatal(respan(span, err))
        } else {
            match fn_abi_request {
                FnAbiRequest::OfFnPtr { sig, extra_args } => {
                    span_bug!(span, "`fn_abi_of_fn_ptr({sig}, {extra_args:?})` failed: {err:?}");
                }
                FnAbiRequest::OfInstance { instance, extra_args } => {
                    span_bug!(
                        span,
                        "`fn_abi_of_instance({instance}, {extra_args:?})` failed: {err:?}"
                    );
                }
            }
        }
    }
}

impl<'tcx, 'gcc> HasTypingEnv<'tcx> for CodegenCx<'gcc, 'tcx> {
    fn typing_env(&self) -> ty::TypingEnv<'tcx> {
        ty::TypingEnv::fully_monomorphized()
    }
}

impl<'b, 'tcx> CodegenCx<'b, 'tcx> {
    /// Generates a new symbol name with the given prefix. This symbol name must
    /// only be used for definitions with `internal` or `private` linkage.
    pub fn generate_local_symbol_name(&self, prefix: &str) -> String {
        let idx = self.local_gen_sym_counter.get();
        self.local_gen_sym_counter.set(idx + 1);
        // Include a '.' character, so there can be no accidental conflicts with
        // user defined names
        let mut name = String::with_capacity(prefix.len() + 6);
        name.push_str(prefix);
        name.push('.');
        // Offset the index by the base so that always at least two characters
        // are generated. This avoids cases where the suffix is interpreted as
        // size by the assembler (for m68k: .b, .w, .l).
        name.push_str(&(idx as u64 + ALPHANUMERIC_ONLY as u64).to_base(ALPHANUMERIC_ONLY));
        name
    }
}

fn to_gcc_tls_mode(tls_model: TlsModel) -> gccjit::TlsModel {
    match tls_model {
        TlsModel::GeneralDynamic => gccjit::TlsModel::GlobalDynamic,
        TlsModel::LocalDynamic => gccjit::TlsModel::LocalDynamic,
        TlsModel::InitialExec => gccjit::TlsModel::InitialExec,
        TlsModel::LocalExec => gccjit::TlsModel::LocalExec,
        TlsModel::Emulated => gccjit::TlsModel::GlobalDynamic,
    }
}

pub fn new_array_type<'gcc>(
    context: &'gcc Context<'gcc>,
    location: Option<Location<'gcc>>,
    typ: Type<'gcc>,
    size: u64,
) -> Type<'gcc> {
    #[cfg(feature = "master")]
    {
        context.new_array_type_u64(location, typ, size)
    }
    #[cfg(not(feature = "master"))]
    context.new_array_type(location, typ, size)
}
