use std::rc::Rc;

use proc_macro2::TokenStream;
use quote::quote;
use syn::Ident;

use crate::TargetEnv;

use super::{AbiPrimitive, ComplexType, Type, TypeKind, TypeWidth};

#[derive(Debug, Clone)]
pub enum SimpleType {
    Primitive(AbiPrimitive),
    Str,
    NonZeroUsize,
    Never,
    Transparent {
        name: Ident,
        inner: AbiPrimitive,
    },
    Reference {
        mutable: bool,
        pointee: Rc<ComplexType>,
    },
    Slice {
        mutable: bool,
        element: Rc<ComplexType>,
    },
    Array {
        mutable: bool,
        element: Rc<ComplexType>,
        length: usize,
    },
}

impl Type for SimpleType {
    fn width(&self, env: &TargetEnv) -> TypeWidth {
        match self {
            Self::Str => env.fat_pointer_width,
            Self::NonZeroUsize => env.thin_pointer_width,
            Self::Reference { .. } => env.thin_pointer_width,
            Self::Array { .. } => env.thin_pointer_width,
            Self::Slice { .. } => env.fat_pointer_width,
            Self::Primitive(ty) => ty.width(env),
            Self::Transparent { inner, .. } => inner.width(env),
            Self::Never => TypeWidth::Zero,
        }
    }

    fn kind(&self) -> TypeKind {
        match self {
            Self::Str => TypeKind::FatPointer,
            Self::NonZeroUsize => TypeKind::ThinPointer,
            Self::Primitive(ty) => ty.kind(),
            Self::Transparent { inner, .. } => inner.kind(),
            Self::Reference { .. } => TypeKind::ThinPointer,
            Self::Array { .. } => TypeKind::ThinPointer,
            Self::Slice { .. } => TypeKind::FatPointer,
            Self::Never => TypeKind::Other,
        }
    }

    fn as_rust_type(&self) -> TokenStream {
        match self {
            Self::Str => quote!(&str),
            Self::NonZeroUsize => quote!(core::num::NonZeroUsize),
            Self::Reference { mutable, pointee } => pointee.as_rust_ref_type(*mutable),
            Self::Array {
                mutable,
                element,
                length,
            } => element.as_rust_array_type(*mutable, *length),
            Self::Slice { mutable, element } => element.as_rust_slice_type(*mutable),
            Self::Primitive(ty) => ty.as_rust_type(),
            Self::Transparent { name, .. } => quote!(#name),
            Self::Never => quote!(!),
        }
    }

    fn emit_to_syscall_arguments(&self, env: &TargetEnv, value: &Ident) -> TokenStream {
        match self {
            Self::NonZeroUsize => quote!(#value.into()),
            Self::Primitive(ty) => ty.emit_to_syscall_arguments(env, value),
            Self::Transparent { .. } => quote!(#value.into_syscall_register()),
            Self::Str => quote!(#value.as_ptr().addr(), #value.len()),
            Self::Never => todo!("Prevent Never type from being a syscall argument"),
            Self::Reference { mutable, pointee } => {
                let pointee = pointee.as_rust_type();
                match mutable {
                    true => quote!((#value as *mut #pointee).addr()),
                    false => quote!((#value as *const #pointee).addr()),
                }
            }
            Self::Array { mutable, .. } => match mutable {
                true => quote!(#value.as_mut_ptr().addr()),
                false => quote!(#value.as_ptr().addr()),
            },
            Self::Slice { mutable, .. } => match mutable {
                true => quote!(#value.as_mut_ptr().addr(), #value.len()),
                false => quote!(#value.as_ptr().addr(), #value.len()),
            },
        }
    }

    fn emit_from_syscall_arguments(
        &self,
        env: &TargetEnv,
        args: &Ident,
        index: usize,
    ) -> (TokenStream, usize) {
        match self {
            Self::Str => (quote!(arg::str_ref(#args[#index], #args[#index + 1])?), 2),
            Self::Never => todo!("!"),
            Self::Slice { mutable, element } => {
                let element = element.as_rust_type();
                match mutable {
                    true => (
                        quote!(arg::slice_mut::<#element>(#args[#index], #args[#index + 1])?),
                        2,
                    ),
                    false => (
                        quote!(arg::slice_ref::<#element>(#args[#index], #args[#index + 1])?),
                        2,
                    ),
                }
            }
            Self::Reference { mutable, pointee } => {
                let pointee = pointee.as_rust_type();
                match mutable {
                    true => (quote!(arg::ref_mut::<#pointee>(#args[#index])?), 1),
                    false => (quote!(arg::ref_const::<#pointee>(#args[#index])?), 1),
                }
            }
            Self::Array {
                mutable,
                element,
                length,
            } => {
                let element = element.as_rust_type();
                match mutable {
                    true => (
                        quote!(arg::ref_mut::<[#element; #length]>(#args[#index])?),
                        1,
                    ),
                    false => (
                        quote!(arg::ref_const::<[#element; #length]>(#args[#index])?),
                        1,
                    ),
                }
            }
            Self::NonZeroUsize => todo!("NonZeroUsize"),
            Self::Primitive(ty) => ty.emit_from_syscall_arguments(env, args, index),
            Self::Transparent { name, .. } => {
                (quote!(<#name>::from_syscall_register(#args[#index])), 1)
            }
        }
    }
}
