use quote::ToTokens;
use syn::{parse::ParseStream, Token};

use super::{
    bitfield_type::BitfieldType,
    enum_type::EnumType,
    extern_block::{ExternType, ExternTypeBlock},
    newtype::NewType,
    struct_type::StructType,
    syscall::SyscallDefinition,
};

#[derive(Clone)]
pub enum DocumentTypeDefinition {
    NewType(NewType),
    Bitfield(BitfieldType),
    Enum(EnumType),
    ExternType(ExternType),
    Struct(StructType),
}

pub struct Document {
    pub types: Vec<DocumentTypeDefinition>,
    pub syscalls: Vec<SyscallDefinition>,
}

pub trait DocumentItemAttributes {
    fn extend_attributes<I: IntoIterator<Item = syn::Attribute>>(&mut self, attrs: I);
}

enum DocumentItem {
    ExternTypeBlock(ExternTypeBlock),

    // Type definitions
    NewType(NewType),
    Bitfield(BitfieldType),
    Enum(EnumType),
    Struct(StructType),

    // Syscall definitions
    SyscallDefinition(SyscallDefinition),
}

impl syn::parse::Parse for DocumentItem {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let lookahead = input.lookahead1();

        if lookahead.peek(Token![extern]) {
            input.parse::<Token![extern]>()?;
            return input.parse().map(Self::ExternTypeBlock);
        }

        let item_attributes = syn::Attribute::parse_outer(input)?;

        let lookahead = input.lookahead1();

        let mut item = if lookahead.peek(syn::Ident) {
            // TODO parse attributes
            let keyword: syn::Ident = input.parse()?;

            match keyword.to_string().as_str() {
                "newtype" => NewType::parse(input).map(Self::NewType),
                "bitfield" => BitfieldType::parse(input).map(Self::Bitfield),
                "syscall" => SyscallDefinition::parse(input).map(Self::SyscallDefinition),
                _ => todo!(),
            }
        } else if lookahead.peek(Token![enum]) {
            input.parse::<Token![enum]>()?;
            EnumType::parse(input).map(Self::Enum)
        } else if lookahead.peek(Token![struct]) {
            StructType::parse(input).map(Self::Struct)
        } else {
            todo!()
        }?;

        item.extend_attributes(item_attributes);

        Ok(item)
    }
}

impl DocumentItemAttributes for DocumentItem {
    fn extend_attributes<I: IntoIterator<Item = syn::Attribute>>(&mut self, attrs: I) {
        match self {
            Self::NewType(inner) => inner.extend_attributes(attrs),
            Self::Enum(inner) => inner.extend_attributes(attrs),
            Self::Bitfield(inner) => inner.extend_attributes(attrs),
            Self::SyscallDefinition(inner) => inner.extend_attributes(attrs),
            Self::ExternTypeBlock(_) => unreachable!(),
            Self::Struct(inner) => inner.extend_attributes(attrs),
        }
    }
}

impl syn::parse::Parse for Document {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let mut types = vec![];
        let mut syscalls = vec![];

        loop {
            if input.is_empty() {
                break;
            }

            let toplevel = DocumentItem::parse(input)?;

            match toplevel {
                DocumentItem::Enum(inner) => types.push(DocumentTypeDefinition::Enum(inner)),
                DocumentItem::NewType(inner) => types.push(DocumentTypeDefinition::NewType(inner)),
                DocumentItem::Bitfield(inner) => {
                    types.push(DocumentTypeDefinition::Bitfield(inner))
                }
                DocumentItem::ExternTypeBlock(inner) => {
                    types.extend(
                        inner
                            .types
                            .into_iter()
                            .map(DocumentTypeDefinition::ExternType),
                    );
                }
                DocumentItem::Struct(inner) => types.push(DocumentTypeDefinition::Struct(inner)),
                DocumentItem::SyscallDefinition(inner) => {
                    syscalls.push(inner);
                }
            }
        }

        Ok(Self { types, syscalls })
    }
}

impl ToTokens for DocumentItem {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        match self {
            Self::Enum(inner) => inner.to_tokens(tokens),
            Self::Bitfield(inner) => inner.to_tokens(tokens),
            Self::NewType(inner) => inner.to_tokens(tokens),
            Self::SyscallDefinition(inner) => inner.to_tokens(tokens),
            Self::ExternTypeBlock(inner) => inner.to_tokens(tokens),
            Self::Struct(inner) => inner.to_tokens(tokens),
        }
    }
}
