{-# LANGUAGE NamedFieldPuns #-}

module FortyTwo.Utils where

import System.Console.ANSI (cursorUpLine, clearFromCursorToScreenEnd)
import System.IO (hSetBuffering, hFlush, hSetEcho, hReady, stdin, stdout, BufferMode(..))
import Data.List (findIndex, findIndices, elemIndex, intercalate)
import Control.Monad (when)
import Data.Maybe (fromJust)
import FortyTwo.Types(Option(..), Options)
import FortyTwo.Constants (emptyString)

-- | Disable the stdin stdout output buffering
noBuffering :: IO()
noBuffering = do
  hSetBuffering stdin NoBuffering
  hSetBuffering stdout NoBuffering

-- | Enaable the stdin stdout buffering
restoreBuffering :: IO()
restoreBuffering = do
  hSetBuffering stdin LineBuffering
  hSetBuffering stdout LineBuffering

-- | Avoid echoing the user input
noEcho :: IO ()
noEcho = hSetEcho stdin False

-- | Restore the user input echos
restoreEcho :: IO ()
restoreEcho = hSetEcho stdin True

-- | Clear terminal lines from the current cursor position
clearLines :: Int -> IO()
clearLines lines' = do
  -- move up of 1 line...
  cursorUpLine lines'
  -- and clear them
  clearFromCursorToScreenEnd

-- | Map a collection with an index
map' :: (Int -> a -> b) -> [a] -> [b]
map' f = zipWith f [0..]

-- | Filter a collection with index
filter' :: Eq a => (Int -> a -> Bool) -> [a] -> [a]
filter' f xs = [x | x <- xs, f (fromJust (elemIndex x xs)) x]

-- | Get the value of any keyboard press
getKey :: IO String
getKey = reverse <$> getKey' emptyString
  where
    getKey' chars = do
      char <- getChar
      more <- hReady stdin
      (if more then getKey' else return) (char:chars)

-- | Flush the output buffer
flush :: IO()
flush = hFlush stdout

-- | Get useful informations from the options collection, like minVal, maxVal, activeIndex
getOptionsMeta :: Options -> (Int, Int, Maybe Int)
getOptionsMeta options = (0, length options - 1, getFocusedOptionIndex options)

-- | Convert a string array to
stringsToOptions :: [String] -> Options
stringsToOptions options = [
    Option { value = o, isFocused = False, isSelected = False } | o <- options
  ]

-- | Give the focus to a single option in the collection
focusOption :: Int -> Options -> Options
focusOption focusedIndex = map' $ \ i o ->
  Option {
    value = getOptionValue o,
    isSelected = getOptionIsSelected o,
    isFocused = focusedIndex == i
  }

-- | Toggle the isSelected flag for a single option
toggleFocusedOption :: Int -> Options -> Options
toggleFocusedOption focusedIndex = map' $ \ i o ->
  Option {
    value = getOptionValue o,
    isFocused = focusedIndex == i,
    isSelected = if focusedIndex == i then
      not $ getOptionIsSelected o
      else getOptionIsSelected o
  }

-- | Print a list to comma separated
toCommaSeparatedString :: [String] -> String
toCommaSeparatedString = intercalate ", "

-- | Get the value of any option
getOptionValue :: Option -> String
getOptionValue Option { value } = value

-- | Get the is focused attribute of any option
getOptionIsFocused :: Option -> Bool
getOptionIsFocused Option { isFocused } = isFocused

-- | Get the is selected attribute of any option
getOptionIsSelected :: Option -> Bool
getOptionIsSelected Option { isSelected } = isSelected

-- | Get the index of the option selected
getFocusedOptionIndex :: Options -> Maybe Int
getFocusedOptionIndex = findIndex getOptionIsFocused

-- | Filter the indexes of the options selected
getSelecteOptionsIndexes :: Options -> [Int]
getSelecteOptionsIndexes = findIndices getOptionIsSelected