use std::{fmt, rc::Rc};

use proc_macro2::TokenStream;
use quote::quote;
use syn::{punctuated::Punctuated, Ident, Token};

use crate::TargetEnv;

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

#[derive(Clone, Copy)]
pub enum ExternKind {
    None,
    Thin,
    Fat,
}

#[derive(Clone)]
pub enum ComplexType {
    Simple(SimpleType),
    MaybeUninit(Rc<ComplexType>),
    Result(Rc<ComplexType>),
    Option(Rc<ComplexType>),
    Tuple(Vec<Rc<ComplexType>>),
    Struct {
        name: syn::Ident,
        kind: ExternKind,
        arguments: syn::PathArguments,
    },
    Extern {
        kind: ExternKind,
        alias: syn::Type,
        arguments: syn::PathArguments,
    },
}

impl ComplexType {
    pub fn primitive(val: AbiPrimitive) -> Self {
        Self::Simple(SimpleType::Primitive(val))
    }

    pub fn transparent(name: Ident, val: AbiPrimitive) -> Self {
        Self::Simple(SimpleType::Transparent { name, inner: val })
    }

    pub fn as_primitive(&self) -> Option<AbiPrimitive> {
        if let Self::Simple(SimpleType::Primitive(ty)) = self {
            Some(*ty)
        } else if let Self::Simple(SimpleType::Transparent { inner, .. }) = self {
            Some(*inner)
        } else {
            None
        }
    }
}

impl Type for ComplexType {
    fn width(&self, env: &TargetEnv) -> TypeWidth {
        match self {
            Self::Simple(ty) => ty.width(env),
            Self::Option(ty) | Self::Result(ty) => match (ty.kind(), ty.width(env)) {
                // Some(val) -> val
                // None -> usize::MAX
                (_, TypeWidth::U31) => TypeWidth::U32,
                (TypeKind::Other, _) => ty.width(env).double(),
                // Some(&[1, 2, 3]) -> (ptr, len), where ptr and len are non-zero
                // None -> (null, 0)
                (TypeKind::FatPointer, _) => env.fat_pointer_width,
                // Some(&x) -> ptr, where ptr is non-zero
                // None -> null
                (TypeKind::ThinPointer, _) => env.thin_pointer_width,
            },
            Self::Tuple(_) => TypeWidth::Unknown,
            Self::Extern { .. } => TypeWidth::Unknown,
            Self::Struct { .. } => TypeWidth::Unknown,
            // MaybeUninit has the same layout as its underlying data
            Self::MaybeUninit(ty) => ty.width(env),
        }
    }

    fn kind(&self) -> TypeKind {
        match self {
            Self::Simple(ty) => ty.kind(),
            Self::MaybeUninit(ty) => ty.kind(),
            _ => TypeKind::Other,
        }
    }

    fn as_rust_type(&self) -> TokenStream {
        match self {
            Self::Simple(ty) => ty.as_rust_type(),
            Self::Option(ty) => {
                let ty = ty.as_rust_type();
                quote!(Option<#ty>)
            }
            Self::Tuple(elems) => {
                let elems = elems
                    .iter()
                    .map(|e| e.as_rust_type())
                    .collect::<Punctuated<_, Token![,]>>();
                quote!((#elems))
            }
            Self::Result(ty) => {
                let ty = ty.as_rust_type();
                // TODO take from TargetEnv
                quote!(Result<#ty, Error>)
            }
            Self::Extern {
                alias, arguments, ..
            } => {
                quote!(#alias #arguments)
            }
            Self::Struct {
                name, arguments, ..
            } => quote!(#name #arguments),
            Self::MaybeUninit(ty) => {
                let ty = ty.as_rust_type();
                quote!(core::mem::MaybeUninit<#ty>)
            }
        }
    }

    fn emit_to_syscall_arguments(&self, env: &TargetEnv, value: &Ident) -> TokenStream {
        // TODO: Option/Result<ExternType>
        match self {
            Self::Simple(ty) => ty.emit_to_syscall_arguments(env, value),
            Self::Option(ty)
                if ty.width(env) < env.thin_pointer_width || ty.kind() == TypeKind::ThinPointer =>
            {
                quote!(#value.into_syscall_register())
            }
            Self::Option(_) => {
                quote!(#value.as_syscall_meta(), #value.as_syscall_data())
            }
            Self::Extern {
                kind: ExternKind::Thin,
                ..
            } => {
                quote!(#value.into_syscall_register())
            }
            Self::Extern {
                kind: ExternKind::Fat,
                ..
            } => {
                quote!(#value.as_syscall_meta(), #value.as_syscall_data())
            }
            _ => todo!("???"),
        }
    }

    fn emit_from_syscall_arguments(
        &self,
        env: &TargetEnv,
        args: &Ident,
        index: usize,
    ) -> (TokenStream, usize) {
        match self {
            Self::Simple(ty) => ty.emit_from_syscall_arguments(env, args, index),
            Self::Option(ty)
                if ty.width(env) < env.thin_pointer_width || ty.kind() == TypeKind::ThinPointer =>
            {
                let ty = ty.as_rust_type();
                (
                    quote!(<Option<#ty>>::from_syscall_register(#args[#index])),
                    1,
                )
            }
            Self::Option(ty) => {
                let ty = ty.as_rust_type();
                (
                    quote!(<Option<#ty>>::from_syscall_registers(#args[#index], #args[#index + 1])),
                    2,
                )
            }
            Self::Result(_) => todo!("Result"),
            Self::Extern {
                kind: ExternKind::Thin,
                alias,
                ..
            } => (quote!(<#alias>::from_syscall_register(#args[#index])), 1),
            Self::MaybeUninit(_) => todo!("MaybeUninit"),
            Self::Tuple(_) => unimplemented!(),
            _ => todo!("{:?}", self),
        }
    }
}

impl fmt::Debug for ComplexType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Simple(ty) => f.debug_tuple("Simple").field(&ty).finish(),
            Self::Result(ty) => f.debug_tuple("Result").field(&ty).finish(),
            Self::Option(ty) => f.debug_tuple("Option").field(&ty).finish(),
            Self::Tuple(elems) => {
                let mut r = f.debug_tuple("Tuple");
                for elem in elems {
                    r.field(elem);
                }
                r.finish()
            }
            Self::Struct { .. } => todo!(),
            Self::Extern { alias, .. } => f.debug_tuple("Extern").field(&quote!(#alias)).finish(),
            Self::MaybeUninit(ty) => f.debug_tuple("MaybeUninit").field(&ty).finish(),
        }
    }
}
