{-# LANGUAGE TupleSections #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable#-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
module Brick.Widgets.List
  ( List
  
  , list
  
  , renderList
  , renderListWithIndex
  
  , handleListEvent
  , handleListEventVi
  
  , listElementsL
  , listSelectedL
  , listNameL
  , listItemHeightL
  
  , listElements
  , listName
  , listSelectedElement
  , listSelected
  , listItemHeight
  
  , listMoveBy
  , listMoveTo
  , listMoveUp
  , listMoveDown
  , listMoveByPages
  , listMovePageUp
  , listMovePageDown
  , listInsert
  , listRemove
  , listReplace
  , listClear
  , listReverse
  , listModify
  
  , listAttr
  , listSelectedAttr
  , listSelectedFocusedAttr
  )
where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>),(<*>),pure)
import Data.Foldable (Foldable)
import Data.Traversable (Traversable)
#endif
import Lens.Micro ((^.), (&), (.~), (%~), _2)
import Data.Maybe (fromMaybe)
import Data.Monoid ((<>))
import Graphics.Vty (Event(..), Key(..), Modifier(..))
import qualified Data.Vector as V
import Brick.Types
import Brick.Main (lookupViewport)
import Brick.Widgets.Core
import Brick.Util (clamp)
import Brick.AttrMap
data List n e =
    List { listElements :: !(V.Vector e)
         
         , listSelected :: !(Maybe Int)
         
         , listName :: n
         
         , listItemHeight :: Int
         
         } deriving (Functor, Foldable, Traversable, Show)
suffixLenses ''List
instance Named (List n e) n where
    getName = listName
handleListEvent :: (Ord n) => Event -> List n e -> EventM n (List n e)
handleListEvent e theList =
    case e of
        EvKey KUp [] -> return $ listMoveUp theList
        EvKey KDown [] -> return $ listMoveDown theList
        EvKey KHome [] -> return $ listMoveTo 0 theList
        EvKey KEnd [] -> return $ listMoveTo (V.length $ listElements theList) theList
        EvKey KPageDown [] -> listMovePageDown theList
        EvKey KPageUp [] -> listMovePageUp theList
        _ -> return theList
handleListEventVi :: (Ord n)
                  => (Event -> List n e -> EventM n (List n e))
                  
                  
                  -> Event
                  -> List n e
                  -> EventM n (List n e)
handleListEventVi fallback e theList =
    case e of
        EvKey (KChar 'k') [] -> return $ listMoveUp theList
        EvKey (KChar 'j') [] -> return $ listMoveDown theList
        EvKey (KChar 'g') [] -> return $ listMoveTo 0 theList
        EvKey (KChar 'G') [] -> return $ listMoveTo (V.length $ listElements theList) theList
        EvKey (KChar 'f') [MCtrl] -> listMovePageDown theList
        EvKey (KChar 'b') [MCtrl] -> listMovePageUp theList
        EvKey (KChar 'd') [MCtrl] -> listMoveByPages (0.5::Double) theList
        EvKey (KChar 'u') [MCtrl] -> listMoveByPages (-0.5::Double) theList
        _ -> fallback e theList
listAttr :: AttrName
listAttr = "list"
listSelectedAttr :: AttrName
listSelectedAttr = listAttr <> "selected"
listSelectedFocusedAttr :: AttrName
listSelectedFocusedAttr = listSelectedAttr <> "focused"
list :: n
     
     -> V.Vector e
     
     -> Int
     
     
     -> List n e
list name es h =
    let selIndex = if V.null es then Nothing else Just 0
        safeHeight = max 1 h
    in List es selIndex name safeHeight
renderList :: (Ord n, Show n)
           => (Bool -> e -> Widget n)
           
           -> Bool
           
           -> List n e
           
           -> Widget n
           
renderList drawElem = renderListWithIndex $ const drawElem
renderListWithIndex :: (Ord n, Show n)
           => (Int -> Bool -> e -> Widget n)
           
           
           -> Bool
           
           -> List n e
           
           -> Widget n
           
renderListWithIndex drawElem foc l =
    withDefAttr listAttr $
    drawListElements foc l drawElem
drawListElements :: (Ord n, Show n) => Bool -> List n e -> (Int -> Bool -> e -> Widget n) -> Widget n
drawListElements foc l drawElem =
    Widget Greedy Greedy $ do
        c <- getContext
        let es = V.slice start num (l^.listElementsL)
            idx = fromMaybe 0 (l^.listSelectedL)
            start = max 0 $ idx - numPerHeight + 1
            num = min (numPerHeight * 2) (V.length (l^.listElementsL) - start)
            
            
            initialNumPerHeight = (c^.availHeightL) `div` (l^.listItemHeightL)
            
            
            
            
            
            
            
            
            numPerHeight = initialNumPerHeight +
                           if initialNumPerHeight * (l^.listItemHeightL) == c^.availHeightL
                           then 0
                           else 1
            off = start * (l^.listItemHeightL)
            drawnElements = flip V.imap es $ \i e ->
                let j = i + start
                    isSelected = Just j == l^.listSelectedL
                    elemWidget = drawElem j isSelected e
                    selItemAttr = if foc
                                  then withDefAttr listSelectedFocusedAttr
                                  else withDefAttr listSelectedAttr
                    makeVisible = if isSelected
                                  then visible . selItemAttr
                                  else id
                in makeVisible elemWidget
        render $ viewport (l^.listNameL) Vertical $
                 translateBy (Location (0, off)) $
                 vBox $ V.toList drawnElements
listInsert :: Int
           
           -> e
           
           -> List n e
           -> List n e
listInsert pos e l =
    let safePos = clamp 0 (V.length es) pos
        es = l^.listElementsL
        newSel = case l^.listSelectedL of
          Nothing -> 0
          Just s -> if safePos <= s
                    then s + 1
                    else s
        (front, back) = V.splitAt safePos es
    in l & listSelectedL .~ Just newSel
         & listElementsL .~ (front V.++ (e `V.cons` back))
listRemove :: Int
           
           -> List n e
           -> List n e
listRemove pos l | V.null (l^.listElementsL) = l
                 | pos /= clamp 0 (V.length (l^.listElementsL) - 1) pos = l
                 | otherwise =
    let newSel = case l^.listSelectedL of
          Nothing -> 0
          Just s | pos == 0 -> 0
                 | pos == s -> pos - 1
                 | pos  < s -> s - 1
                 | otherwise -> s
        (front, back) = V.splitAt pos es
        es' = front V.++ V.tail back
        es = l^.listElementsL
    in l & listSelectedL .~ (if V.null es' then Nothing else Just newSel)
         & listElementsL .~ es'
listReplace :: V.Vector e -> Maybe Int -> List n e -> List n e
listReplace es idx l =
    let newSel = if V.null es then Nothing else clamp 0 (V.length es - 1) <$> idx
    in l & listSelectedL .~ newSel
         & listElementsL .~ es
listMoveUp :: List n e -> List n e
listMoveUp = listMoveBy (-1)
listMovePageUp :: (Ord n) => List n e -> EventM n (List n e)
listMovePageUp theList = listMoveByPages (-1::Double) theList
listMoveDown :: List n e -> List n e
listMoveDown = listMoveBy 1
listMovePageDown :: (Ord n) => List n e -> EventM n (List n e)
listMovePageDown theList = listMoveByPages (1::Double) theList
listMoveByPages :: (Ord n, RealFrac m) => m -> List n e -> EventM n (List n e)
listMoveByPages pages theList = do
    v <- lookupViewport (theList^.listNameL)
    case v of
        Nothing -> return theList
        Just vp -> let
            nElems = round $ pages * (fromIntegral $ vp^.vpSize._2) / (fromIntegral $ theList^.listItemHeightL)
          in
            return $ listMoveBy nElems theList
listMoveBy :: Int -> List n e -> List n e
listMoveBy amt l =
    let current = case l^.listSelectedL of
          Nothing
            | amt > 0 -> Just 0
            | otherwise -> Just (V.length (l^.listElementsL) - 1)
          cur -> cur
        clamp' a b c
          | a <= b = Just (clamp a b c)
          | otherwise = Nothing
        newSel = clamp' 0 (V.length (l^.listElementsL) - 1) =<< (amt +) <$> current
    in l & listSelectedL .~ newSel
listMoveTo :: Int -> List n e -> List n e
listMoveTo pos l =
    let len = V.length (l^.listElementsL)
        newSel = clamp 0 (len - 1) $ if pos < 0 then len - pos else pos
    in l & listSelectedL .~ if len > 0
                            then Just newSel
                            else Nothing
listSelectedElement :: List n e -> Maybe (Int, e)
listSelectedElement l = do
  sel <- l^.listSelectedL
  return (sel, (l^.listElementsL) V.! sel)
listClear :: List n e -> List n e
listClear l = l & listElementsL .~ V.empty & listSelectedL .~ Nothing
listReverse :: List n e -> List n e
listReverse theList = theList & listElementsL %~ V.reverse & listSelectedL .~ newSel
  where n = V.length (listElements theList)
        newSel = (-) <$> pure (n-1) <*> listSelected theList
listModify :: (e -> e) -> List n e -> List n e
listModify f l = case listSelectedElement l of
  Nothing -> l
  Just (n,e) -> let es = V.update (l^.listElementsL) (return (n, f e))
                in listReplace es (Just n) l