-----------------------------------------------------------------------------
--
-- Module      : Language.PureScript.Ide.State
-- Description : Functions to access psc-ide's state
-- Copyright   : Christoph Hegemann 2016
-- License     : MIT (http://opensource.org/licenses/MIT)
--
-- Maintainer  : Christoph Hegemann <christoph.hegemann1337@gmail.com>
-- Stability   : experimental
--
-- |
-- Functions to access psc-ide's state
-----------------------------------------------------------------------------

{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE PackageImports        #-}
{-# LANGUAGE TemplateHaskell       #-}

module Language.PureScript.Ide.State
  ( getPscIdeState
  , getExternFiles
  , getModule
  , getModuleWithReexports
  , getAllModulesWithReexports
  , getAllModulesWithReexportsAndCache
  , insertModule
  , insertModuleSTM
  , getCachedRebuild
  , resetPscIdeState
  , setCachedRebuild
  ) where

import           Prelude                           ()
import           Prelude.Compat

import           Control.Concurrent.STM
import           Control.Monad.IO.Class
import           "monad-logger" Control.Monad.Logger
import           Control.Monad.Reader.Class
import qualified Data.Map.Lazy                     as M
import           Data.Maybe                        (mapMaybe)
import           Data.Monoid
import           Language.PureScript.Externs
import           Language.PureScript.Ide.Externs
import           Language.PureScript.Ide.Reexports
import           Language.PureScript.Ide.Types
import           Language.PureScript.Ide.Util
import qualified Language.PureScript as P

-- | Resets the PscIdeState to emptyPscIdeState
resetPscIdeState :: PscIde m => m ()
resetPscIdeState = do
  stateVar <- envStateVar <$> ask
  liftIO $ atomically (writeTVar stateVar emptyPscIdeState)

-- | Gets the entire PscIdeState
getPscIdeState :: PscIde m => m PscIdeState
getPscIdeState = do
  stateVar <- envStateVar <$> ask
  liftIO (readTVarIO stateVar)

-- | Gets all loaded ExternFiles
getExternFiles :: (PscIde m) => m (M.Map P.ModuleName ExternsFile)
getExternFiles = do
  stateVar <- envStateVar <$> ask
  liftIO (pscIdeStateExternsFiles <$> readTVarIO stateVar)

-- | Gets all loaded Modules and resolves Reexports
getAllModulesWithReexports :: (PscIde m) => m [Module]
getAllModulesWithReexports = getAllModulesWithReexports' <$> getPscIdeState

-- | Pure version of @getAllModulesWithReexports@
getAllModulesWithReexports' :: PscIdeState -> [Module]
getAllModulesWithReexports' state =
  mapMaybe (getModuleWithReexports' state) (M.keys (pscIdeStateModules state))

-- | Checks if the given ModuleName matches the last rebuild cache and if it
-- does, runs @getAllModulesWithReexports@ with the cached module replacing the
-- loaded module
getAllModulesWithReexportsAndCache
  :: (PscIde m)
  => Maybe P.ModuleName
  -> m [Module]
getAllModulesWithReexportsAndCache Nothing = getAllModulesWithReexports
getAllModulesWithReexportsAndCache (Just mn) = do
  state <- getPscIdeState
  cachedRebuild <- getCachedRebuild
  case cachedRebuild of
    Just (cachedIdent, ef) | cachedIdent == mn ->
     pure (getAllModulesWithReexports' (insertModule' ef state))
    _ -> getAllModulesWithReexports

-- | Looks up a single Module inside the loaded Modules
getModule :: (PscIde m, MonadLogger m) => ModuleIdent -> m (Maybe Module)
getModule m = getModule' <$> getPscIdeState <*> pure m

-- | Pure version of @getModule@
getModule' :: PscIdeState -> ModuleIdent -> Maybe Module
getModule' ps mi = (mi,) <$> M.lookup mi (pscIdeStateModules ps)

-- | Looks up a single Module and resolves its Reexports
getModuleWithReexports :: PscIde m => ModuleIdent -> m (Maybe Module)
getModuleWithReexports i = getModuleWithReexports' <$> getPscIdeState <*> pure i

-- | Pure version of @getModuleWithReexports@
getModuleWithReexports' :: PscIdeState -> ModuleIdent -> Maybe Module
getModuleWithReexports' ps mi =
  resolveReexports (pscIdeStateModules ps) <$> getModule' ps mi

-- | Inserts an @ExternsFile@ into the PscIdeState. Also converts the
-- ExternsFile into psc-ide's internal Declaration format
insertModule :: (PscIde m, MonadLogger m) =>
               ExternsFile -> m ()
insertModule externsFile = do
  stateVar <- envStateVar <$> ask
  let moduleName = efModuleName externsFile
  $(logDebug) $ "Inserting Module: " <> runModuleNameT moduleName
  liftIO . atomically $ insertModuleSTM stateVar externsFile

-- | STM version of insertModule
insertModuleSTM :: TVar PscIdeState -> ExternsFile -> STM ()
insertModuleSTM st ef = modifyTVar st (insertModule' ef)

-- | Pure version of insertModule
insertModule' :: ExternsFile -> PscIdeState -> PscIdeState
insertModule' ef state =
  state
  { pscIdeStateExternsFiles =
      M.insert (efModuleName ef) ef (pscIdeStateExternsFiles state)
  , pscIdeStateModules = let (mn, decls) = convertExterns ef
                         in M.insert mn decls (pscIdeStateModules state)
  }

-- | Sets rebuild cache to the given ExternsFile
setCachedRebuild :: PscIde m => ExternsFile -> m ()
setCachedRebuild ef = do
  st <- envStateVar <$> ask
  liftIO . atomically . modifyTVar st $ \x ->
    x { pscIdeStateCachedRebuild = Just (efModuleName ef, ef) }

-- | Retrieves the rebuild cache
getCachedRebuild :: PscIde m => m (Maybe (P.ModuleName, ExternsFile))
getCachedRebuild = pscIdeStateCachedRebuild <$> getPscIdeState