    -- | This module provides combinators for centering other widgets.
    module Brick.Widgets.Center
      ( -- * Centering horizontally
        hCenter
      , hCenterWith
      , hCenterLayer
      -- * Centering vertically
      , vCenter
      , vCenterWith
      , vCenterLayer
      -- * Centering both horizontally and vertically
      , center
      , centerWith
      , centerLayer
      -- * Centering about an arbitrary origin
      , centerAbout
      )
    where

    import Lens.Micro ((^.), (&), (.~), to)
    import Data.Maybe (fromMaybe)
    import Graphics.Vty (imageWidth, imageHeight, horizCat, charFill, vertCat,
                        translateX, translateY)

    import Brick.Types
    import Brick.Widgets.Core

    -- | Center the specified widget horizontally. Consumes all available
    -- horizontal space.
    hCenter :: Widget n -> Widget n
    hCenter = hCenterWith Nothing

    -- | Center the specified widget horizontally using a Vty image
    -- translation. Consumes all available horizontal space. Unlike hCenter,
    -- this does not fill the surrounding space so it is suitable for use
    -- as a layer. Layers underneath this widget will be visible in regions
    -- surrounding the centered widget.
    hCenterLayer :: Widget n -> Widget n
    hCenterLayer p =
        Widget Greedy (vSize p) $ do
            result <- render p
            c <- getContext
            let rWidth = result^.imageL.to imageWidth
                leftPaddingAmount = max 0 $ (c^.availWidthL - rWidth) `div` 2
                paddedImage = translateX leftPaddingAmount $ result^.imageL
                off = Location (leftPaddingAmount, 0)
            if leftPaddingAmount == 0 then
                return result else
                return $ addResultOffset off
                       $ result & imageL .~ paddedImage

    -- | Center the specified widget horizontally. Consumes all available
    -- horizontal space. Uses the specified character to fill in the space
    -- to either side of the centered widget (defaults to space).
    hCenterWith :: Maybe Char -> Widget n -> Widget n
    hCenterWith mChar p =
        let ch = fromMaybe ' ' mChar
        in Widget Greedy (vSize p) $ do
               result <- render p
               c <- getContext
               let rWidth = result^.imageL.to imageWidth
                   rHeight = result^.imageL.to imageHeight
                   remainder = max 0 $ c^.availWidthL - (leftPaddingAmount * 2)
                   leftPaddingAmount = max 0 $ (c^.availWidthL - rWidth) `div` 2
                   rightPaddingAmount = max 0 $ leftPaddingAmount + remainder
                   leftPadding = charFill (c^.attrL) ch leftPaddingAmount rHeight
                   rightPadding = charFill (c^.attrL) ch rightPaddingAmount rHeight
                   paddedImage = horizCat [ leftPadding
                                          , result^.imageL
                                          , rightPadding
                                          ]
                   off = Location (leftPaddingAmount, 0)
               if leftPaddingAmount == 0 && rightPaddingAmount == 0 then
                   return result else
                   return $ addResultOffset off
                          $ result & imageL .~ paddedImage

    -- | Center a widget vertically.  Consumes all vertical space.
    vCenter :: Widget n -> Widget n
    vCenter = vCenterWith Nothing

    -- | Center the specified widget vertically using a Vty image
    -- translation. Consumes all available vertical space. Unlike vCenter,
    -- this does not fill the surrounding space so it is suitable for use
    -- as a layer. Layers underneath this widget will be visible in regions
    -- surrounding the centered widget.
    vCenterLayer :: Widget n -> Widget n
    vCenterLayer p =
        Widget (hSize p) Greedy $ do
            result <- render p
            c <- getContext
            let rHeight = result^.imageL.to imageHeight
                topPaddingAmount = max 0 $ (c^.availHeightL - rHeight) `div` 2
                paddedImage = translateY topPaddingAmount $ result^.imageL
                off = Location (0, topPaddingAmount)
            if topPaddingAmount == 0 then
                return result else
                return $ addResultOffset off
                       $ result & imageL .~ paddedImage

    -- | Center a widget vertically. Consumes all vertical space. Uses the
    -- specified character to fill in the space above and below the centered
    -- widget (defaults to space).
    vCenterWith :: Maybe Char -> Widget n -> Widget n
    vCenterWith mChar p =
        let ch = fromMaybe ' ' mChar
        in Widget (hSize p) Greedy $ do
               result <- render p
               c <- getContext
               let rWidth = result^.imageL.to imageWidth
                   rHeight = result^.imageL.to imageHeight
                   remainder = max 0 $ c^.availHeightL - (topPaddingAmount * 2)
                   topPaddingAmount = max 0 $ (c^.availHeightL - rHeight) `div` 2
                   bottomPaddingAmount = max 0 $ topPaddingAmount + remainder
                   topPadding = charFill (c^.attrL) ch rWidth topPaddingAmount
                   bottomPadding = charFill (c^.attrL) ch rWidth bottomPaddingAmount
                   paddedImage = vertCat [ topPadding
                                         , result^.imageL
                                         , bottomPadding
                                         ]
                   off = Location (0, topPaddingAmount)
               if topPaddingAmount == 0 && bottomPaddingAmount == 0 then
                   return result else
                   return $ addResultOffset off
                          $ result & imageL .~ paddedImage

    -- | Center a widget both vertically and horizontally. Consumes all
    -- available vertical and horizontal space.
    center :: Widget n -> Widget n
    center = centerWith Nothing

    -- | Center a widget both vertically and horizontally. Consumes all
    -- available vertical and horizontal space. Uses the specified character
    -- to fill in the space around the centered widget (defaults to space).
    centerWith :: Maybe Char -> Widget n -> Widget n
    centerWith c = vCenterWith c . hCenterWith c

    -- | Center a widget both vertically and horizontally using a Vty image
    -- translation. Consumes all available vertical and horizontal space.
    -- Unlike center, this does not fill in the surrounding space with a
    -- character so it is usable as a layer. Any widget underneath this one
    -- will be visible in the region surrounding the centered widget.
    centerLayer :: Widget n -> Widget n
    centerLayer = vCenterLayer . hCenterLayer

    -- | Center the widget horizontally and vertically about the specified
    -- origin.
    centerAbout :: Location -> Widget n -> Widget n
    centerAbout l p =
        Widget Greedy Greedy $ do
          -- Compute translation offset so that loc is in the middle of the
          -- rendering area
          c <- getContext
          let centerW = c^.availWidthL `div` 2
              centerH = c^.availHeightL `div` 2
              off = Location ( centerW - l^.locationColumnL
                             , centerH - l^.locationRowL
                             )
          result <- render $ translateBy off p

          -- Pad the result so it consumes available space
          let rightPaddingAmt = max 0 $ c^.availWidthL - imageWidth (result^.imageL)
              bottomPaddingAmt = max 0 $ c^.availHeightL - imageHeight (result^.imageL)
              rightPadding = charFill (c^.attrL) ' ' rightPaddingAmt (imageHeight $ result^.imageL)
              bottomPadding = charFill (c^.attrL) ' ' (imageWidth $ result^.imageL) bottomPaddingAmt
              paddedImg = horizCat [vertCat [result^.imageL, bottomPadding], rightPadding]

          return $ result & imageL .~ paddedImg