mod args;
mod regex_code;
use {
    crate::{args::*, regex_code::*},
    proc_macro::TokenStream,
    quote::quote,
    syn::{parse_macro_input, Expr},
};
fn process<T, F>(input: TokenStream, as_bytes: bool, f: F) -> TokenStream
where
    T: Into<TokenStream>,
    F: Fn(RegexCode) -> T,
{
    match RegexCode::from_token_stream(input, as_bytes) {
        Ok(r) => f(r).into(),
        Err(e) => e.to_compile_error().into(),
    }
}
fn process_with_value<T, F>(input: TokenStream, as_bytes: bool, f: F) -> TokenStream
where
    T: Into<TokenStream>,
    F: Fn(RegexCode, Expr) -> T,
{
    let parsed = parse_macro_input!(input as RexValArgs);
    match RegexCode::from_lit_str(parsed.regex_str, as_bytes) {
        Ok(r) => f(r, parsed.value).into(),
        Err(e) => e.to_compile_error().into(),
    }
}
#[proc_macro]
pub fn regex(input: TokenStream) -> TokenStream {
    process(input, false, |regex_code| regex_code.lazy_static())
}
#[proc_macro]
pub fn bytes_regex(input: TokenStream) -> TokenStream {
    process(input, true, |regex_code| regex_code.lazy_static())
}
#[proc_macro]
pub fn lazy_regex(input: TokenStream) -> TokenStream {
    process(input, false, |regex_code| regex_code.build)
}
#[proc_macro]
pub fn bytes_lazy_regex(input: TokenStream) -> TokenStream {
    process(input, true, |regex_code| regex_code.build)
}
#[proc_macro]
pub fn regex_is_match(input: TokenStream) -> TokenStream {
    process_with_value(input, false, |regex_code, value| {
        let statick = regex_code.statick();
        quote! {{
            #statick;
            RE.is_match(#value)
        }}
    })
}
#[proc_macro]
pub fn bytes_regex_is_match(input: TokenStream) -> TokenStream {
    process_with_value(input, true, |regex_code, value| {
        let statick = regex_code.statick();
        quote! {{
            #statick;
            RE.is_match(#value)
        }}
    })
}
#[proc_macro]
pub fn regex_find(input: TokenStream) -> TokenStream {
    process_with_value(input, false, |regex_code, value| {
        let statick = regex_code.statick();
        let as_method = match regex_code.regex {
            RegexInstance::Regex(..) => quote!(as_str),
            RegexInstance::Bytes(..) => quote!(as_bytes),
        };
        quote! {{
            #statick;
            RE.find(#value).map(|mat| mat. #as_method ())
        }}
    })
}
#[proc_macro]
pub fn bytes_regex_find(input: TokenStream) -> TokenStream {
    process_with_value(input, true, |regex_code, value| {
        let statick = regex_code.statick();
        let as_method = match regex_code.regex {
            RegexInstance::Regex(..) => quote!(as_str),
            RegexInstance::Bytes(..) => quote!(as_bytes),
        };
        quote! {{
            #statick;
            RE.find(#value).map(|mat| mat. #as_method ())
        }}
    })
}
#[proc_macro]
pub fn regex_captures(input: TokenStream) -> TokenStream {
    process_with_value(input, false, |regex_code, value| {
        let statick = regex_code.statick();
        let n = regex_code.captures_len();
        let groups = (0..n).map(|i| {
            quote! {
                caps.get(#i).map_or("", |c| c.as_str())
            }
        });
        quote! {{
            #statick;
            RE.captures(#value)
                .map(|caps| (
                    #(#groups),*
                ))
        }}
    })
}
#[proc_macro]
pub fn bytes_regex_captures(input: TokenStream) -> TokenStream {
    process_with_value(input, true, |regex_code, value| {
        let statick = regex_code.statick();
        let n = regex_code.captures_len();
        let groups = (0..n).map(|i| {
            quote! {
                caps.get(#i).map_or(&b""[..], |c| c.as_bytes())
            }
        });
        quote! {{
            #statick;
            RE.captures(#value)
                .map(|caps| (
                    #(#groups),*
                ))
        }}
    })
}
#[proc_macro]
pub fn regex_captures_iter(input: TokenStream) -> TokenStream {
    process_with_value(input, false, |regex_code, value| {
        let statick = regex_code.statick();
        quote! {{
            #statick;
            RE.captures_iter(#value)
        }}
    })
}
#[proc_macro]
pub fn bytes_regex_captures_iter(input: TokenStream) -> TokenStream {
    process_with_value(input, true, |regex_code, value| {
        let statick = regex_code.statick();
        quote! {{
            #statick;
            RE.captures_iter(#value)
        }}
    })
}
fn replacen(input: TokenStream, limit: usize) -> TokenStream {
    let parsed = parse_macro_input!(input as ReplaceArgs);
    let ReplaceArgs { regex_str, value, replacer } = parsed;
    let regex_code = match RegexCode::from_lit_str(regex_str, false) {
        Ok(r) => r,
        Err(e) => {
            return e.to_compile_error().into();
        }
    };
    let statick = regex_code.statick();
    let stream = match replacer {
        MaybeFun::Fun(fun) => {
            let n = regex_code.captures_len();
            let groups = (0..n).map(|i| {
                quote! {
                    caps.get(#i).map_or("", |c| c.as_str())
                }
            });
            quote! {{
                #statick;
                RE.replacen(
                    #value,
                    #limit,
                    |caps: &lazy_regex::Captures<'_>| {
                        let mut fun = #fun;
                        fun(
                            #(#groups),*
                        )
                    })
            }}
        }
        MaybeFun::Expr(expr) => {
            quote! {{
                #statick;
                RE.replacen(#value, #limit, #expr)
            }}
        }
    };
    stream.into()
}
fn bytes_replacen(input: TokenStream, limit: usize) -> TokenStream {
    let parsed = parse_macro_input!(input as ReplaceArgs);
    let ReplaceArgs { regex_str, value, replacer } = parsed;
    let regex_code = match RegexCode::from_lit_str(regex_str, true) {
        Ok(r) => r,
        Err(e) => {
            return e.to_compile_error().into();
        }
    };
    let statick = regex_code.statick();
    let stream = match replacer {
        MaybeFun::Fun(fun) => {
            let n = regex_code.captures_len();
            let groups = (0..n).map(|i| {
                quote! {
                    caps.get(#i).map_or(&b""[..], |c| c.as_bytes())
                }
            });
            quote! {{
                #statick;
                RE.replacen(
                    #value,
                    #limit,
                    |caps: &lazy_regex::regex::bytes::Captures<'_>| {
                        let mut fun = #fun;
                        fun(
                            #(#groups),*
                        )
                    })
            }}
        }
        MaybeFun::Expr(expr) => {
            quote! {{
                #statick;
                RE.replacen(#value, #limit, #expr)
            }}
        }
    };
    stream.into()
}
#[proc_macro]
pub fn regex_replace(input: TokenStream) -> TokenStream {
    replacen(input, 1)
}
#[proc_macro]
pub fn bytes_regex_replace(input: TokenStream) -> TokenStream {
    bytes_replacen(input, 1)
}
#[proc_macro]
pub fn regex_replace_all(input: TokenStream) -> TokenStream {
    replacen(input, 0)
}
#[proc_macro]
pub fn bytes_regex_replace_all(input: TokenStream) -> TokenStream {
    bytes_replacen(input, 0)
}
#[proc_macro]
pub fn regex_if(input: TokenStream) -> TokenStream {
    let RexIfArgs {
        regex_str,
        value,
        then,
    } = parse_macro_input!(input as RexIfArgs);
    let regex_code = match RegexCode::from_lit_str(regex_str, false) {
        Ok(r) => r,
        Err(e) => {
            return e.to_compile_error().into();
        }
    };
    let statick = regex_code.statick();
    let assigns = regex_code.named_groups().into_iter().map(|(idx, name)| {
        let var_name = syn::Ident::new(name, proc_macro2::Span::call_site());
        quote! {
            let #var_name: &str = caps.get(#idx).map_or("", |c| c.as_str());
        }
    });
    quote! {{
        #statick;
        match RE.captures(#value) {
            Some(caps) => {
                #(#assigns);*
                Some(#then)
            }
            None => None,
        }
    }}.into()
}
#[proc_macro]
pub fn bytes_regex_if(input: TokenStream) -> TokenStream {
    let RexIfArgs {
        regex_str,
        value,
        then,
    } = parse_macro_input!(input as RexIfArgs);
    let regex_code = match RegexCode::from_lit_str(regex_str, true) {
        Ok(r) => r,
        Err(e) => {
            return e.to_compile_error().into();
        }
    };
    let statick = regex_code.statick();
    let assigns = regex_code.named_groups().into_iter().map(|(idx, name)| {
        let var_name = syn::Ident::new(name, proc_macro2::Span::call_site());
        quote! {
            let #var_name: &[u8] = caps.get(#idx).map_or(&b""[..], |c| c.as_bytes());
        }
    });
    quote! {{
        #statick;
        match RE.captures(#value) {
            Some(caps) => {
                #(#assigns);*
                Some(#then)
            }
            None => None,
        }
    }}.into()
}
#[proc_macro]
pub fn regex_switch(input: TokenStream) -> TokenStream {
    let RexSwitchArgs {
        value,
        arms,
    } = parse_macro_input!(input as RexSwitchArgs);
    let mut q_arms = Vec::new();
    for RexSwitchArmArgs { regex_str, then } in arms {
        let regex_code = match RegexCode::from_lit_str(regex_str, false) {
            Ok(r) => r,
            Err(e) => {
                return e.to_compile_error().into();
            }
        };
        let statick = regex_code.statick();
        let assigns = regex_code.named_groups().into_iter().map(|(idx, name)| {
            let var_name = syn::Ident::new(name, proc_macro2::Span::call_site());
            quote! {
                let #var_name: &str = caps.get(#idx).map_or("", |c| c.as_str());
            }
        });
        q_arms.push(
            quote! {{
                #statick;
                if let Some(caps) = RE.captures(#value) {
                    #(#assigns);*
                    let output = Some(#then);
                    break 'switch output;
                }
            }}
        );
    }
    quote! {{
        'switch: {
            #(#q_arms)*
            None
        }
    }}.into()
}
#[proc_macro]
pub fn bytes_regex_switch(input: TokenStream) -> TokenStream {
    let RexSwitchArgs {
        value,
        arms,
    } = parse_macro_input!(input as RexSwitchArgs);
    let mut q_arms = Vec::new();
    for RexSwitchArmArgs { regex_str, then } in arms {
        let regex_code = match RegexCode::from_lit_str(regex_str, true) {
            Ok(r) => r,
            Err(e) => {
                return e.to_compile_error().into();
            }
        };
        let statick = regex_code.statick();
        let assigns = regex_code.named_groups().into_iter().map(|(idx, name)| {
            let var_name = syn::Ident::new(name, proc_macro2::Span::call_site());
            quote! {
                let #var_name: &[u8] = caps.get(#idx).map_or(&b""[..], |c| c.as_bytes());
            }
        });
        q_arms.push(
            quote! {{
                #statick;
                if let Some(caps) = RE.captures(#value) {
                    #(#assigns);*
                    let output = Some(#then);
                    break 'switch output;
                }
            }}
        );
    }
    quote! {{
        'switch: {
            #(#q_arms)*
            None
        }
    }}.into()
}