//! This module provides methods to retrieve allocation information, such as static variables.

use std::io::Read;

use serde::Serialize;

use crate::mir::mono::{Instance, StaticDef};
use crate::target::{Endian, MachineInfo};
use crate::ty::{Allocation, Binder, ExistentialTraitRef, Ty, index_impl};
use crate::{Error, ThreadLocalIndex, with};

/// An allocation in the rustc_public's IR global memory can be either a function pointer,
/// a static, or a "real" allocation with some data in it.
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub enum GlobalAlloc {
    /// The alloc ID is used as a function pointer.
    Function(Instance),
    /// This alloc ID points to a symbolic (not-reified) vtable.
    /// The `None` trait ref is used to represent auto traits.
    VTable(Ty, Option<Binder<ExistentialTraitRef>>),
    /// The alloc ID points to a "lazy" static variable that did not get computed (yet).
    /// This is also used to break the cycle in recursive statics.
    Static(StaticDef),
    /// The alloc ID points to memory.
    Memory(Allocation),
    /// The first pointer-sized segment of a type id. On 64 bit systems, the 128 bit type id
    /// is split into two segments, on 32 bit systems there are 4 segments, and so on.
    TypeId { ty: Ty },
}

impl From<AllocId> for GlobalAlloc {
    fn from(value: AllocId) -> Self {
        with(|cx| cx.global_alloc(value))
    }
}

impl GlobalAlloc {
    /// Retrieve the allocation id for a global allocation if it exists.
    ///
    /// For `[GlobalAlloc::VTable]`, this will return the allocation for the VTable of the given
    /// type for the optional trait if the type implements the trait.
    ///
    /// This method will always return `None` for allocations other than `[GlobalAlloc::VTable]`.
    pub fn vtable_allocation(&self) -> Option<AllocId> {
        with(|cx| cx.vtable_allocation(self))
    }
}

/// A unique identification number for each provenance
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct AllocId(usize, ThreadLocalIndex);
index_impl!(AllocId);

/// Utility function used to read an allocation data into a unassigned integer.
pub(crate) fn read_target_uint(mut bytes: &[u8]) -> Result<u128, Error> {
    let mut buf = [0u8; size_of::<u128>()];
    match MachineInfo::target_endianness() {
        Endian::Little => {
            bytes.read_exact(&mut buf[..bytes.len()])?;
            Ok(u128::from_le_bytes(buf))
        }
        Endian::Big => {
            bytes.read_exact(&mut buf[16 - bytes.len()..])?;
            Ok(u128::from_be_bytes(buf))
        }
    }
}

/// Utility function used to read an allocation data into an assigned integer.
pub(crate) fn read_target_int(mut bytes: &[u8]) -> Result<i128, Error> {
    let mut buf = [0u8; size_of::<i128>()];
    match MachineInfo::target_endianness() {
        Endian::Little => {
            bytes.read_exact(&mut buf[..bytes.len()])?;
            Ok(i128::from_le_bytes(buf))
        }
        Endian::Big => {
            bytes.read_exact(&mut buf[16 - bytes.len()..])?;
            Ok(i128::from_be_bytes(buf))
        }
    }
}
