use std::str::FromStr;

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

use crate::TargetEnv;

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

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum AbiPrimitive {
    Unit,
    Bool,
    U8,
    I8,
    U16,
    I16,
    U31,
    U32,
    I32,
    U64,
    I64,
    U128,
    USize,
}

impl Type for AbiPrimitive {
    fn width(&self, env: &TargetEnv) -> TypeWidth {
        match self {
            Self::Unit => TypeWidth::Zero,
            Self::U8 | Self::I8 => TypeWidth::U8,
            Self::U16 | Self::I16 => TypeWidth::U16,
            Self::U31 => TypeWidth::U31,
            Self::U32 | Self::I32 => TypeWidth::U32,
            Self::U64 | Self::I64 => TypeWidth::U64,
            Self::U128 => TypeWidth::U128,
            Self::USize => env.thin_pointer_width,
            // ???
            Self::Bool => TypeWidth::U32,
        }
    }

    fn kind(&self) -> TypeKind {
        TypeKind::Other
    }

    fn as_rust_type(&self) -> TokenStream {
        match self {
            Self::Unit => quote!(()),
            Self::U8 => quote!(u8),
            Self::I8 => quote!(i8),
            Self::U16 => quote!(u16),
            Self::I16 => quote!(i16),
            Self::U32 => quote!(u32),
            Self::I32 => quote!(i32),
            Self::U64 => quote!(u64),
            Self::I64 => quote!(i64),
            Self::U128 => quote!(u128),
            Self::USize => quote!(usize),
            Self::Bool => quote!(bool),

            Self::U31 => quote!(u32),
        }
    }

    fn emit_to_syscall_arguments(&self, env: &TargetEnv, value: &Ident) -> TokenStream {
        match self {
            Self::USize => quote!(#value),
            Self::U128 => todo!("Emit to syscall for u128"),
            Self::U64 if env.thin_pointer_width == TypeWidth::U32 => {
                quote!(#value as usize, (#value >> 32) as usize)
            }
            _ => quote!(#value as usize),
        }
    }

    fn emit_from_syscall_arguments(
        &self,
        env: &TargetEnv,
        args: &Ident,
        index: usize,
    ) -> (TokenStream, usize) {
        match self {
            Self::Bool => (quote!(#args[#index] != 0), 1),
            Self::U128 => todo!("Emit from syscall for u128"),
            Self::U64 if env.thin_pointer_width == TypeWidth::U32 => (
                quote!((#args[#index] as u64) | ((#args[#index + 1] as u64) << 32)),
                2,
            ),
            _ => {
                let ty = self.as_rust_type();
                (quote!(#args[#index] as #ty), 1)
            }
        }
    }
}

impl FromStr for AbiPrimitive {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "i8" => Ok(Self::I8),
            "u8" => Ok(Self::U8),
            "i16" => Ok(Self::I16),
            "u16" => Ok(Self::U16),
            "i32" => Ok(Self::I32),
            "u31" => Ok(Self::U31),
            "u32" => Ok(Self::U32),
            "i64" => Ok(Self::I64),
            "u64" => Ok(Self::U64),
            "u128" => Ok(Self::U128),
            "bool" => Ok(Self::Bool),
            "usize" => Ok(Self::USize),
            _ => Err(()),
        }
    }
}
