content: {- |
  Module      : Language.Egison.Completion
  Licence     : MIT
  
  This module provides command-line completion.
  -}
  
  module Language.Egison.Completion
    ( completeEgison
    ) where
  
  import           Data.HashMap.Strict         (keys)
  import           Data.List
  import           System.Console.Haskeline    (Completion (..), CompletionFunc, completeWord)
  
  import           Language.Egison.Data        (Env (..))
  import           Language.Egison.IExpr       (Var (..))
  import           Language.Egison.Parser.NonS (lowerReservedWords, upperReservedWords)
  
  -- |Complete Egison keywords
  completeEgison :: Monad m => Env -> CompletionFunc m
  completeEgison _   arg@(')':_, _) = completeParen arg
  completeEgison _   arg@(']':_, _) = completeParen arg
  completeEgison _   arg@('(':_, _) = completeWord Nothing "" completeNothing arg
  completeEgison _   arg@(' ':_, _) = completeWord Nothing "" completeNothing arg
  completeEgison _   arg@('[':_, _) = completeWord Nothing "" completeNothing arg
  completeEgison _   arg@([], _)    = completeWord Nothing "" completeNothing arg
  completeEgison env arg            = completeWord Nothing " \t[]{}$," (completeEgisonKeyword env) arg
  
  completeNothing :: Monad m => String -> m [Completion]
  completeNothing _ = return []
  
  completeEgisonKeyword :: Monad m => Env -> String -> m [Completion]
  completeEgisonKeyword (Env env _) str = do
    let definedWords = filter f $ map (\(Var name _) -> name) $ concatMap keys env
    return $ map (\kwd -> Completion kwd kwd False) $ filter (isPrefixOf str) (egisonKeywords ++ definedWords)
   where
     f [_]         = False
     f [_, '\'']   = False
     f ('b':'.':_) = False
     f _           = True
  
  egisonKeywords :: [String]
  egisonKeywords = upperReservedWords ++ lowerReservedWords
  
  completeParen :: Monad m => CompletionFunc m
  completeParen (lstr, _) = case closeParen lstr of
    Nothing    -> return (lstr, [])
    Just paren -> return (lstr, [Completion paren paren False])
  
  closeParen :: String -> Maybe String
  closeParen str = closeParen' 0 $ removeCharAndStringLiteral str
  
  removeCharAndStringLiteral :: String -> String
  removeCharAndStringLiteral []              = []
  removeCharAndStringLiteral ('"':'\\':str)  = '"':'\\':removeCharAndStringLiteral str
  removeCharAndStringLiteral ('"':str)       = removeCharAndStringLiteral' str
  removeCharAndStringLiteral ('\'':'\\':str) = '\'':'\\':removeCharAndStringLiteral str
  removeCharAndStringLiteral ('\'':str)      = removeCharAndStringLiteral' str
  removeCharAndStringLiteral (c:str)         = c:removeCharAndStringLiteral str
  
  removeCharAndStringLiteral' :: String -> String
  removeCharAndStringLiteral' []              = []
  removeCharAndStringLiteral' ('"':'\\':str)  = removeCharAndStringLiteral' str
  removeCharAndStringLiteral' ('"':str)       = removeCharAndStringLiteral str
  removeCharAndStringLiteral' ('\'':'\\':str) = removeCharAndStringLiteral' str
  removeCharAndStringLiteral' ('\'':str)      = removeCharAndStringLiteral str
  removeCharAndStringLiteral' (_:str)         = removeCharAndStringLiteral' str
  
  closeParen' :: Integer -> String -> Maybe String
  closeParen' _ []        = Nothing
  closeParen' 0 ('(':_)   = Just ")"
  closeParen' 0 ('[':_)   = Just "]"
  closeParen' n ('(':str) = closeParen' (n - 1) str
  closeParen' n ('[':str) = closeParen' (n - 1) str
  closeParen' n (')':str) = closeParen' (n + 1) str
  closeParen' n (']':str) = closeParen' (n + 1) str
  closeParen' n (_:str)   = closeParen' n str
  