use anyhow::Context;
use itertools::Itertools;
use std::{hash::Hash, ptr::NonNull};

use crate::core_types::{self, FName};
use crate::tarray::TArray;

use self::traits::{AsUObject, AsUStruct, FromRaw};

#[repr(transparent)]
#[derive(Debug, Clone)]
pub struct UObject {
    inner: NonNull<core_types::UObject>,
}

unsafe impl Send for UObject {}
unsafe impl Sync for UObject {}

impl Eq for UObject {}

impl PartialEq for UObject {
    fn eq(&self, other: &Self) -> bool {
        unsafe {
            self.inner.as_ref().internal_index == other.inner.as_ref().internal_index
                && self.inner.as_ref().name == other.inner.as_ref().name
        }
    }
}

impl Hash for UObject {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.internal_index().hash(state);
        self.name().hash(state);
    }
}

#[repr(transparent)]
#[derive(Debug, Clone)]
pub struct UClass {
    pub(crate) inner: NonNull<core_types::UClass>,
}

impl Eq for UClass {}

impl PartialEq for UClass {
    fn eq(&self, other: &Self) -> bool {
        self.as_uobject().eq(&other.as_uobject())
    }
}

impl Hash for UClass {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.as_uobject().hash(state);
    }
}

#[repr(transparent)]
#[derive(Debug, Clone)]
pub struct UField {
    inner: NonNull<core_types::UField>,
}

impl Eq for UField {}

impl PartialEq for UField {
    fn eq(&self, other: &Self) -> bool {
        self.as_uobject().eq(&other.as_uobject())
    }
}

impl Hash for UField {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.as_uobject().hash(state);
    }
}

impl UObject {
    pub fn new(inner: NonNull<core_types::UObject>) -> Self {
        Self { inner }
    }

    pub fn maybe_new(inner: Option<NonNull<core_types::UObject>>) -> Option<Self> {
        inner.map(|inner| Self { inner })
    }

    pub fn to_inner(self) -> NonNull<core_types::UObject> {
        self.inner
    }

    pub const unsafe fn cast<T>(self) -> NonNull<T> {
        self.inner.cast::<T>()
    }

    /// returns a `Self` with the provided raw pointer, if the pointer is not null
    pub fn maybe_with_raw(raw: *mut core_types::UObject) -> Option<Self> {
        Self::maybe_new(NonNull::new(raw))
    }

    pub fn class(&self) -> Option<UClass> {
        unsafe { self.inner.as_ref().class.map(|inner| UClass::new(inner)) }
    }

    pub fn internal_index(&self) -> u32 {
        unsafe { self.inner.as_ref().internal_index }
    }

    pub fn name(&self) -> core_types::FName {
        unsafe { self.inner.as_ref().name }
    }

    pub fn get_name(&self) -> anyhow::Result<String> {
        let name = self.name();
        name.get_name()
            .map(|name_str| {
                if name.number > 0 {
                    format!("{}_{}", name_str, name.number)
                } else {
                    name_str
                }
            })
            .map(|name_str| {
                name_str
                    .rfind("/")
                    .map(|pos| name_str[(pos + 1)..].to_string())
                    .unwrap_or_else(|| name_str)
            })
    }

    pub fn get_name_or_default(&self) -> String {
        self.get_name()
            .unwrap_or_else(|_| "DefaultObjectName".to_string())
    }

    pub fn get_full_name(&self) -> anyhow::Result<String> {
        let tmp = self
            .iter_outer_objects()
            .map(|obj| obj.get_name())
            .fold_ok(String::new(), |acc, obj_name| {
                format!("{}.{}", obj_name, acc)
            })?;

        Ok(format!(
            "{} {}{}",
            self.class()
                .context("invalid class pointer")?
                .as_uobject()
                .get_name_or_default(),
            tmp,
            self.get_name_or_default()
        ))
    }

    pub fn get_full_name_or_default(&self) -> String {
        self.get_full_name()
            .unwrap_or_else(|_| "DefaultObjectFullName".to_string())
    }

    pub fn outer(&self) -> Option<UObject> {
        unsafe { self.inner.as_ref().outer.map(|inner| UObject::new(inner)) }
    }

    /// returns the package object of this object
    pub fn outermost(&self) -> Option<UObject> {
        self.iter_outer_objects().last()
    }

    pub fn is_package_object(&self) -> bool {
        unsafe { self.inner.as_ref().outer.is_none() }
    }

    pub fn iter_outer_objects(&self) -> OuterObjectIterator {
        OuterObjectIterator::new(self.clone())
    }

    pub fn is_a(&self, other: &UClass) -> bool {
        self.class()
            .map(|class| class.iter_super_classes().contains(other))
            .unwrap_or(false)
    }
}

pub struct OuterObjectIterator {
    object: UObject,
}

impl OuterObjectIterator {
    pub fn new(object: UObject) -> Self {
        Self { object }
    }
}

impl Iterator for OuterObjectIterator {
    type Item = UObject;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some(outer) = self.object.outer() {
            self.object = outer.clone();

            Some(outer)
        } else {
            None
        }
    }
}

impl From<UObject> for UClass {
    fn from(obj: UObject) -> Self {
        Self::new(obj.inner.cast())
    }
}

impl AsUStruct for UClass {
    fn as_ustruct(&self) -> UStruct {
        UStruct::new(self.inner.clone().cast())
    }
}

impl AsRef<core_types::UClass> for UClass {
    fn as_ref(&self) -> &core_types::UClass {
        unsafe { self.inner.as_ref() }
    }
}

impl FromRaw<core_types::UClass> for UClass {
    fn from_non_null(inner: NonNull<core_types::UClass>) -> Self {
        Self::new(inner)
    }
}

impl UClass {
    pub fn new(inner: NonNull<core_types::UClass>) -> Self {
        Self { inner }
    }
    pub fn iter_super_classes(&self) -> SuperClassIter {
        SuperClassIter::new(self.clone())
    }
}

pub struct SuperClassIter {
    class: UClass,
}

impl SuperClassIter {
    pub fn new(class: UClass) -> Self {
        Self { class }
    }
}

impl Iterator for SuperClassIter {
    type Item = UClass;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some(next) = self.class.super_struct().map(|spr| spr.as_uobject().into()) {
            let item = core::mem::replace(&mut self.class, next);
            Some(item)
        } else {
            None
        }
    }
}

impl From<UObject> for UField {
    fn from(obj: UObject) -> Self {
        Self::new(obj.inner.cast())
    }
}

impl UField {
    pub fn new(inner: NonNull<core_types::UField>) -> Self {
        Self { inner }
    }

    pub fn maybe_new(inner: Option<NonNull<core_types::UField>>) -> Option<Self> {
        inner.map(|inner| Self { inner })
    }

    pub fn maybe_with_raw(raw: *mut core_types::UField) -> Option<Self> {
        NonNull::new(raw).map(|inner| Self::new(inner))
    }

    pub fn as_uobject(&self) -> UObject {
        UObject::new(self.inner.clone().cast())
    }

    pub unsafe fn as_ref(&self) -> &core_types::UField {
        self.inner.as_ref()
    }

    pub fn next(&self) -> Option<Self> {
        Self::maybe_new(unsafe { self.as_ref().next })
    }
}

#[derive(Debug, Clone)]
pub struct UEnum {
    inner: NonNull<core_types::UEnum>,
}

impl From<UObject> for UEnum {
    fn from(obj: UObject) -> Self {
        Self::new(obj.inner.cast())
    }
}

impl UEnum {
    pub fn new(inner: NonNull<core_types::UEnum>) -> Self {
        Self { inner }
    }

    pub fn maybe_new(inner: Option<NonNull<core_types::UEnum>>) -> Option<Self> {
        inner.map(|inner| Self { inner })
    }

    pub fn maybe_with_raw(raw: *mut core_types::UEnum) -> Option<Self> {
        NonNull::new(raw).map(|inner| Self::new(inner))
    }

    pub fn as_uobject(&self) -> UObject {
        UObject::new(self.inner.clone().cast())
    }

    pub unsafe fn as_ref(&self) -> &core_types::UEnum {
        self.inner.as_ref()
    }

    pub fn get_names(&self) -> &TArray<FName> {
        unsafe { &self.as_ref().names }
    }
}

#[derive(Debug, Clone)]
pub struct UStruct {
    inner: NonNull<core_types::UStruct>,
}

impl From<UObject> for UStruct {
    fn from(obj: UObject) -> Self {
        Self::new(obj.inner.cast())
    }
}

impl FromRaw<core_types::UStruct> for UStruct {
    fn from_non_null(inner: NonNull<core_types::UStruct>) -> Self {
        Self::new(inner)
    }
}

impl UStruct {
    pub fn new(inner: NonNull<core_types::UStruct>) -> Self {
        Self { inner }
    }

    pub unsafe fn as_raw(&self) -> &core_types::UStruct {
        self.inner.as_ref()
    }

    fn super_struct(&self) -> Option<Self> {
        Self::from_maybe_non_null(unsafe { self.as_raw().super_field })
    }

    fn children(&self) -> Option<UField> {
        UField::maybe_new(unsafe { self.as_raw().children })
    }

    fn iter_fields(&self) -> Option<UStructFieldIterator> {
        self.children()
            .map(|field| UStructFieldIterator::new(field))
    }
}

impl traits::AsUStruct for UStruct {
    fn as_ustruct(&self) -> UStruct {
        self.clone()
    }
}

pub struct UStructFieldIterator {
    field: UField,
}

impl UStructFieldIterator {
    pub fn new(field: UField) -> Self {
        Self { field }
    }
}

impl Iterator for UStructFieldIterator {
    type Item = UField;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some(mut next) = self.field.next() {
            std::mem::swap(&mut self.field, &mut next);

            Some(next)
        } else {
            None
        }
    }
}

#[derive(Debug, Clone)]
pub struct UScriptStruct {
    inner: NonNull<core_types::UStruct>,
}

impl From<UObject> for UScriptStruct {
    fn from(obj: UObject) -> Self {
        Self::new(obj.inner.cast())
    }
}

impl UScriptStruct {
    pub fn new(inner: NonNull<core_types::UStruct>) -> Self {
        Self { inner }
    }

    pub fn maybe_new(inner: Option<NonNull<core_types::UStruct>>) -> Option<Self> {
        inner.map(|inner| Self { inner })
    }

    pub fn maybe_with_raw(raw: *mut core_types::UStruct) -> Option<Self> {
        NonNull::new(raw).map(|inner| Self::new(inner))
    }

    pub fn as_uobject(&self) -> UObject {
        UObject::new(self.inner.clone().cast())
    }

    pub unsafe fn as_ref(&self) -> &core_types::UStruct {
        self.inner.as_ref()
    }

    pub fn iter_fields(&self) -> Option<UStructFieldIterator> {
        UField::maybe_new(unsafe { self.as_ref().children })
            .map(|field| UStructFieldIterator::new(field))
    }
}

#[derive(Debug, Clone)]
pub struct UProperty {
    inner: NonNull<core_types::UProperty>,
}

impl From<UObject> for UProperty {
    /// WARNING: There is no type check on this conversion, so it is most certainly unsafe
    fn from(obj: UObject) -> Self {
        Self::new(obj.inner.cast())
    }
}

impl UProperty {
    pub fn new(inner: NonNull<core_types::UProperty>) -> Self {
        Self { inner }
    }

    pub fn maybe_new(inner: Option<NonNull<core_types::UProperty>>) -> Option<Self> {
        inner.map(|inner| Self { inner })
    }

    pub fn maybe_with_raw(raw: *mut core_types::UProperty) -> Option<Self> {
        NonNull::new(raw).map(|inner| Self::new(inner))
    }

    pub fn as_uobject(&self) -> UObject {
        UObject::new(self.inner.clone().cast())
    }

    pub unsafe fn as_ref(&self) -> &core_types::UProperty {
        self.inner.as_ref()
    }
}

#[derive(Debug, Clone)]
pub struct UFunction {
    inner: NonNull<core_types::UFunction>,
}

impl From<UObject> for UFunction {
    /// WARNING: There is no type check on this conversion, so it is most certainly unsafe
    fn from(obj: UObject) -> Self {
        Self::new(obj.inner.cast())
    }
}

impl UFunction {
    pub fn new(inner: NonNull<core_types::UFunction>) -> Self {
        Self { inner }
    }

    pub fn maybe_new(inner: Option<NonNull<core_types::UFunction>>) -> Option<Self> {
        inner.map(|inner| Self { inner })
    }

    pub fn maybe_with_raw(raw: *mut core_types::UFunction) -> Option<Self> {
        NonNull::new(raw).map(|inner| Self::new(inner))
    }

    pub fn as_uobject(&self) -> UObject {
        UObject::new(self.inner.clone().cast())
    }

    pub unsafe fn as_ref(&self) -> &core_types::UFunction {
        self.inner.as_ref()
    }
}

#[derive(Debug, Clone)]
pub enum UAnyType {
    UObject(UObject),
    UClass(UClass),
    UField(UField),
    UScriptStruct(UScriptStruct),
    UProperty(UProperty),
    UEnum(UEnum),
    UStruct(UStruct),
    UFunction(UFunction),
}

impl UAnyType {
    pub fn as_uobject(&self) -> UObject {
        match self {
            UAnyType::UObject(rep) => rep.clone(),
            UAnyType::UClass(rep) => rep.as_uobject(),
            UAnyType::UField(rep) => rep.as_uobject(),
            UAnyType::UScriptStruct(rep) => rep.as_uobject(),
            UAnyType::UProperty(rep) => rep.as_uobject(),
            UAnyType::UEnum(rep) => rep.as_uobject(),
            UAnyType::UStruct(rep) => rep.as_uobject(),
            UAnyType::UFunction(rep) => rep.as_uobject(),
        }
    }
}

impl Hash for UAnyType {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.as_uobject().hash(state);
    }
}

pub mod traits {
    use crate::core_types::FName;
    use std::ptr::NonNull;

    use itertools::Itertools;

    use super::{OuterObjectIterator, UClass, UField, UObject, UStruct, UStructFieldIterator};

    pub unsafe fn null_ptr_cast<'a, T, U>(ptr: &'a NonNull<T>) -> &'a NonNull<U> {
        std::mem::transmute(ptr)
    }

    pub trait AsUObject: Sized {
        fn as_uobject(&self) -> UObject;

        fn class(&self) -> Option<UClass> {
            self.as_uobject().class()
        }

        fn internal_index(&self) -> u32 {
            self.as_uobject().internal_index()
        }

        fn name(&self) -> FName {
            self.as_uobject().name()
        }

        fn get_name(&self) -> anyhow::Result<String> {
            self.as_uobject().get_name()
        }

        fn get_name_or_default(&self) -> String {
            self.as_uobject().get_name_or_default()
        }

        fn get_full_name(&self) -> anyhow::Result<String> {
            self.as_uobject().get_full_name()
        }

        fn get_full_name_or_default(&self) -> String {
            self.as_uobject().get_full_name_or_default()
        }

        fn outer(&self) -> Option<UObject> {
            self.as_uobject().outer()
        }

        fn outermost(&self) -> Option<UObject> {
            self.as_uobject().outermost()
        }

        fn is_package_object(&self) -> bool {
            self.as_uobject().is_package_object()
        }

        fn iter_outer_objects(&self) -> OuterObjectIterator {
            OuterObjectIterator::new(self.as_uobject())
        }

        fn is_a(&self, other: &UClass) -> bool {
            self.class()
                .map(|class| class.iter_super_classes().contains(other))
                .unwrap_or(false)
        }
    }

    pub trait AsUStruct {
        fn as_ustruct(&self) -> UStruct;

        #[inline]
        fn children(&self) -> Option<UField> {
            self.as_ustruct().children()
        }

        #[inline]
        fn super_struct(&self) -> Option<UStruct> {
            self.as_ustruct().super_struct()
        }

        #[inline]
        fn iter_fields(&self) -> Option<UStructFieldIterator> {
            self.children()
                .map(|field| UStructFieldIterator::new(field))
        }
    }

    impl<T> AsUObject for T
    where
        T: AsUStruct,
    {
        fn as_uobject(&self) -> UObject {
            UObject::new(self.as_ustruct().inner.cast())
        }
    }

    pub trait FromRaw<R> {
        fn from_non_null(inner: NonNull<R>) -> Self;

        fn from_raw(raw: *mut R) -> Option<Self>
        where
            Self: Sized,
        {
            NonNull::new(raw).map(|inner| Self::from_non_null(inner))
        }

        fn from_maybe_non_null(inner: Option<NonNull<R>>) -> Option<Self>
        where
            Self: Sized,
        {
            inner.map(|inner| Self::from_non_null(inner))
        }
    }
}