{-# OPTIONS_GHC -Wall #-}

{-# LANGUAGE LambdaCase #-}

{- |

Functions for making alterations to @skylighting-core@ values ('SyntaxMap',
'Syntax', 'Context', 'Rule', etc.).

-}

module Skylighting.Modding
  (
  -- * Syntax maps
    SyntaxMap, syntaxMap, addSyntax, modifySyntax

  -- * Syntax fields
  , modifySyntaxContexts

  -- * Context maps
  , ContextMap, contextMap, addContext, modifyContext

  -- * Rules
  , replaceKeywordRule, isKeywordRule

  ) where

-- containers
import qualified Data.Map as Map
import Data.Map (Map)

-- skylighting-core
import Skylighting.Core hiding (syntaxMap)

-- text
import Data.Text (Text)

{- |

A map of contexts, keyed by 'cName'.

This is the type of 'sContexts'.

-}

type ContextMap = Map Text Context

{- |

Convert a 'Context' into a 'ContextMap' that contains only that one context.

-}

contextMap :: Context -> ContextMap
contextMap c = Map.singleton (cName c) c

{- |

Adds one 'Context' to a 'ContextMap', or replaces a context of the same name if
one exists.

-}

addContext :: Context -> ContextMap -> ContextMap
addContext c m = Map.insert (cName c) c m

{- |

Modify a 'SyntaxMap' by looking up a particular syntax by name, applying some
function to it, and placing the resulting syntax into the map in place of the
original syntax.

-}

modifyContext
    :: Text -- ^ The name of the context to modify
    -> (Context -> Context) -> ContextMap -> ContextMap

modifyContext name f m = Map.adjust f name m

{- |

Convert a 'Syntax' into a 'SyntaxMap' that contains only that one syntax.

-}

syntaxMap :: Syntax -> SyntaxMap
syntaxMap s = Map.singleton (sName s) s

{- |

Adds one 'Syntax' to a 'SyntaxMap', or replaces a context of the same name if
one exists.

-}

addSyntax :: Syntax -> SyntaxMap -> SyntaxMap
addSyntax s m = Map.insert (sName s) s m

{- |

Modify a 'SyntaxMap' by looking up a particular syntax by name, applying some
function to it, and placing the resulting syntax into the map in place of the
original syntax.

-}

modifySyntax
    :: Text -- ^ The name of the syntax to modify
    -> (Syntax -> Syntax) -> SyntaxMap -> SyntaxMap

modifySyntax name f m = Map.adjust f name m

{- |

Apply a function to the 'sContexts' field of a 'Syntax'.

-}

modifySyntaxContexts :: (ContextMap -> ContextMap) -> Syntax -> Syntax
modifySyntaxContexts f s = s { sContexts = f (sContexts s) }

{- |

Alters a 'Context' by replacing any 'Rule' that looks like a typical keyword
rule (as determined by 'isKeywordRule') with the given rule.

-}

replaceKeywordRule :: Rule -> (Context -> Context)
replaceKeywordRule newRule context =
    let
        rules =
            fmap
                (\r -> if isKeywordRule r then newRule else r)
                (cRules context)
    in
        context { cRules = rules }

{- |

Determines whether a 'Rule' looks like a typical rule for keywords:

  1. 'rAttribute' = 'KeywordTok'
  2. 'rMatcher' is of the 'Keyword' variety

-}

isKeywordRule :: Rule -> Bool
isKeywordRule =
    \case
        Rule{ rAttribute = KeywordTok, rMatcher = Keyword _ _ } -> True
        _                                                       -> False