Documentation
#![recursion_limit = "128"]
#![no_std]

extern crate alloc;

mod deserialize;
mod serialize;

use alloc::string::{String, ToString};
use alloc::vec::Vec;
use proc_macro::TokenStream;
use syn::parse::ParseStream;
use syn::spanned::Spanned;
use syn::{DeriveInput, parse_macro_input};

const V21MAX: usize = 0x1FFFFF;
const V7MAX: usize = 0x7F;

mod kw {
    syn::custom_keyword!(varint);
    syn::custom_keyword!(filter);
    syn::custom_keyword!(header);
    syn::custom_keyword!(camel_case);
}

#[derive(Default)]
struct Attrs {
    varint: bool,
    header: Option<syn::Path>,
    camel_case: bool,
}

impl syn::parse::Parse for Attrs {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut attr = Self::default();
        while !input.is_empty() {
            let lookahead = input.lookahead1();
            if lookahead.peek(kw::varint) {
                let _: kw::varint = input.parse()?;
                attr.varint = true;
            } else if lookahead.peek(kw::header) {
                let _: kw::header = input.parse()?;
                let _: syn::Token![=] = input.parse()?;
                attr.header = Some(input.parse::<syn::Path>()?);
            } else if lookahead.peek(kw::camel_case) {
                let _: kw::camel_case = input.parse()?;
                attr.camel_case = true;
            } else {
                return Err(lookahead.error());
            }

            if !input.is_empty() {
                let _: syn::Token![,] = input.parse()?;
            }
        }

        Ok(attr)
    }
}

fn crate_name(input: &DeriveInput) -> Result<(Attrs, syn::Path), syn::Error> {
    let mut find = Attrs::default();
    let mut flag = false;
    for attr in input.attrs.iter() {
        if attr.path().is_ident("mser") {
            if flag {
                return Err(syn::Error::new_spanned(attr, "multiple `mser` attributes"));
            }
            flag = true;
            find = attr.parse_args()?;
        }
    }
    Ok((
        find,
        syn::Ident::new("mser", proc_macro2::Span::call_site()).into(),
    ))
}

#[proc_macro_derive(Serialize, attributes(mser))]
pub fn serialize(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as syn::DeriveInput);
    let (attr, cratename) = match crate_name(&input) {
        Ok(cratename) => cratename,
        Err(err) => {
            return err.to_compile_error().into();
        }
    };

    let x = match input.data {
        syn::Data::Struct(_) => serialize::serialize_struct(input, cratename),
        syn::Data::Enum(_) => serialize::serialize_enum(input, cratename, attr),
        syn::Data::Union(_) => Err(syn::Error::new_spanned(input, "unions are not supported")),
    };
    match x {
        Ok(x) => x.into(),
        Err(err) => err.to_compile_error().into(),
    }
}

#[proc_macro_derive(Deserialize, attributes(mser))]
pub fn deserialize(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as syn::DeriveInput);
    let (attr, cratename) = match crate_name(&input) {
        Ok(cratename) => cratename,
        Err(err) => {
            return err.to_compile_error().into();
        }
    };

    let x = match input.data {
        syn::Data::Struct(_) => deserialize::deserialize_struct(input, cratename),
        syn::Data::Enum(_) => deserialize::deserialize_enum(input, cratename, attr),
        syn::Data::Union(_) => Err(syn::Error::new_spanned(input, "unions are not supported")),
    };
    match x {
        Ok(x) => x.into(),
        Err(err) => err.to_compile_error().into(),
    }
}

struct FieldAttrs {
    pub filter: Option<syn::Path>,
    pub varint: bool,
}

impl syn::parse::Parse for FieldAttrs {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut filter = None;
        let mut varint = false;
        while !input.is_empty() {
            let lookahead = input.lookahead1();
            if lookahead.peek(kw::filter) {
                let _: kw::filter = input.parse()?;
                let _: syn::Token![=] = input.parse()?;
                filter = Some(input.parse::<syn::Path>()?);
            } else if lookahead.peek(kw::varint) {
                let _: kw::varint = input.parse()?;
                varint = true;
            } else {
                return Err(lookahead.error());
            }

            if !input.is_empty() {
                let _: syn::Token![,] = input.parse()?;
            }
        }
        Ok(FieldAttrs { filter, varint })
    }
}

#[allow(clippy::type_complexity)]
fn parse_fields(fields: &syn::Fields) -> syn::Result<Vec<(&syn::Field, FieldAttrs, syn::Member)>> {
    let mut vec = Vec::with_capacity(fields.len());
    for (idx, field) in fields.iter().enumerate() {
        vec.push(match &field.ident {
            Some(ident) => (
                field,
                parse_field_attrs(field)?,
                syn::Member::Named(ident.clone()),
            ),
            None => (
                field,
                parse_field_attrs(field)?,
                syn::Member::Unnamed(syn::Index {
                    index: idx as u32,
                    span: field.span(),
                }),
            ),
        });
    }
    Ok(vec)
}

fn parse_field_attrs(field: &syn::Field) -> syn::Result<FieldAttrs> {
    let mut find = FieldAttrs {
        filter: None,
        varint: false,
    };
    let mut flag = false;
    for attr in field.attrs.iter() {
        if attr.path().is_ident("mser") {
            if flag {
                return Err(syn::Error::new_spanned(attr, "multiple `mser` attributes"));
            }
            flag = true;
            find = attr.parse_args()?;
        }
    }
    Ok(find)
}

#[derive(Clone, Copy)]
enum Ty {
    I32,
    U32,
    U64,
    I64,
    U8Array,
    Other,
}

fn ty(ty: &syn::Type) -> Ty {
    match ty {
        syn::Type::Path(path) => match path.path.get_ident() {
            Some(x) => {
                if x == "i32" {
                    Ty::I32
                } else if x == "u32" {
                    Ty::U32
                } else if x == "u64" {
                    Ty::U64
                } else if x == "i64" {
                    Ty::I64
                } else {
                    Ty::Other
                }
            }
            None => Ty::Other,
        },
        syn::Type::Array(arr) => match &*arr.elem {
            syn::Type::Path(x) => {
                if x.path.is_ident("u8") {
                    Ty::U8Array
                } else {
                    Ty::Other
                }
            }
            _ => Ty::Other,
        },
        _ => Ty::Other,
    }
}

fn ident_case(a: &Attrs, s: &syn::Ident) -> String {
    let s = s.to_string();
    if a.camel_case {
        return s;
    }
    let mut result = String::with_capacity(s.len());
    let mut last_end = 0;
    for (start, part) in s.match_indices(|x: char| x.is_ascii_uppercase()) {
        result.push_str(unsafe { s.get_unchecked(last_end..start) });
        if last_end != 0 {
            result.push('_');
        }
        result.push(part.chars().next().unwrap().to_ascii_lowercase());
        last_end = start + part.len();
    }
    result.push_str(unsafe { s.get_unchecked(last_end..s.len()) });
    result
}