-- | Shield - the Penny runtime environment
--
-- Both Cabin and Copper can benefit from knowing information about
-- the Penny runtime environment, such as environment variables and
-- whether standard output is a terminal. That information is provided
-- by the Runtime type. In the future this module may also provide
-- information about the POSIX locale configuration. For now, that
-- information would require reaching into the FFI and so it is not
-- implemented.

module Penny.Shield (
  ScreenLines,
  unScreenLines,
  ScreenWidth,
  unScreenWidth,
  Output(IsTTY, NotTTY),
  Runtime,
  environment,
  currentTime,
  output,
  screenLines,
  screenWidth,
  Term,
  term,
  runtime,
  termFromEnv,
  autoTerm)
  where

import Control.Applicative ((<$>), (<*>))
import qualified Data.Time as T
import System.Environment (getEnvironment)
import System.IO (hIsTerminalDevice, stdout)
import qualified System.Console.Rainbow as C

import qualified Penny.Lincoln.Bits as B

data ScreenLines = ScreenLines { unScreenLines :: Int }
                 deriving Show

newtype ScreenWidth = ScreenWidth { unScreenWidth :: Int }
                      deriving Show

data Output = IsTTY | NotTTY deriving (Eq, Ord, Show)

newtype Term = Term { unTerm :: String } deriving Show

-- | Information about the runtime environment.
data Runtime = Runtime { environment :: [(String, String)]
                       , currentTime :: B.DateTime
                       , output :: Output }

runtime :: IO Runtime
runtime = Runtime
          <$> getEnvironment
          <*> (toDT <$> T.getZonedTime)
          <*> findOutput
          where
            toDT t = case B.fromZonedTime t of
              Nothing -> error "time conversion error"
              Just ti -> ti

findOutput :: IO Output
findOutput = do
  isTerm <- hIsTerminalDevice stdout
  return $ if isTerm then IsTTY else NotTTY

screenLines :: Runtime -> Maybe ScreenLines
screenLines r =
  (lookup "LINES" . environment $ r)
  >>= safeRead
  >>= return . ScreenLines

screenWidth :: Runtime -> Maybe ScreenWidth
screenWidth r =
  (lookup "COLUMNS" . environment $ r)
  >>= safeRead
  >>= return . ScreenWidth

term :: Runtime -> Maybe Term
term r =
  (lookup "TERM" . environment $ r)
  >>= return . Term

-- | Read, but without crashes.
safeRead :: (Read a) => String -> Maybe a
safeRead s = case reads s of
  (a, []):[] -> Just a
  _ -> Nothing

-- | Determines which Chunk Term to use based on the TERM environment
-- variable, regardless of whether standard output is a terminal. Uses
-- Dumb if TERM is not set.
termFromEnv :: Runtime -> C.Term
termFromEnv rt = case term rt of
  Just t -> C.TermName . unTerm $ t
  Nothing -> C.Dumb

-- | Determines which Chunk Term to use based on whether standard
-- output is a terminal. Uses Dumb if standard output is not a
-- terminal; otherwise, uses the TERM environment variable.
autoTerm :: Runtime -> C.Term
autoTerm rt = case output rt of
  IsTTY -> termFromEnv rt
  NotTTY -> C.Dumb