/-
Copyright (c) 2020 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Sebastian Ullrich
-/
import Lean.Parser.Attr
import Lean.Attributes
import Lean.MonadEnv
import Lean.Elab.Util
namespace Lean.Elab

structure Attribute where
  kind  : AttributeKind := AttributeKind.global
  name  : Name
  stx   : Syntax := Syntax.missing
  deriving Inhabited

instance : ToFormat Attribute where
  format attr :=
   let kindStr := match attr.kind with
     | AttributeKind.global => ""
     | AttributeKind.local  => "local "
     | AttributeKind.scoped => "scoped "
   Format.bracket "@[" f!"{kindStr}{attr.name}{toString attr.stx}" "]"

/--
  ```
  attrKind := leading_parser optional («scoped» <|> «local»)
  ```
-/
def toAttributeKind (attrKindStx : Syntax) : MacroM AttributeKind := do
  if attrKindStx[0].isNone then
    return AttributeKind.global
  else if attrKindStx[0][0].getKind == ``Lean.Parser.Term.scoped then
    if (← Macro.getCurrNamespace).isAnonymous then
      throw <| Macro.Exception.error (← getRef) "scoped attributes must be used inside namespaces"
    return AttributeKind.scoped
  else
    return AttributeKind.local

def mkAttrKindGlobal : Syntax :=
  mkNode ``Lean.Parser.Term.attrKind #[mkNullNode]

def elabAttr [Monad m] [MonadEnv m] [MonadResolveName m] [MonadError m] [MonadMacroAdapter m] [MonadRecDepth m] [MonadTrace m] [MonadOptions m] [AddMessageContext m] [MonadInfoTree m] [MonadLiftT IO m] (attrInstance : Syntax) : m Attribute := do
  /- attrInstance     := ppGroup $ leading_parser attrKind >> attrParser -/
  let attrKind ← liftMacroM <| toAttributeKind attrInstance[0]
  let attr := attrInstance[1]
  let attr ← liftMacroM <| expandMacros attr
  let attrName ← if attr.getKind == ``Parser.Attr.simple then
    pure attr[0].getId.eraseMacroScopes
  else match attr.getKind with
    | .str _ s => pure <| Name.mkSimple s
    | _ => throwErrorAt attr  "unknown attribute"
  let .ok impl := getAttributeImpl (← getEnv) attrName
    | throwError "unknown attribute [{attrName}]"
  let attrSyntaxNodeKind := attrInstance[1].getKind
  -- `Lean.Parser.Attr.simple` is a generic `attribute` parser used in simple attributes.
  -- We don't want to create an info tree node from a simple attribute to the generic parser.
  let declTarget := if attrSyntaxNodeKind == ``Lean.Parser.Attr.simple then impl.ref else attrSyntaxNodeKind
  if (← getEnv).contains declTarget && (← getInfoState).enabled then
    pushInfoLeaf <| .ofCommandInfo {
      elaborator := declTarget  -- not truly an elaborator, but a sensible target for go-to-definition
      stx        := attrInstance[1][0] -- We want to associate the information to the first atom only
    }
  /- The `AttrM` does not have sufficient information for expanding macros in `args`.
     So, we expand them before here before we invoke the attributer handlers implemented using `AttrM`. -/
  return { kind := attrKind, name := attrName, stx := attr }

def elabAttrs [Monad m] [MonadEnv m] [MonadResolveName m] [MonadError m] [MonadMacroAdapter m] [MonadRecDepth m] [MonadTrace m] [MonadOptions m] [AddMessageContext m] [MonadLog m] [MonadInfoTree m] [MonadLiftT IO m] (attrInstances : Array Syntax) : m (Array Attribute) := do
  let mut attrs := #[]
  for attr in attrInstances do
    try
      attrs := attrs.push (← withRef attr do elabAttr attr)
    catch ex =>
      logException ex
  return attrs

-- leading_parser "@[" >> sepBy1 attrInstance ", " >> "]"
def elabDeclAttrs [Monad m] [MonadEnv m] [MonadResolveName m] [MonadError m] [MonadMacroAdapter m] [MonadRecDepth m] [MonadTrace m] [MonadOptions m] [AddMessageContext m] [MonadLog m] [MonadInfoTree m] [MonadLiftT IO m] (stx : Syntax) : m (Array Attribute) :=
  elabAttrs stx[1].getSepArgs

end Lean.Elab
