#![feature(try_blocks)]

extern crate proc_macro;
#[macro_use] extern crate quote;

//use proc_macro2::{TokenStream};
use proc_macro::TokenStream;
use syn::{fold::Fold, parse, parse_macro_input, ExprMatch, DeriveInput, Data, DataEnum, ExprTuple, ExprLit, Lit};

use syn::{Pat, PatPath, Path};

mod entish;
use entish::EntishBuilder;

mod pattern;
use pattern::PatternBuilder;

mod utils;

/// Main macro entry point of the crate. Depending on what it wraps,
/// behaves differently.
/// - a `match` expression: is the same as [expr_match!](macro.expr_match.html)
/// - an item (such as `enum`, `struct`, etc.): is the same as
///   [generate!](macro.generate.html)
#[proc_macro]
pub fn entish(input: TokenStream) -> TokenStream {
    let out: syn::parse::Result<_> = parse::<ExprMatch>(input.clone())
        .map(|_| expr_match(input.clone()))
        .or_else(|_| {
            parse::<DeriveInput>(input.clone())?;
            Ok(generate(input))
        });
    out.expect("unsupported use of `entish`")
}

/// Runs codegeneration on the tree declared inside.
/// # Options
/// ???
#[proc_macro]
pub fn generate(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let mut builder = EntishBuilder::new(&input);

    let data = input.data;

    match data {
        Data::Enum(DataEnum { variants, .. }) => {
            for variant in variants.iter() {
                builder.add_node(variant);
            }
        },
        _ => panic!("entish can only be generated from an enum")
    };

    builder.generate().into()
}

use syn::{Expr, token::Comma, punctuated::Punctuated};

/// `unravel(tree, n)` takes a `tree: &Tree` (where `Tree` is generated by Entish)
/// and recursively applies `.as_ref()` to self *and then* its children a total of n times.
#[proc_macro]
pub fn unravel(input: TokenStream) -> TokenStream {
    let input: proc_macro2::TokenStream = input.into();
    let input = quote! { (#input) };
    let mut input: ExprTuple = syn::parse2(input).unwrap();

    let n: u32 = match input.elems.pop().expect("an n-fold").into_value() {
        Expr::Lit(ExprLit { lit: Lit::Int(lit_int), .. }) => {
            lit_int.base10_digits().parse().unwrap()
        },
        _ => panic!("n-fold param should be int literal")
    };
    let tree = input.elems.pop().expect("an expr").into_value();

    let out = match n {
        0 => quote! { #tree },
        1 => quote! { #tree.as_ref() },
        n => {
            let mut out = quote! { c.as_ref() };
            for _ in 1..(n-1) {
                out = quote! { c.as_ref().map(|c| #out) };
            }
            quote! { #tree.as_ref().map(|c| #out) }
        }
    };

    out.into()
}

/// Helper for matching on Entish generated trees.
#[proc_macro]
pub fn expr_match(input: TokenStream) -> TokenStream
{
    unimplemented!();

    let mut input = parse_macro_input!(input as ExprMatch);

    let mut path_pats = input.arms
        .iter()
        .filter_map(|arm| match &arm.pat {
            Pat::Path(PatPath { path, .. }) => Some(path),
            _ => None
        });

    if let Some(path) = path_pats.next() {
        let mut node_path = path.clone();
        node_path
            .segments
            .pop()
            .expect("top-level path pattern should match on variant of node");

        let mut builder = PatternBuilder::new(node_path);
        input = builder.fold_expr_match(input);
    }

    (quote! { #input }).into()
}
