module Rasa.Ext.Views.Internal.Actions
  ( rotate
  , closeInactive
  , focusViewLeft
  , focusViewRight
  , focusViewAbove
  , focusViewBelow
  , hSplit
  , vSplit
  , addSplit
  , nextBuf
  , prevBuf
  , focusDo
  , focusDo_
  , focusedBufs
  , isFocused
  , scrollBy
  ) where

import Rasa.Ext
import qualified Rasa.Ext.Views.Internal.Views as V
import Rasa.Ext.Views.Internal.BiTree

import Control.Lens
import Control.Monad
import Data.Maybe
import Data.List

-- | Flip all Horizontal splits to Vertical ones and vice versa.
rotate :: App ()
rotate = V.overWindows V.rotate

-- | Move focus from any viewports one viewport to the left
focusViewLeft :: App ()
focusViewLeft = V.overWindows V.focusViewLeft

-- | Move focus from any viewports one viewport to the right
focusViewRight :: App ()
focusViewRight = V.overWindows V.focusViewRight

-- | Move focus from any viewports one viewport above
focusViewAbove :: App ()
focusViewAbove = V.overWindows V.focusViewAbove

-- | Move focus from any viewports one viewport below
focusViewBelow :: App ()
focusViewBelow = V.overWindows V.focusViewBelow

-- | Close all inactive viewports
closeInactive :: App ()
closeInactive = do
  mWindows <- V.getViews
  V.setViews $ mWindows >>= V.closeBy (not . view V.active)

-- | Split active views horizontally
hSplit :: App ()
hSplit = V.overWindows V.hSplit

-- | Split active views vertically
vSplit :: App ()
vSplit = V.overWindows V.vSplit

-- | Add a new split at the top level in the given direction containing the given buffer.
addSplit :: BufAdded -> App ()
addSplit (BufAdded bRef) = do
  mWin <- V.getViews
  case mWin of
    Nothing -> V.setViews . Just $ Leaf (V.View True (V.BufView bRef) 0)
    Just win -> V.setViews . Just $ V.addSplit V.Vert (V.BufView bRef) win

-- | Select the next buffer in any active viewports
nextBuf :: App ()
nextBuf = V.traverseViews next
  where
    next vw
      | vw ^. V.active = do
        newViewable <- getNextBufRef (vw^. V.viewable)
        return (vw &  V.viewable .~ newViewable)
      | otherwise = return vw

    getNextBufRef (V.BufView br) = V.BufView <$> nextBufRef br
    getNextBufRef v = return v

-- | Select the previous buffer in any active viewports
prevBuf :: App ()
prevBuf = V.traverseViews prev
  where
    prev vw
      | vw ^. V.active = do
        newViewable <- getPrevBufRef (vw^. V.viewable)
        return (vw &  V.viewable .~ newViewable)
      | otherwise = return vw

    getPrevBufRef (V.BufView br) = V.BufView <$> prevBufRef br
    getPrevBufRef v = return v

-- | Get bufRefs for all buffers that are selected in at least one viewport
focusedBufs :: App [BufRef]
focusedBufs = do
  mWindows <- V.getViews
  case mWindows of
    Nothing -> return []
    Just win -> return . nub . activeBufRefs $ win
  where activeBufRefs = toListOf $ traverse . filtered (view V.active) . V.viewable . V._BufViewRef

-- | Returns whether the current buffer is focused in at least one view.
isFocused :: BufAction Bool
isFocused = do
  inFocus <- liftApp focusedBufs
  br <- getBufRef
  return $ br `elem` inFocus

-- | Run a bufAction over all focused buffers and return any results.
focusDo :: BufAction a -> App [a]
focusDo bufAct = do
  bufRefs <- focusedBufs
  catMaybes <$> mapM (`bufDo` bufAct) bufRefs

-- | 'focusDo' with a void return
focusDo_ :: BufAction a -> App ()
focusDo_ = void . focusDo

-- | Scrolls each focused viewport by the given amount.
scrollBy :: Int -> App ()
scrollBy amt = V.overWindows $ V.scrollBy amt