content: {-# LANGUAGE CPP #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  
  -- | Vty provides interfaces for both terminal input and terminal
  -- output.
  --
  -- - User input to the terminal is provided to the Vty application as a
  --   sequence of 'Event's.
  --
  -- - Output is provided to by the application to Vty in the form of a
  --   'Picture'. A 'Picture' is one or more layers of 'Image's.
  --   'Image' values can be built by the various constructors in
  --   "Graphics.Vty.Image". Output can be syled using 'Attr' (attribute)
  --   values in the "Graphics.Vty.Attributes" module.
  --
  -- - Each platform on which Vty is supported provides a package that
  --   provides Vty with access to the platform-specific terminal
  --   interface. For example, on Unix systems, the @vty-unix@ package
  --   must be used to initialize Vty with access to a Unix terminal.
  --
  -- As a small example, the following program demonstrates the use of Vty
  -- on a Unix system using the @vty-unix@ package:
  --
  -- > import Graphics.Vty
  -- > import Graphics.Vty.Platform.Unix (mkVty)
  -- >
  -- > main = do
  -- >     vty <- mkVty defaultConfig
  -- >     let line0 = string (defAttr `withForeColor` green) "first line"
  -- >         line1 = string (defAttr `withBackColor` blue) "second line"
  -- >         img = line0 <-> line1
  -- >         pic = picForImage img
  -- >     update vty pic
  -- >     e <- nextEvent vty
  -- >     shutdown vty
  -- >     print ("Last event was: " ++ show e)
  --
  -- Vty uses threads internally, so programs made with Vty must be
  -- compiled with the threaded runtime using the GHC @-threaded@ option.
  module Graphics.Vty
    ( Vty(..)
    , setWindowTitle
    , installCustomWidthTable
    , mkVtyFromPair
    , module Graphics.Vty.Config
    , module Graphics.Vty.Input
    , module Graphics.Vty.Input.Events
    , module Graphics.Vty.Output
    , module Graphics.Vty.Picture
    , module Graphics.Vty.Image
    , module Graphics.Vty.Attributes
    )
  where
  
  import Graphics.Vty.Config
  import Graphics.Vty.Input
  import Graphics.Vty.Input.Events
  import Graphics.Vty.Output
  import Graphics.Vty.Picture
  import Graphics.Vty.Image
  import Graphics.Vty.Attributes
  import Graphics.Vty.UnicodeWidthTable.IO
  import Graphics.Vty.UnicodeWidthTable.Install
  
  import qualified Control.Exception as E
  import Control.Monad (when)
  import Control.Concurrent.STM
  
  import Data.IORef
  #if !(MIN_VERSION_base(4,11,0))
  import Data.Semigroup ((<>))
  #endif
  
  -- | A 'Vty' value represents a handle to the Vty library that the
  -- application must create in order to use Vty.
  --
  -- The use of this library typically follows this process:
  --
  -- 1. Initialize Vty with the 'mkVty' implementation for your
  -- platform's Vty package (e.g. @vty-unix@), or, more generically, with
  -- 'mkVtyFromPair'. This takes control of (and sets up) the terminal.
  --
  -- 2. Use 'update' to display a picture.
  --
  -- 3. Use 'nextEvent' to get the next input event.
  --
  -- 4. Depending on the event, go to 2 or 5.
  --
  -- 5. Shutdown Vty and restore the terminal state with 'shutdown'. At
  -- this point the 'Vty' handle cannot be used again.
  --
  -- Operations on Vty handles are not thread-safe.
  data Vty =
      Vty { update :: Picture -> IO ()
          -- ^ Output the given 'Picture' to the terminal.
          , nextEvent :: IO Event
          -- ^ Return the next 'Event' or block until one becomes
          -- available.
          , nextEventNonblocking :: IO (Maybe Event)
          -- ^ Non-blocking version of 'nextEvent'.
          , inputIface :: Input
          -- ^ The input interface. See 'Input'.
          , outputIface :: Output
          -- ^ The output interface. See 'Output'.
          , refresh :: IO ()
          -- ^ Refresh the display. If other programs output to the
          -- terminal and mess up the display then the application might
          -- want to force a refresh using this function.
          , shutdown :: IO ()
          -- ^ Clean up after vty. A call to this function is necessary to
          -- cleanly restore the terminal state before application exit.
          -- The above methods will throw an exception if executed after
          -- this is executed. Idempotent.
          , isShutdown :: IO Bool
          }
  
  -- | Attempt to load and install a custom character width table into
  -- this process.
  --
  -- This looks up the specified terminal name in the specified width
  -- table map and, if a map file path is found, the map is loaded and
  -- installed. This is exposed for Vty platform package implementors;
  -- application developers should never need to call this.
  installCustomWidthTable :: Maybe FilePath
                          -- ^ Optional path to a log file where log
                          -- messages should be written when attempting to
                          -- load a width table.
                          -> Maybe String
                          -- ^ Optional width table entry name (usually
                          -- the terminal name, e.g. value of @TERM@ on
                          -- Unix systems). If omitted, this function does
                          -- not attempt to load a table.
                          -> [(String, FilePath)]
                          -- ^ Mapping from width table entry names to
                          -- width table file paths. This is usually
                          -- obtained from 'configTermWidthMaps' of
                          -- 'VtyUserConfig'.
                          -> IO ()
  installCustomWidthTable logPath tblName widthMaps = do
      let doLog s = case logPath of
                        Nothing -> return ()
                        Just path -> appendFile path $ "installWidthTable: " <> s <> "\n"
  
      customInstalled <- isCustomTableReady
      when (not customInstalled) $ do
          case tblName of
              Nothing ->
                  doLog "No terminal name given in the configuration, skipping load"
              Just name ->
                  case lookup name widthMaps of
                      Nothing ->
                          doLog $ "Width table " <> show name <> " not found in custom character width mapping list"
                      Just path -> do
                          tableResult <- E.try $ readUnicodeWidthTable path
                          case tableResult of
                              Left (e::E.SomeException) ->
                                  doLog $ "Error reading custom character width table " <>
                                          "at " <> show path <> ": " <> show e
                              Right (Left msg) ->
                                  doLog $ "Error reading custom character width table " <>
                                          "at " <> show path <> ": " <> msg
                              Right (Right table) -> do
                                  installResult <- E.try $ installUnicodeWidthTable table
                                  case installResult of
                                      Left (e::E.SomeException) ->
                                          doLog $ "Error installing unicode table (" <>
                                                  show path <> ": " <> show e
                                      Right () ->
                                          doLog $ "Successfully installed Unicode width table " <>
                                                  " from " <> show path
  
  -- | Build a 'Vty' handle from an input/output pair.
  --
  -- This is exposed for Vty platform package implementors; application
  -- developers should never need to call this, and should instead call
  -- @mkVty@ or equivalent from their platform package of choice.
  mkVtyFromPair :: Input -> Output -> IO Vty
  mkVtyFromPair input out = do
      reserveDisplay out
  
      shutdownVar <- newTVarIO False
      let shutdownIo = do
              alreadyShutdown <- atomically $ swapTVar shutdownVar True
              when (not alreadyShutdown) $ do
                  shutdownInput input
                  releaseDisplay out
                  releaseTerminal out
  
          shutdownStatus = readTVarIO shutdownVar
  
      lastPicRef <- newIORef Nothing
      lastUpdateRef <- newIORef Nothing
  
      let innerUpdate inPic = do
              b <- displayBounds out
              mlastUpdate <- readIORef lastUpdateRef
              updateData <- case mlastUpdate of
                  Nothing -> do
                      dc <- displayContext out b
                      outputPicture dc inPic
                      return (b, dc)
                  Just (lastBounds, lastContext) -> do
                      if b /= lastBounds
                          then do
                              dc <- displayContext out b
                              outputPicture dc inPic
                              return (b, dc)
                          else do
                              outputPicture lastContext inPic
                              return (b, lastContext)
              writeIORef lastUpdateRef $ Just updateData
              writeIORef lastPicRef $ Just inPic
  
          innerRefresh = do
              writeIORef lastUpdateRef Nothing
              bounds <- displayBounds out
              dc <- displayContext out bounds
              writeIORef (assumedStateRef $ contextDevice dc) initialAssumedState
              mPic <- readIORef lastPicRef
              maybe (return ()) innerUpdate mPic
  
          mkResize = uncurry EvResize <$> displayBounds out
  
          translateInternalEvent ResumeAfterInterrupt = mkResize
          translateInternalEvent (InputEvent e) = return e
  
          gkey = do
              e <- atomically $ readTChan $ eventChannel input
              translateInternalEvent e
          gkey' = do
              mEv <- atomically $ tryReadTChan $ eventChannel input
              case mEv of
                  Just e  -> Just <$> translateInternalEvent e
                  Nothing -> return Nothing
  
      return $ Vty { update = innerUpdate
                   , nextEvent = gkey
                   , nextEventNonblocking = gkey'
                   , inputIface = input
                   , outputIface = out
                   , refresh = innerRefresh
                   , shutdown = shutdownIo
                   , isShutdown = shutdownStatus
                   }
  
  -- | Set the terminal window title string.
  setWindowTitle :: Vty -> String -> IO ()
  setWindowTitle vty title =
      setOutputWindowTitle (outputIface vty) title
  