{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE CPP #-}
-- |
-- Module      : Scion.Inspect.DefinitionSite
-- Copyright   : (c) Thomas Schilling 2009
-- License     : BSD-style
--
-- Maintainer  : nominolo@gmail.com
-- Stability   : experimental
-- Portability : portable
--
-- Collecting and finding the definition site of an identifier.
--
-- This module analyses Haskell code to find the definition sites of
-- identifiers within.
--
module Scion.Inspect.DefinitionSite where

import Scion.Types
import Scion.Types.Notes

import GHC
import Name ( getOccString, getSrcSpan )
import Outputable ( showSDoc, ppr, Outputable, (<+>) )
import PprTyThing ( pprTyThingInContext )
import TyCon ( isCoercionTyCon, isFamInstTyCon )
import Var ( globalIdVarDetails )
import IdInfo ( GlobalIdDetails(..) )
import HscTypes ( isBootSummary )

import qualified Data.Map as M
import Data.List ( foldl' )
import Data.Monoid
import Control.Monad ( foldM )

------------------------------------------------------------------------
-- * Intended Interface

-- | Construct a 'DefSiteDB' for a complete module graph.
--
-- Note: All the modules mentioned in the module graph must have been
-- loaded.  This is done either by a successful call to 'GHC.load' or by a
-- call to 'GHC.loadModule' for each module (in dependency order).
moduleGraphDefSiteDB ::
     FilePath    -- ^ Base path (see 'ghcSpanToLocation')
  -> ModuleGraph
  -> ScionM DefSiteDB
moduleGraphDefSiteDB base_dir mg = do
    let mg' = filter (not . isBootSummary) mg
    foldM go emptyDefSiteDB mg'
  where
    go db modsum = do
      db1 <- moduleSiteDB (base_dir, ms_mod modsum)
      return (db1 `mappend` db)

-- | Construct a 'DefSiteDB' for a single module only.
moduleSiteDB :: (FilePath, Module) 
                -- ^ Base path (see 'ghcSpanToLocation') and module.
             -> ScionM DefSiteDB
moduleSiteDB (base_dir, mdl) = do
  mb_mod_info <- getModuleInfo mdl
  case mb_mod_info of
    Nothing -> return emptyDefSiteDB
    Just mod_info -> do
      return $ mkSiteDB base_dir (modInfoTyThings mod_info)

-- ** Internal Stuff

-- | Construct a 'SiteDB' from a base directory and a list of 'TyThing's.
mkSiteDB :: FilePath -> [TyThing] -> DefSiteDB
mkSiteDB base_dir ty_things = foldl' go emptyDefSiteDB ty_things
  where
    -- TODO: there's probably more stuff to ignore
    go db (ATyCon tycon) | is_boring_tycon tycon = db -- ignore
    go db (ADataCon datacon) | is_boring_datacon datacon = db
    go db (AnId nm)
      | isDictonaryId nm || not (is_interesting_id nm) = db
    go db ty_thing =
       addToDB (getOccString ty_thing)
               (ghcSpanToLocation base_dir (getSrcSpan ty_thing))
               ty_thing db

    is_interesting_id ident =
        case globalIdVarDetails ident of
          VanillaGlobal -> True
          ClassOpId _ -> True
          RecordSelId {} -> True
          NotGlobalId -> True -- global but not exported
          _ -> False

    is_boring_tycon tycon =
        isClassTyCon tycon || isCoercionTyCon tycon || isFamInstTyCon tycon

    is_boring_datacon datacon =
        is_boring_tycon (dataConTyCon datacon)

addToDB :: String -> Location -> TyThing -> DefSiteDB -> DefSiteDB
addToDB nm loc ty_thing (DefSiteDB m) =
    DefSiteDB (M.insertWith (++) nm [(loc,ty_thing)] m)


-- | Dump a definition site DB to stdout. (For debugging purposes.)
dumpDefSiteDB :: DefSiteDB -> String
dumpDefSiteDB (DefSiteDB m) = unlines (map pp (M.assocs m))
  where
    pp (s, l_ty_things) = show s ++ ":\n" ++ unlines
      [ "  " ++ show (viewLoc l) ++ ", " ++ pp_ty_thing t
        | (l, t) <- l_ty_things ]

    pp_ty_thing tt@(AnId ident) =
        showSDoc (pprTyThingInContext False tt <+> ppr (globalIdVarDetails ident))

    pp_ty_thing (ADataCon dcon) =
        showSDoc (ppr dcon <+> ppr (dataConType dcon))

    pp_ty_thing tt = showSDoc (ppr tt)