-- |
-- Parser for PSCI.
--
module Language.PureScript.Interactive.Parser
  ( parseCommand
  ) where

import           Prelude.Compat hiding (lex)

import           Data.Bifunctor (first)
import           Data.Char (isSpace)
import           Data.List (intercalate)
import qualified Data.Text as T
import           Text.Parsec hiding ((<|>))
import qualified Language.PureScript as P
import qualified Language.PureScript.Interactive.Directive as D
import           Language.PureScript.Interactive.Types
import           Language.PureScript.Parser.Common (mark, same)

-- |
-- Parses PSCI metacommands or expressions input from the user.
--
parseCommand :: String -> Either String Command
parseCommand cmdString =
  case cmdString of
    (':' : cmd) -> parseDirective cmd
    _ -> parseRest psciCommand cmdString

parseRest :: P.TokenParser a -> String -> Either String a
parseRest p s = first show $ do
  ts <- P.lex "" (T.pack s)
  P.runTokenParser "" (p <* eof) ts

psciCommand :: P.TokenParser Command
psciCommand = choice (map try parsers)
  where
  parsers =
    [ psciImport
    , psciDeclaration
    , psciExpression
    , psciDeprecatedLet
    ]

trim :: String -> String
trim = trimEnd . trimStart

trimStart :: String -> String
trimStart = dropWhile isSpace

trimEnd :: String -> String
trimEnd = reverse . trimStart . reverse

parseDirective :: String -> Either String Command
parseDirective cmd =
  case D.directivesFor' dstr of
    [(d, _)] -> commandFor d
    []       -> Left "Unrecognized directive. Type :? for help."
    ds       -> Left ("Ambiguous directive. Possible matches: " ++
                  intercalate ", " (map snd ds) ++ ". Type :? for help.")
  where
  (dstr, arg) = break isSpace cmd

  commandFor d = case d of
    Help    -> return ShowHelp
    Quit    -> return QuitPSCi
    Reload  -> return ReloadState
    Clear   -> return ClearState
    Paste   -> return PasteLines
    Browse  -> BrowseModule <$> parseRest P.moduleName arg
    Show    -> ShowInfo <$> parseReplQuery' (trim arg)
    Type    -> TypeOf <$> parseRest P.parseValue arg
    Kind    -> KindOf <$> parseRest P.parseType arg

-- |
-- Parses expressions entered at the PSCI repl.
--
psciExpression :: P.TokenParser Command
psciExpression = Expression <$> P.parseValue

-- | Imports must be handled separately from other declarations, so that
-- :show import works, for example.
psciImport :: P.TokenParser Command
psciImport = do
  (mn, declType, asQ) <- P.parseImportDeclaration'
  return $ Import (mn, declType, asQ)

-- | Any declaration that we don't need a 'special case' parser for
-- (like import declarations).
psciDeclaration :: P.TokenParser Command
psciDeclaration = fmap Decls $ mark $ many1 $ same *> do
  decl <- discardPositionInfo <$> P.parseDeclaration
  if acceptable decl
    then return decl
    else fail "this kind of declaration is not supported in psci"

discardPositionInfo :: P.Declaration -> P.Declaration
discardPositionInfo (P.PositionedDeclaration _ _ d) = d
discardPositionInfo d = d

acceptable :: P.Declaration -> Bool
acceptable P.DataDeclaration{} = True
acceptable P.TypeSynonymDeclaration{} = True
acceptable P.ExternDeclaration{} = True
acceptable P.ExternDataDeclaration{} = True
acceptable P.TypeClassDeclaration{} = True
acceptable P.TypeInstanceDeclaration{} = True
acceptable P.ExternKindDeclaration{} = True
acceptable P.TypeDeclaration{} = True
acceptable P.ValueDeclaration{} = True
acceptable _ = False

parseReplQuery' :: String -> Either String ReplQuery
parseReplQuery' str =
  case parseReplQuery str of
    Nothing -> Left ("Don't know how to show " ++ str ++ ". Try one of: " ++
                      intercalate ", " replQueryStrings ++ ".")
    Just query -> Right query

-- | To show error message when 'let' is used for declaration in PSCI,
-- which is deprecated.
psciDeprecatedLet :: P.TokenParser Command
psciDeprecatedLet = do
  P.reserved "let"
  P.indented
  _ <- mark (many1 (same *> P.parseLocalDeclaration))
  notFollowedBy $ P.reserved "in"
  fail "Declarations in PSCi no longer require \"let\", as of version 0.11.0"