{-# LANGUAGE
  TemplateHaskell,
  ScopedTypeVariables,
  NoMonomorphismRestriction,
  Haskell2010,
  GeneralizedNewtypeDeriving,
  DeriveDataTypeable,
  CPP,
  StandaloneDeriving,
  DeriveGeneric #-}

-- | This module implements persistence across different Yi runs.
--   It includes minibuffer command history, marks etc.
--   Warning: Current version will _not_ check whether two or more instances
--   of Yi are run at the same time.

module Yi.PersistentState(loadPersistentState,
                          savePersistentState,
                          maxHistoryEntries,
                          persistentSearch)
where

import Control.Monad
import Data.Typeable
import Data.Binary
#if __GLASGOW_HASKELL__ < 708
import Data.DeriveTH
#else
import GHC.Generics (Generic)
#endif
import Data.Default
import System.Directory(doesFileExist)
import qualified Data.Map as M

import Control.Exc(ignoringException)
import Control.Lens

import Yi.Config.Simple.Types(customVariable, Field)
import Yi.Editor
import Yi.History
import Yi.Keymap(YiM)
import Yi.KillRing(Killring(..))
import Yi.Paths(getPersistentStateFilename)
import Yi.Regex(SearchExp(..))
import Yi.Search.Internal (getRegexE, setRegexE)
import Yi.Utils
import Yi.Types (YiConfigVariable)

data PersistentState = PersistentState { histories     :: !Histories
                                       , aKillring     :: !Killring
                                       , aCurrentRegex :: Maybe SearchExp
                                       }

#if __GLASGOW_HASKELL__ < 708
$(derive makeBinary ''PersistentState)
#else
deriving instance Generic PersistentState
instance Binary PersistentState
#endif

newtype MaxHistoryEntries = MaxHistoryEntries { _unMaxHistoryEntries :: Int }
  deriving(Typeable, Binary)

instance Default MaxHistoryEntries where
  def = MaxHistoryEntries 1000

instance YiConfigVariable MaxHistoryEntries

makeLenses ''MaxHistoryEntries

maxHistoryEntries :: Field Int
maxHistoryEntries = customVariable . unMaxHistoryEntries

newtype PersistentSearch = PersistentSearch { _unPersistentSearch :: Bool }
  deriving(Typeable, Binary)

instance Default PersistentSearch where
  def = PersistentSearch True

instance YiConfigVariable PersistentSearch

makeLenses ''PersistentSearch

persistentSearch :: Field Bool
persistentSearch = customVariable . unPersistentSearch

-- | Trims per-command histories to contain at most N completions each.
trimHistories :: Int -> Histories -> Histories
trimHistories maxHistory (Histories m) = Histories $ M.map trimH m
  where
    trimH (History cur content prefix) = History cur (trim content) prefix
    trim content = drop (max 0 (length content - maxHistory)) content

-- | Here is a persistent history saving part.
--   We assume each command is a single line.
--   To add new components, one has to:
--
--   * add new field in @PersistentState@ structure,
--   * add write and read parts in @loadPersistentState@/@savePersistentState@,
--   * add a trimming code in @savePersistentState@ to prevent blowing up
--     of save file.
savePersistentState :: YiM ()
savePersistentState = do
    MaxHistoryEntries histLimit <- withEditor askConfigVariableA
    pStateFilename      <- getPersistentStateFilename
    (hist :: Histories) <- withEditor   getEditorDyn
    kr                  <- withEditor $ use killringA
    curRe               <- withEditor   getRegexE
    let pState = PersistentState {
                   histories     = trimHistories histLimit hist
                 , aKillring     = kr    -- trimmed during normal operation
                 , aCurrentRegex = curRe -- just a single value -> no need to trim
                 }
    io $ encodeFile pStateFilename pState

-- | Reads and decodes a persistent state in both strict, and exception robust
--   way.
readPersistentState :: YiM (Maybe PersistentState)
readPersistentState = do pStateFilename <- getPersistentStateFilename
                         pStateExists <- io $ doesFileExist pStateFilename
                         if not pStateExists
                           then return Nothing
                           else io $ ignoringException $ strictDecoder pStateFilename
  where
    strictDecoder filename = do (state :: PersistentState) <- decodeFile filename
                                state `seq` return (Just state)

-- | Loads a persistent state, and sets Yi state variables accordingly.
loadPersistentState :: YiM ()
loadPersistentState = do
    maybePState <- readPersistentState
    case maybePState of
      Nothing     -> return ()
      Just pState -> do putEditorDyn                $ histories     pState
                        assign killringA            $ aKillring     pState
                        PersistentSearch keepSearch <- askConfigVariableA
                        when keepSearch . withEditor $
                            maybe (return ()) setRegexE $ aCurrentRegex pState