use std::fmt::{Display, Formatter};
use std::sync::OnceLock;

use widestring::{WideCStr, WideCString};

use crate::{Add, Container};
use crate::t_array::TArray;

type FStringPrintf = fn(handle: usize, fmt: *const u16);
static FSTRING_PRINTF: OnceLock<usize> = OnceLock::new();

pub trait Printf<T: ?Sized> {
    /// Since rust does not support varargs in functions, it is recommended to call format!
    fn printf<A: AsRef<T>>(handle: usize, fmt: A);
}

pub struct FString(pub(crate) TArray);

impl FString {
    pub fn set_f_string_printf_ptr(f_string_printf: usize) {
        FSTRING_PRINTF.set(f_string_printf).unwrap();
    }
}

impl Printf<str> for FString {
    #[inline(always)]
    fn printf<A: AsRef<str>>(handle: usize, fmt: A) {
        let tmp = WideCString::from_str(fmt).unwrap();
        unsafe {
            std::mem::transmute::<usize, FStringPrintf>(*fstring_printf())(
                handle,
                tmp.as_ucstr().as_ptr(),
            );
        }
    }
}

impl<'a> Container<&'a WideCStr> for FString {
    #[inline(always)]
    fn read_container(&self) -> &'a WideCStr {
        unsafe {
            WideCStr::from_ptr(self.0.data_ptr as *const u16, self.0.len as usize - 1)
        }.unwrap()
    }
}

impl Add for FString {
    fn add(&mut self, other: &mut Self) -> &mut Self {
        let old_len = self.0.resize_grow(Some(other.0.len));
        let src = unsafe {
            std::slice::from_raw_parts_mut(other.0.data_ptr as *mut u16, other.0.len as usize)
        };
        let dst = unsafe {
            std::slice::from_raw_parts_mut(self.0.data_ptr as *mut u16, self.0.len as usize)
        };
        dst[old_len as usize..other.0.len as usize].copy_from_slice(src);
        // Clear the header of the original container
        other.0.release_header_stack();
        self
    }
}

impl Display for FString {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.read_container().to_string_lossy())
    }
}

#[inline(always)]
fn fstring_printf() -> &'static usize {
    FSTRING_PRINTF.get().unwrap()
}