{-# LANGUAGE OverloadedStrings #-}
# LANGUAGE RecordWildCards #

module Matterhorn.Draw.ListWindow
  ( drawListWindow
  , WindowPosition(..)
  )
where

import           Prelude ()
import           Matterhorn.Prelude

import           Brick
import           Brick.Widgets.Border
import           Brick.Widgets.Center
import           Brick.Widgets.Edit
import qualified Brick.Widgets.List as L
import           Control.Monad.Trans.Reader ( withReaderT )
import qualified Data.Foldable as F
import qualified Data.Text as T
import           Graphics.Vty ( imageWidth, translateX)
import           Lens.Micro.Platform ( (%~), (.~), to )

import           Matterhorn.Themes
import           Matterhorn.Types


hLimitWithPadding :: Int -> Widget n -> Widget n
hLimitWithPadding pad contents = Widget
  { hSize  = Fixed
  , vSize  = (vSize contents)
  , render =
      withReaderT (& availWidthL  %~ (\ n -> n - (2 * pad))) $ render $ cropToContext contents
  }

data WindowPosition =
    WindowCenter
    | WindowUpperRight
    deriving (Eq, Show)

-- | Draw a ListWindowState. This draws a bordered box with the
-- window's search input and results list inside the box. The provided
-- functions determine how to render the window in various states.
drawListWindow :: ListWindowState a b
               -- ^ The window state
               -> (b -> Widget Name)
               -- ^ The function to build the window title from the
               -- current search scope
               -> (b -> Widget Name)
               -- ^ The function to generate a message for the search
               -- scope indicating that no results were found
               -> (b -> Widget Name)
               -- ^ The function to generate the editor prompt for the
               -- search scope
               -> (Bool -> a -> Widget Name)
               -- ^ The function to render an item from the window's
               -- list
               -> Maybe (Widget Name)
               -- ^ The footer widget to render underneath the search
               -- results
               -> WindowPosition
               -- ^ How to position the window layer
               -> Int
               -- ^ The maximum window width in columns
               -> Widget Name
drawListWindow st scopeHeader scopeNoResults scopePrompt renderItem footer layerPos maxWinWidth =
  positionLayer $ hLimitWithPadding 10 $ vLimit 25 $
  hLimit maxWinWidth $
  borderWithLabel title body
  where
      title = withDefAttr clientEmphAttr $
              hBox [ scopeHeader scope
                   , case st^.listWindowRecordCount of
                         Nothing -> emptyWidget
                         Just c -> txt " (" <+> str (show c) <+> txt ")"
                   ]
      positionLayer = case layerPos of
          WindowCenter -> centerLayer
          WindowUpperRight -> upperRightLayer
      body = vBox [ (padRight (Pad 1) promptMsg) <+>
                    renderEditor (txt . T.unlines) True (st^.listWindowSearchInput)
                  , cursorPositionBorder
                  , showResults
                  , fromMaybe emptyWidget footer
                  ]
      plural 1 = ""
      plural _ = "s"
      cursorPositionBorder =
          if st^.listWindowSearching
          then hBorderWithLabel $ txt "[Searching...]"
          else case st^.listWindowSearchResults.L.listSelectedL of
              Nothing -> hBorder
              Just _ ->
                  let showingFirst = "Showing first " <> show numSearchResults <>
                                     " result" <> plural numSearchResults
                      showingAll = "Showing all " <> show numSearchResults <>
                                   " result" <> plural numSearchResults
                      showing = "Showing " <> show numSearchResults <>
                                " result" <> plural numSearchResults
                      msg = case getEditContents (st^.listWindowSearchInput) of
                          [""] ->
                              case st^.listWindowRecordCount of
                                  Nothing -> showing
                                  Just total -> if numSearchResults < total
                                                then showingFirst
                                                else showingAll
                          _ -> showing
                  in hBorderWithLabel $ str $ "[" <> msg <> "]"

      scope = st^.listWindowSearchScope
      promptMsg = scopePrompt scope

      showMessage = center . withDefAttr clientEmphAttr

      showResults
        | numSearchResults == 0 = showMessage $ scopeNoResults scope
        | otherwise = renderedUserList

      renderedUserList = L.renderList renderItem True (st^.listWindowSearchResults)
      numSearchResults = F.length $ st^.listWindowSearchResults.L.listElementsL

upperRightLayer :: Widget a -> Widget a
upperRightLayer w =
    Widget (hSize w) (vSize w) $ do
        result <- render w
        c <- getContext
        let rWidth = result^.imageL.to imageWidth
            leftPaddingAmount = max 0 $ c^.availWidthL - rWidth
            paddedImage = translateX leftPaddingAmount $ result^.imageL
            off = Location (leftPaddingAmount, 0)
        if leftPaddingAmount == 0 then
            return result else
            return $ addResultOffset off
                   $ result & imageL .~ paddedImage

# LANGUAGE OverloadedStrings #
 | Draw a ListWindowState. This draws a bordered box with the
 window's search input and results list inside the box. The provided
 functions determine how to render the window in various states.
 ^ The window state
 ^ The function to build the window title from the
 current search scope
 ^ The function to generate a message for the search
 scope indicating that no results were found
 ^ The function to generate the editor prompt for the
 search scope
 ^ The function to render an item from the window's
 list
 ^ The footer widget to render underneath the search
 results
 ^ How to position the window layer
 ^ The maximum window width in columns
# LANGUAGE RecordWildCards #

module Matterhorn.Draw.ListWindow
  ( drawListWindow
  , WindowPosition(..)
  )
where

import           Prelude ()
import           Matterhorn.Prelude

import           Brick
import           Brick.Widgets.Border
import           Brick.Widgets.Center
import           Brick.Widgets.Edit
import qualified Brick.Widgets.List as L
import           Control.Monad.Trans.Reader ( withReaderT )
import qualified Data.Foldable as F
import qualified Data.Text as T
import           Graphics.Vty ( imageWidth, translateX)
import           Lens.Micro.Platform ( (%~), (.~), to )

import           Matterhorn.Themes
import           Matterhorn.Types


hLimitWithPadding :: Int -> Widget n -> Widget n
hLimitWithPadding pad contents = Widget
  { hSize  = Fixed
  , vSize  = (vSize contents)
  , render =
      withReaderT (& availWidthL  %~ (\ n -> n - (2 * pad))) $ render $ cropToContext contents
  }

data WindowPosition =
    WindowCenter
    | WindowUpperRight
    deriving (Eq, Show)

drawListWindow :: ListWindowState a b
               -> (b -> Widget Name)
               -> (b -> Widget Name)
               -> (b -> Widget Name)
               -> (Bool -> a -> Widget Name)
               -> Maybe (Widget Name)
               -> WindowPosition
               -> Int
               -> Widget Name
drawListWindow st scopeHeader scopeNoResults scopePrompt renderItem footer layerPos maxWinWidth =
  positionLayer $ hLimitWithPadding 10 $ vLimit 25 $
  hLimit maxWinWidth $
  borderWithLabel title body
  where
      title = withDefAttr clientEmphAttr $
              hBox [ scopeHeader scope
                   , case st^.listWindowRecordCount of
                         Nothing -> emptyWidget
                         Just c -> txt " (" <+> str (show c) <+> txt ")"
                   ]
      positionLayer = case layerPos of
          WindowCenter -> centerLayer
          WindowUpperRight -> upperRightLayer
      body = vBox [ (padRight (Pad 1) promptMsg) <+>
                    renderEditor (txt . T.unlines) True (st^.listWindowSearchInput)
                  , cursorPositionBorder
                  , showResults
                  , fromMaybe emptyWidget footer
                  ]
      plural 1 = ""
      plural _ = "s"
      cursorPositionBorder =
          if st^.listWindowSearching
          then hBorderWithLabel $ txt "[Searching...]"
          else case st^.listWindowSearchResults.L.listSelectedL of
              Nothing -> hBorder
              Just _ ->
                  let showingFirst = "Showing first " <> show numSearchResults <>
                                     " result" <> plural numSearchResults
                      showingAll = "Showing all " <> show numSearchResults <>
                                   " result" <> plural numSearchResults
                      showing = "Showing " <> show numSearchResults <>
                                " result" <> plural numSearchResults
                      msg = case getEditContents (st^.listWindowSearchInput) of
                          [""] ->
                              case st^.listWindowRecordCount of
                                  Nothing -> showing
                                  Just total -> if numSearchResults < total
                                                then showingFirst
                                                else showingAll
                          _ -> showing
                  in hBorderWithLabel $ str $ "[" <> msg <> "]"

      scope = st^.listWindowSearchScope
      promptMsg = scopePrompt scope

      showMessage = center . withDefAttr clientEmphAttr

      showResults
        | numSearchResults == 0 = showMessage $ scopeNoResults scope
        | otherwise = renderedUserList

      renderedUserList = L.renderList renderItem True (st^.listWindowSearchResults)
      numSearchResults = F.length $ st^.listWindowSearchResults.L.listElementsL

upperRightLayer :: Widget a -> Widget a
upperRightLayer w =
    Widget (hSize w) (vSize w) $ do
        result <- render w
        c <- getContext
        let rWidth = result^.imageL.to imageWidth
            leftPaddingAmount = max 0 $ c^.availWidthL - rWidth
            paddedImage = translateX leftPaddingAmount $ result^.imageL
            off = Location (leftPaddingAmount, 0)
        if leftPaddingAmount == 0 then
            return result else
            return $ addResultOffset off
                   $ result & imageL .~ paddedImage
