module FortyTwo.Prompts.Password (password) where

import Control.Monad.IO.Class

import System.Console.ANSI (cursorBackward, clearFromCursorToScreenEnd, setCursorColumn, clearFromCursorToLineEnd)
import Control.Monad (unless)
import FortyTwo.Renderers.Password (renderPassword, hideLetters)
import FortyTwo.Renderers.Question (renderQuestion)
import FortyTwo.Constants (emptyString, enterKey, delKey)
import FortyTwo.Utils

-- | Ask a user password
-- password "What your secret password?"
password :: MonadIO m => String -> m String
password :: forall (m :: * -> *). MonadIO m => String -> m String
password String
question = forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ do
  String -> IO ()
putStrLn String
emptyString
  forall (m :: * -> *).
MonadIO m =>
String -> String -> String -> m ()
renderQuestion String
question String
emptyString String
emptyString
  String -> IO ()
putStr String
" "
  IO ()
flush
  forall (m :: * -> *). MonadIO m => m ()
noBuffering
  String
answer <- String -> IO String
loop String
emptyString
  forall (m :: * -> *). MonadIO m => m ()
restoreBuffering
  -- for the password prompt we need to clear the prompt a bit differently
  Int -> IO ()
setCursorColumn Int
0
  IO ()
clearFromCursorToLineEnd
  forall (m :: * -> *).
MonadIO m =>
String -> String -> String -> m ()
renderQuestion String
question String
emptyString forall a b. (a -> b) -> a -> b
$ String -> String
hideLetters String
answer
  forall (m :: * -> *) a. Monad m => a -> m a
return String
answer

-- | Loop to let the users select an single option
loop :: String -> IO String
loop :: String -> IO String
loop String
pass = do
  forall (m :: * -> *). MonadIO m => m ()
noEcho
  forall (m :: * -> *). MonadIO m => String -> m ()
renderPassword String
pass
  String
key <- IO String
getKey

  -- Cancel part of the password avoiding to cancel the question as well
  -- the password and the question are located on the same line
  forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
pass) forall a b. (a -> b) -> a -> b
$ do
    Int -> IO ()
cursorBackward forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t a -> Int
length String
pass
    IO ()
clearFromCursorToScreenEnd

  -- Try to filter the control keys
  String
res <- String -> String -> IO String
handleEvent String
pass (if forall (t :: * -> *) a. Foldable t => t a -> Int
length String
key forall a. Ord a => a -> a -> Bool
> Int
1 then String
emptyString else String
key)
  forall (m :: * -> *). MonadIO m => m ()
restoreEcho
  forall (m :: * -> *) a. Monad m => a -> m a
return String
res

-- | Handle a user event
handleEvent ::String -> String -> IO String
handleEvent :: String -> String -> IO String
handleEvent String
pass String
key
  | String
key forall a. Eq a => a -> a -> Bool
== String
enterKey = forall (m :: * -> *) a. Monad m => a -> m a
return String
pass
  | String
key forall a. Eq a => a -> a -> Bool
== String
delKey =
    if forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
pass then forall (m :: * -> *) a. Monad m => a -> m a
return String -> IO String
loop String
emptyString String
pass
    else forall (m :: * -> *) a. Monad m => a -> m a
return String -> IO String
loop String
emptyString (forall a. [a] -> [a]
init String
pass)
  | Bool
otherwise = String -> IO String
loop forall a b. (a -> b) -> a -> b
$ String
pass forall a. [a] -> [a] -> [a]
++ String
key