/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use proc_macro2::Span;
use syn::parse::{Parse, ParseStream, Result};
use syn::{braced, punctuated::Punctuated, token, Attribute, Ident, Path, Token, Type};

#[allow(non_camel_case_types)]
mod kw {
    syn::custom_keyword!(accessor_type);
    syn::custom_keyword!(gen_accessors);
    syn::custom_keyword!(gen_types);
}

pub struct MacroInput {
    pub type_def: RootTypeDef,
    pub gen_accessors: Ident,
    pub accessor_type: Path,
}

enum MacroArg {
    GenAccessors(ArgInner<kw::gen_accessors, Ident>),
    AccessorType(ArgInner<kw::accessor_type, Path>),
    Types(ArgInner<kw::gen_types, RootTypeDef>),
}

struct ArgInner<K, V> {
    _field_kw: K,
    _equals: Token![=],
    value: V,
}

pub struct Field {
    pub attributes: Vec<Attribute>,
    pub name: Ident,
    _colon: Token![:],
    pub field_type: FieldType,
}

pub enum FieldType {
    Existing(Type),
    NewTypeDef(NewTypeDef),
}

pub struct NewTypeDef {
    _braces: token::Brace,
    pub fields: Punctuated<Field, Token![, ]>,
}

pub struct RootTypeDef {
    pub type_name: Ident,
    pub type_def: NewTypeDef,
}

impl Parse for MacroInput {
    fn parse(input: ParseStream) -> Result<Self> {
        let fields: Punctuated<MacroArg, Token![, ]> = input.parse_terminated(MacroArg::parse)?;
        let mut gen_accessors = None;
        let mut type_def = None;
        let mut accessor_type = None;
        for arg in fields.into_iter() {
            match arg {
                MacroArg::GenAccessors(ArgInner { value, .. }) => gen_accessors = Some(value),
                MacroArg::AccessorType(ArgInner { value, .. }) => accessor_type = Some(value),
                MacroArg::Types(ArgInner { value, .. }) => type_def = Some(value),
            }
        }

        fn missing_attr(att_name: &str) -> syn::Error {
            syn::Error::new(
                Span::call_site(),
                format!("Expected `{}` attribute", att_name),
            )
        }

        Ok(MacroInput {
            type_def: type_def.ok_or_else(|| missing_attr("gen_types"))?,
            gen_accessors: gen_accessors.ok_or_else(|| missing_attr("gen_accessors"))?,
            accessor_type: accessor_type.ok_or_else(|| missing_attr("accessor_type"))?,
        })
    }
}

impl Parse for MacroArg {
    fn parse(input: ParseStream) -> Result<Self> {
        let lookahead = input.lookahead1();
        if lookahead.peek(kw::gen_types) {
            Ok(MacroArg::Types(input.parse()?))
        } else if lookahead.peek(kw::gen_accessors) {
            Ok(MacroArg::GenAccessors(input.parse()?))
        } else if lookahead.peek(kw::accessor_type) {
            Ok(MacroArg::AccessorType(input.parse()?))
        } else {
            Err(lookahead.error())
        }
    }
}

impl<K: Parse, V: Parse> Parse for ArgInner<K, V> {
    fn parse(input: ParseStream) -> Result<Self> {
        Ok(ArgInner {
            _field_kw: input.parse()?,
            _equals: input.parse()?,
            value: input.parse()?,
        })
    }
}

impl Parse for Field {
    fn parse(input: ParseStream) -> Result<Self> {
        Ok(Field {
            attributes: input.call(Attribute::parse_outer)?,
            name: input.parse()?,
            _colon: input.parse()?,
            field_type: input.parse()?,
        })
    }
}

impl Parse for RootTypeDef {
    fn parse(input: ParseStream) -> Result<Self> {
        Ok(RootTypeDef {
            type_name: input.parse()?,
            type_def: input.parse()?,
        })
    }
}

impl Parse for NewTypeDef {
    fn parse(input: ParseStream) -> Result<Self> {
        let content;
        #[allow(clippy::eval_order_dependence)]
        Ok(NewTypeDef {
            _braces: braced!(content in input),
            fields: content.parse_terminated(Field::parse)?,
        })
    }
}

impl Parse for FieldType {
    fn parse(input: ParseStream) -> Result<Self> {
        if input.peek(token::Brace) {
            Ok(FieldType::NewTypeDef(input.parse()?))
        } else {
            Ok(FieldType::Existing(input.parse()?))
        }
    }
}