content: {-# LANGUAGE DataKinds #-}
  {-# LANGUAGE FlexibleContexts #-}
  {-# LANGUAGE GADTs #-}
  {-# LANGUAGE OverloadedRecordDot #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE TypeFamilies #-}
  {-# LANGUAGE TypeOperators #-}
  
  module GLuaFixer.Effects.AnalyseGlobals where
  
  import Data.Char (toLower)
  import Data.Map.Strict (Map)
  import qualified Data.Map.Strict as Map
  import Effectful (Eff, (:>))
  import qualified Effectful.State.Static.Local as State
  import GHC.Exts (sortWith)
  import GLua.AG.AST (AST)
  import GLua.AG.PrettyPrint (renderRegion)
  import GLua.Position (Region)
  import GLuaFixer.AG.ASTLint (globalDefinitions)
  import GLuaFixer.Effects.Logging (Logging, putStrLnStdOut)
  import GLuaFixer.LintSettings (LintSettings)
  import Prelude hiding (readFile, writeFile)
  
  data VariableLocation = VariableLocation
    { file :: !FilePath
    , regions :: ![Region]
    }
  newtype AnalysisState = AnalysisState {state :: Map String [VariableLocation]}
  
  emptyState :: AnalysisState
  emptyState = AnalysisState Map.empty
  
  -- | Run the analysis and get the finished analysis state
  execAnalysis :: Eff (State.State AnalysisState : es) () -> Eff es AnalysisState
  execAnalysis = State.execState emptyState
  
  -- | Analyse a single file
  analyseFile :: State.State AnalysisState :> es => LintSettings -> FilePath -> AST -> Eff es ()
  analyseFile lintSettings filepath ast = do
    let
      definitionsInFile :: Map String [Region] = globalDefinitions lintSettings ast
      variableLocations :: Map String [VariableLocation] =
        Map.map ((: []) . VariableLocation filepath) definitionsInFile
  
    State.modify $ \analysisState ->
      AnalysisState $ Map.unionWith (++) analysisState.state variableLocations
  
  -- | Print the analysis state to the terminal.
  -- TODO: make the rendering a pure function
  reportAnalysis :: forall es. Logging :> es => AnalysisState -> Eff es ()
  reportAnalysis analysis =
    mapM_ reportGlobal globals
    where
      globals :: [(String, [VariableLocation])]
      globals = sortWith (map toLower . fst) $ Map.toList $ Map.map (sortWith file) analysis.state
  
      reportRegions :: [Region] -> Eff es ()
      reportRegions rgs =
        mapM_ (\r -> putStrLnStdOut $ "    " ++ renderRegion r) $ reverse rgs
  
      reportVariableLocation :: VariableLocation -> Eff es ()
      reportVariableLocation location =
        do
          putStrLnStdOut $ "  " ++ file location ++ ":"
          reportRegions $ regions location
          putStrLnStdOut ""
  
      reportGlobal :: (String, [VariableLocation]) -> Eff es ()
      reportGlobal (global, analyses) =
        do
          putStrLnStdOut $ "- " ++ global
          mapM_ reportVariableLocation analyses
  