use crate::{kw, variable::VariableDefinition, SolIdent, Spanned};
use proc_macro2::Span;
use std::fmt;
use syn::{
    parse::{Parse, ParseStream},
    Attribute, Result, Token,
};
mod contract;
pub use contract::{ContractKind, Inheritance, ItemContract};
mod r#enum;
pub use r#enum::{ItemEnum, Variant};
mod error;
pub use error::ItemError;
mod event;
pub use event::{EventParameter, ItemEvent};
mod function;
pub use function::{FunctionBody, FunctionKind, ItemFunction, Returns};
mod import;
pub use import::{
    ImportAlias, ImportAliases, ImportDirective, ImportGlob, ImportPath, ImportPlain,
};
mod pragma;
pub use pragma::{PragmaDirective, PragmaTokens};
mod r#struct;
pub use r#struct::ItemStruct;
mod udt;
pub use udt::ItemUdt;
mod using;
pub use using::{UserDefinableOperator, UsingDirective, UsingList, UsingListItem, UsingType};
#[derive(Clone)]
pub enum Item {
    Contract(ItemContract),
    Enum(ItemEnum),
    Error(ItemError),
    Event(ItemEvent),
    Function(ItemFunction),
    Import(ImportDirective),
    Pragma(PragmaDirective),
    Struct(ItemStruct),
    Udt(ItemUdt),
    Using(UsingDirective),
    Variable(VariableDefinition),
}
impl fmt::Display for Item {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Contract(item) => item.fmt(f),
            Self::Enum(item) => item.fmt(f),
            Self::Error(item) => item.fmt(f),
            Self::Event(item) => item.fmt(f),
            Self::Function(item) => item.fmt(f),
            Self::Import(item) => item.fmt(f),
            Self::Pragma(item) => item.fmt(f),
            Self::Struct(item) => item.fmt(f),
            Self::Udt(item) => item.fmt(f),
            Self::Using(item) => item.fmt(f),
            Self::Variable(item) => item.fmt(f),
        }
    }
}
impl fmt::Debug for Item {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("Item::")?;
        match self {
            Self::Contract(item) => item.fmt(f),
            Self::Enum(item) => item.fmt(f),
            Self::Error(item) => item.fmt(f),
            Self::Event(item) => item.fmt(f),
            Self::Function(item) => item.fmt(f),
            Self::Import(item) => item.fmt(f),
            Self::Pragma(item) => item.fmt(f),
            Self::Struct(item) => item.fmt(f),
            Self::Udt(item) => item.fmt(f),
            Self::Using(item) => item.fmt(f),
            Self::Variable(item) => item.fmt(f),
        }
    }
}
impl Parse for Item {
    fn parse(input: ParseStream<'_>) -> Result<Self> {
        let mut attrs = input.call(Attribute::parse_outer)?;
        let lookahead = input.lookahead1();
        let mut item = if FunctionKind::peek(&lookahead) {
            input.parse().map(Self::Function)
        } else if lookahead.peek(Token![struct]) {
            input.parse().map(Self::Struct)
        } else if lookahead.peek(kw::event) {
            input.parse().map(Self::Event)
        } else if lookahead.peek(kw::error) {
            input.parse().map(Self::Error)
        } else if ContractKind::peek(&lookahead) {
            input.parse().map(Self::Contract)
        } else if lookahead.peek(Token![enum]) {
            input.parse().map(Self::Enum)
        } else if lookahead.peek(Token![type]) {
            input.parse().map(Self::Udt)
        } else if lookahead.peek(kw::pragma) {
            input.parse().map(Self::Pragma)
        } else if lookahead.peek(kw::import) {
            input.parse().map(Self::Import)
        } else if lookahead.peek(kw::using) {
            input.parse().map(Self::Using)
        } else if crate::Type::peek(&lookahead) {
            input.parse().map(Self::Variable)
        } else {
            Err(lookahead.error())
        }?;
        attrs.extend(item.replace_attrs(Vec::new()));
        item.replace_attrs(attrs);
        Ok(item)
    }
}
impl Spanned for Item {
    fn span(&self) -> Span {
        match self {
            Self::Contract(contract) => contract.span(),
            Self::Enum(enumm) => enumm.span(),
            Self::Error(error) => error.span(),
            Self::Event(event) => event.span(),
            Self::Function(function) => function.span(),
            Self::Import(import) => import.span(),
            Self::Pragma(pragma) => pragma.span(),
            Self::Struct(strukt) => strukt.span(),
            Self::Udt(udt) => udt.span(),
            Self::Using(using) => using.span(),
            Self::Variable(variable) => variable.span(),
        }
    }
    fn set_span(&mut self, span: Span) {
        match self {
            Self::Contract(contract) => contract.set_span(span),
            Self::Enum(enumm) => enumm.set_span(span),
            Self::Error(error) => error.set_span(span),
            Self::Event(event) => event.set_span(span),
            Self::Function(function) => function.set_span(span),
            Self::Import(import) => import.set_span(span),
            Self::Pragma(pragma) => pragma.set_span(span),
            Self::Struct(strukt) => strukt.set_span(span),
            Self::Udt(udt) => udt.set_span(span),
            Self::Using(using) => using.set_span(span),
            Self::Variable(variable) => variable.set_span(span),
        }
    }
}
impl Item {
    pub fn name(&self) -> Option<&SolIdent> {
        match self {
            Self::Contract(ItemContract { name, .. })
            | Self::Enum(ItemEnum { name, .. })
            | Self::Error(ItemError { name, .. })
            | Self::Event(ItemEvent { name, .. })
            | Self::Function(ItemFunction { name: Some(name), .. })
            | Self::Struct(ItemStruct { name, .. })
            | Self::Udt(ItemUdt { name, .. }) => Some(name),
            _ => None,
        }
    }
    pub fn attrs(&self) -> Option<&Vec<Attribute>> {
        match self {
            Self::Contract(ItemContract { attrs, .. })
            | Self::Function(ItemFunction { attrs, .. })
            | Self::Enum(ItemEnum { attrs, .. })
            | Self::Error(ItemError { attrs, .. })
            | Self::Event(ItemEvent { attrs, .. })
            | Self::Struct(ItemStruct { attrs, .. })
            | Self::Udt(ItemUdt { attrs, .. })
            | Self::Variable(VariableDefinition { attrs, .. }) => Some(attrs),
            Self::Import(_) | Self::Pragma(_) | Self::Using(_) => None,
        }
    }
    pub fn attrs_mut(&mut self) -> Option<&mut Vec<Attribute>> {
        match self {
            Self::Contract(ItemContract { attrs, .. })
            | Self::Function(ItemFunction { attrs, .. })
            | Self::Enum(ItemEnum { attrs, .. })
            | Self::Error(ItemError { attrs, .. })
            | Self::Event(ItemEvent { attrs, .. })
            | Self::Struct(ItemStruct { attrs, .. })
            | Self::Udt(ItemUdt { attrs, .. })
            | Self::Variable(VariableDefinition { attrs, .. }) => Some(attrs),
            Self::Import(_) | Self::Pragma(_) | Self::Using(_) => None,
        }
    }
    fn replace_attrs(&mut self, src: Vec<Attribute>) -> Vec<Attribute> {
        if let Some(attrs) = self.attrs_mut() {
            std::mem::replace(attrs, src)
        } else {
            Vec::new()
        }
    }
}