    {-# LANGUAGE CPP #-}

    -- | Vty provides interfaces for both terminal input and terminal
    -- output.
    --
    -- - Input to the terminal is provided to the Vty application as a
    --   sequence of 'Event's.
    --
    -- - Output is provided to Vty by the application 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.
    --
    -- Vty uses threads internally, so programs made with Vty need to be
    -- compiled with the threaded runtime using the GHC `-threaded` option.
    --
    -- @
    --  import "Graphics.Vty"
    --
    --  main = do
    --      cfg <- 'standardIOConfig'
    --      vty <- 'mkVty' cfg
    --      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)
    -- @
    module Graphics.Vty
      ( Vty(..)
      , mkVty
      , Mode(..)
      , module Graphics.Vty.Config
      , module Graphics.Vty.Input
      , module Graphics.Vty.Output
      , module Graphics.Vty.Output.Interface
      , module Graphics.Vty.Picture
      , module Graphics.Vty.Image
      , module Graphics.Vty.Attributes
      )
    where

    import Graphics.Vty.Config
    import Graphics.Vty.Input
    import Graphics.Vty.Output
    import Graphics.Vty.Output.Interface
    import Graphics.Vty.Picture
    import Graphics.Vty.Image
    import Graphics.Vty.Attributes

    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 Vty typically follows this process:
    --
    --    1. Initialize vty
    --
    --    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.
    --
    -- Operations on Vty handles are not thread-safe.
    data Vty = Vty
        { -- | Outputs the given 'Picture'.
          update :: Picture -> IO ()
          -- | Return the next 'Event' or block until one becomes available.
        , nextEvent :: IO Event
          -- | Non-blocking version of 'nextEvent'.
        , nextEventNonblocking :: IO (Maybe Event)
          -- | The input interface. See 'Input'.
        , inputIface :: Input
          -- | The output interface. See 'Output'.
        , outputIface :: Output
          -- | 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.
        , refresh :: 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.
        , shutdown :: IO ()
        , isShutdown :: IO Bool
        }

    -- | Create a Vty handle. At most one handle should be created at a time
    -- for a given terminal device.
    --
    -- The specified configuration is added to the the configuration
    -- loaded by 'userConfig' with the 'userConfig' configuration taking
    -- precedence. See "Graphics.Vty.Config".
    --
    -- For most applications @mkVty defaultConfig@ is sufficient.
    mkVty :: Config -> IO Vty
    mkVty appConfig = do
        config <- (<> appConfig) <$> userConfig
        input  <- inputForConfig config
        out    <- outputForConfig config
        intMkVty input out

    intMkVty :: Input -> Output -> IO Vty
    intMkVty input out = do
        reserveDisplay out

        shutdownVar <- atomically $ newTVar False
        let shutdownIo = do
                alreadyShutdown <- atomically $ swapTVar shutdownVar True
                when (not alreadyShutdown) $ do
                    shutdownInput input
                    releaseDisplay out
                    releaseTerminal out

        let shutdownStatus = atomically $ readTVar 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

        let innerRefresh = do
                writeIORef lastUpdateRef Nothing
                bounds <- displayBounds out
                dc <- displayContext out bounds
                writeIORef (assumedStateRef $ contextDevice dc) initialAssumedState
                mPic <- readIORef lastPicRef
                maybe (return ()) innerUpdate mPic

        let mkResize = uncurry EvResize <$> displayBounds out
            gkey = do
                k <- atomically $ readTChan $ _eventChannel input
                case k of
                    (EvResize _ _)  -> mkResize
                    _ -> return k
            gkey' = do
                k <- atomically $ tryReadTChan $ _eventChannel input
                case k of
                    (Just (EvResize _ _))  -> Just <$> mkResize
                    _ -> return k

        return $ Vty { update = innerUpdate
                     , nextEvent = gkey
                     , nextEventNonblocking = gkey'
                     , inputIface = input
                     , outputIface = out
                     , refresh = innerRefresh
                     , shutdown = shutdownIo
                     , isShutdown = shutdownStatus
                     }