{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE OverloadedStrings, TemplateHaskell, TupleSections #-}

module UI.FileBrowser (runFileBrowserUI) where

import Brick
import Brick.Widgets.Border
import Brick.Widgets.Center
import Brick.Widgets.List
import Brick.Widgets.FileBrowser
import Control.Exception (displayException, try)
import Control.Monad.IO.Class
import Lens.Micro.Platform
import Parser
import Types
import qualified Graphics.Vty as V

type Event = ()
type Name = ()
data State = State
  { _fb         :: FileBrowser Name
  , _exception  :: Maybe String
  , _cards      :: [Card]
  , _filePath   :: Maybe FilePath
  , _showHidden :: Bool
  }

makeLenses ''State

app :: App State Event Name
app = App
  { appDraw = drawUI
  , appChooseCursor = neverShowCursor
  , appHandleEvent = handleEvent
  , appStartEvent = return
  , appAttrMap = const theMap
  }

errorAttr :: AttrName
errorAttr = "error"

theMap :: AttrMap
theMap = attrMap V.defAttr
    [ (listSelectedFocusedAttr, V.black `on` V.yellow)
    , (fileBrowserCurrentDirectoryAttr, V.white `on` V.blue)
    , (fileBrowserSelectionInfoAttr, V.white `on` V.blue)
    , (fileBrowserDirectoryAttr, fg V.blue)
    , (fileBrowserBlockDeviceAttr, fg V.magenta)
    , (fileBrowserCharacterDeviceAttr, fg V.green)
    , (fileBrowserNamedPipeAttr, fg V.yellow)
    , (fileBrowserSymbolicLinkAttr, fg V.cyan)
    , (fileBrowserUnixSocketAttr, fg V.red)
    , (fileBrowserSelectedAttr, V.white `on` V.magenta)
    , (errorAttr, fg V.red)
    ]

drawUI :: State -> [Widget Name]
drawUI State{_fb=b, _exception=exc} = [center $ ui <=> help]
    where
        ui = hCenter $
             vLimit 15 $
             hLimit 50 $
             borderWithLabel (txt "Choose a file") $
             renderFileBrowser True b
        help = padTop (Pad 1) $
               vBox [ hCenter $ txt "Up/Down: select, h: toggle show hidden files"
                    , hCenter $ txt "/: search, Ctrl-C or Esc: cancel search"
                    , hCenter $ txt "Enter: change directory or select file"
                    , hCenter $ txt "Esc: quit"
                    , case exc of
                          Nothing -> emptyWidget
                          Just e -> hCenter $ withDefAttr errorAttr $
                                    str e
                    ]

handleEvent :: State -> BrickEvent Name Event -> EventM Name (Next State)
handleEvent s@State{_fb=b} (VtyEvent ev) =
    case ev of
        V.EvKey V.KEsc [] | not (fileBrowserIsSearching b) ->
            halt s
        V.EvKey (V.KChar 'c') [V.MCtrl] | not (fileBrowserIsSearching b) ->
            halt s
        V.EvKey (V.KChar 'h') [] | not (fileBrowserIsSearching b) -> let s' = s & showHidden %~ not in
            continue $ s' & fb .~ setFileBrowserEntryFilter (Just (entryFilter (s' ^. showHidden))) b
        _ -> do
            b' <- handleFileBrowserEvent ev b
            let s' = s & fb .~ b'
            -- If the browser has a selected file after handling the
            -- event (because the user pressed Enter), shut down.
            case ev of
                V.EvKey V.KEnter [] ->
                    case fileBrowserSelection b' of
                        [] -> continue s'
                        [fileInfo] -> do
                          let fp = fileInfoFilePath fileInfo
                          fileOrExc <- liftIO (try (readFile fp) :: IO (Either IOError String))
                          case fileOrExc of
                            Left exc -> continue (s' & exception ?~ displayException exc)
                            Right file -> case parseCards file of
                              Left parseError -> continue (s & exception ?~ show parseError)
                              Right result -> halt (s' & cards .~ result & filePath ?~ fp)
                        _ -> halt s'

                _ -> continue s'
handleEvent s _ = continue s

runFileBrowserUI :: IO (Maybe ([Card], FilePath))
runFileBrowserUI = do
  browser <- newFileBrowser selectNonDirectories () Nothing
  let filteredBrowser = setFileBrowserEntryFilter (Just (entryFilter False)) browser
  s <- defaultMain app (State filteredBrowser Nothing [] Nothing False)
  let mfp = s ^. filePath
  return $ fmap (s ^. cards,) mfp

entryFilter :: Bool -> FileInfo -> Bool
entryFilter acceptHidden info = fileExtensionMatch "txt" info && (acceptHidden ||
  case fileInfoFilename info of
    ".."    -> True
    '.' : _ -> False
    _       -> True)