-- This module uses the open recursion interface
-- ("Language.Haskell.Names.Open") to annotate the AST with binding
-- information.
{-# LANGUAGE FlexibleContexts, MultiParamTypeClasses, ImplicitParams,
    UndecidableInstances, ScopedTypeVariables,
    TypeOperators, GADTs #-}
module Language.Haskell.Names.Annotated
  ( Scoped(..)
  , NameInfo(..)
  , annotateDecl
  ) where


import Language.Haskell.Names.Types
import Language.Haskell.Names.RecordWildcards
import Language.Haskell.Names.Open.Base
import Language.Haskell.Names.Open.Instances ()
import qualified Language.Haskell.Names.GlobalSymbolTable as Global
import qualified Language.Haskell.Names.LocalSymbolTable as Local
import Language.Haskell.Names.SyntaxUtils (
  dropAnn, setAnn, nameQualification, qNameToName)
import Language.Haskell.Exts
import Data.Proxy
import Data.Lens.Light
import Data.Typeable (
  Typeable, (:~:)(Refl), eqT)


annotateDecl
  :: forall a l .
     (Resolvable (a (Scoped l)), Functor a, Typeable l)
  => Scope -> a l -> a (Scoped l)
annotateDecl sc = annotateRec (Proxy :: Proxy l) sc . fmap (Scoped None)

annotateRec
  :: forall a l .
     (Typeable l, Resolvable a)
  => Proxy l -> Scope -> a -> a
annotateRec _ sc a = go sc a where
  go :: forall a . Resolvable a => Scope -> a -> a
  go sc a
    | Just (Refl :: QName (Scoped l) :~: a) <- eqT
      = lookupQName (fmap sLoc a) sc <$ a
    | Just (Refl :: Name (Scoped l) :~: a) <- eqT
      = lookupName (fmap sLoc a) sc <$ a
    | Just (Refl :: FieldUpdate (Scoped l) :~: a) <- eqT
      = case a of
          FieldWildcard l ->
            FieldWildcard (Scoped (RecExpWildcard namesRes) (sLoc l)) where
              namesRes = do
                f <- sc ^. wcNames
                let localQName = qualifyName Nothing (setAnn (sLoc l) (wcFieldName f))
                    symbol = wcFieldSymbol f
                Scoped info _ <- return (lookupQName localQName sc)
                return (symbol, info)
          _ -> rmap go sc a
    | Just (Refl :: PatField (Scoped l) :~: a) <- eqT
      = case a of
          PFieldWildcard l ->
            PFieldWildcard (Scoped (RecPatWildcard namesRes) (sLoc l)) where
              namesRes = do
                f <- sc ^. wcNames
                let symbol = wcFieldSymbol f
                return symbol
          _ -> rmap go sc a
    | otherwise
      = rmap go sc a


lookupQName :: QName l -> Scope -> Scoped l
lookupQName (Special l _) _ = Scoped None l
lookupQName qname scope = Scoped nameInfo (ann qname) where

  nameInfo = case getL patSynMode scope of

    Nothing -> case getL nameCtx scope of

      ReferenceV -> case Local.lookupValue qname (getL lTable scope) of
        Right srcloc -> LocalValue srcloc
        _ ->
          checkUniqueness (Global.lookupValue qname globalTable)

      ReferenceT ->
        checkUniqueness (Global.lookupType qname globalTable)

      ReferenceUT ->
        checkUniqueness (Global.lookupMethodOrAssociate qname' globalTable) where
          qname' = case qname of
            UnQual _ name -> qualifyName maybeQualification name where
              maybeQualification = maybe Nothing nameQualification (getL instClassName scope)
            _ -> qname

      ReferenceRS ->
        checkUniqueness (Global.lookupSelector qname globalTable)

      _ -> None

    Just PatSynLeftHandSide -> case getL nameCtx scope of

      ReferenceV -> ValueBinder

      ReferenceRS -> ValueBinder

      _ -> None

    Just PatSynRightHandSide -> case getL nameCtx scope of

      ReferenceV -> case Local.lookupValue qname (getL lTable scope) of
        Right srcloc -> LocalValue srcloc
        _ -> checkUniqueness (Global.lookupValue qname globalTable)
      ReferenceRS ->
        checkUniqueness (Global.lookupSelector qname globalTable)

      _ -> None


  globalTable = getL gTable scope

  checkUniqueness symbols = case symbols of
    [] -> ScopeError (ENotInScope qname)
    [symbol] -> GlobalSymbol symbol (dropAnn qname)
    _ -> ScopeError (EAmbiguous qname symbols)


lookupName :: Name l -> Scope -> Scoped l
lookupName name scope = Scoped nameInfo (ann name) where

  nameInfo = case getL patSynMode scope of

    Nothing -> case getL nameCtx scope of

      ReferenceUV ->
        disambiguateMethod maybeClassName qname (Global.lookupMethodOrAssociate qname globalTable) where
          qname = qualifyName maybeQualification name
          maybeQualification = maybe Nothing nameQualification maybeClassName
          maybeClassName = getL instClassName scope

      SignatureV ->
        checkUniqueness qname (Global.lookupValue qname globalTable) where
          qname = qualifyName (Just (getL moduName scope)) name

      BindingV -> ValueBinder

      BindingT -> TypeBinder

      _ -> None

    Just PatSynLeftHandSide -> case getL nameCtx scope of

      BindingV -> ValueBinder

      _ -> None

    Just PatSynRightHandSide -> case getL nameCtx scope of

      BindingV ->
        case Local.lookupValue (qualifyName Nothing name) (getL lTable scope) of
          Right srcloc -> LocalValue srcloc
          _ -> None

      _ -> None


  globalTable = getL gTable scope


checkUniqueness :: QName l -> [Symbol] -> NameInfo l
checkUniqueness qname symbols = case symbols of
  [] -> ScopeError (ENotInScope qname)
  [symbol] -> GlobalSymbol symbol (dropAnn qname)
  _ -> ScopeError (EAmbiguous qname symbols)


disambiguateMethod :: Maybe (QName ()) -> QName l -> [Symbol] -> NameInfo l
disambiguateMethod Nothing _ _ = ScopeError (EInternal "method in instance of unknown class")
disambiguateMethod (Just instanceClassName) qname symbols = checkUniqueness qname disambiguatedSymbols where
  disambiguatedSymbols =
    filter (\symbol -> className symbol == qNameToName instanceClassName) symbols

qualifyName :: Maybe (ModuleName ()) -> Name l -> QName l
qualifyName Nothing n = UnQual (ann n) n
qualifyName (Just (ModuleName () moduleName)) n =
  Qual (ann n) annotatedModuleName n where
    annotatedModuleName = ModuleName (ann n) moduleName

