{-# OPTIONS_HADDOCK hide #-}

module Network.Yogurt.IO
  ( writeToTTY, splitAtPrompt
  , maybeInput
  , hGetImpatientLine
  ) where

import System.IO
import System.Console.Readline
import Control.Monad (when, liftM)
import Data.List (elemIndices)

writeToTTY :: String -> IO ()
writeToTTY msg = do
  -- Find out which part of the message is the prompt.
  let (prePrompt, prompt) = splitAtPrompt msg

  -- Empty buffer.
  buf <- getLineBuffer
  pt  <- getPoint
  setLineBuffer ""
  setPoint 0  -- AAARGH
  redisplay

  -- Print prePrompt.
  when (prePrompt /= "") $ do
    putStr prePrompt
    onNewLine

  -- Set prompt as new message; put buffer and point back.
  message prompt
  insertText buf
  setPoint pt
  redisplay

-- Splits a message x in two submessages y and z such that:
-- * @x == y ++ z@
-- * @'all' (/= '\n') z@
-- * @'null' y || 'last' y == '\n'@  (i.e. z is maximal)
splitAtPrompt :: String -> (String, String)
splitAtPrompt cs = case elemIndices '\n' cs of
  [] -> ("", cs)
  is -> splitAt (last is + 1) cs

-- Takes an input method and catches errors, returning results in the Maybe monad.
maybeInput :: IO String -> IO (Maybe String)
maybeInput input = fmap Just input `catch` const (return Nothing)

-- Waits for input, but once the first character is read, waits
-- no longer than the specified number of ms before giving up.
hGetImpatientLine :: Handle -> Int -> IO String
hGetImpatientLine h patience = rec where
  rec = do
    c <- hGetChar h
    if c == '\n'
      then return [c]
      else do
        b <- hWaitForInput h patience
        if b
          then liftM (c:) rec
          else return [c]