use crate::error::{Error, SpanRange};
use proc_macro2::TokenStream;
use quote::ToTokens;
use std::{fmt::Debug, hash::Hash, ops::Deref};

/// A reference which uses its address for comparisons.
pub struct Ptr<'a, T: ?Sized>(pub &'a T);

impl<T: ?Sized> Clone for Ptr<'_, T> {
    fn clone(&self) -> Self {
        *self
    }
}
impl<T: ?Sized> Copy for Ptr<'_, T> {}
impl<T: ?Sized> Deref for Ptr<'_, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        self.0
    }
}

impl<T: ?Sized> AsRef<T> for Ptr<'_, T> {
    fn as_ref(&self) -> &T {
        self.0
    }
}

impl<T: ?Sized> PartialEq for Ptr<'_, T> {
    fn eq(&self, other: &Self) -> bool {
        std::ptr::eq(self.0, other.0)
    }
}

impl<T: ?Sized> Eq for Ptr<'_, T> {}

impl<T: ?Sized> Hash for Ptr<'_, T> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        (self.0 as *const T).hash(state);
    }
}

impl<T: ?Sized> Debug for Ptr<'_, T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_fmt(format_args!("Ptr({:p})", self.0))
    }
}

pub const ARCIS_CIRCUIT: &str = "arcis_circuit";

pub fn get_base_circuit(attrs: &[syn::Attribute]) -> Option<&syn::Expr> {
    attrs.iter().find_map(|attr| {
        if let syn::Meta::NameValue(nv) = &attr.meta {
            if let Some(ident) = nv.path.get_ident() {
                if ident == ARCIS_CIRCUIT {
                    return Some(&nv.value);
                }
            }
        }
        None
    })
}

pub fn error_macro(mut input: TokenStream, err: &'static str) -> TokenStream {
    input.extend(Error::new(SpanRange::call_site(), err).to_token_stream());
    input
}

pub fn syn_ty_to_syn_path(ty: &syn::Type) -> Option<&syn::Path> {
    match ty {
        syn::Type::Group(ty) => syn_ty_to_syn_path(&ty.elem),
        syn::Type::Paren(ty) => syn_ty_to_syn_path(&ty.elem),
        syn::Type::Path(ty) => Some(&ty.path),
        _ => None,
    }
}

pub fn get_instruction_attr(i: &syn::ItemFn) -> Option<&syn::Attribute> {
    i.attrs
        .iter()
        .find(|attr| attr.path().is_ident("instruction"))
}
