--------------------------------------------------------------------------------
-- |
-- Module       :  XMonad.Config.Components
-- Copyright    :  (c) whythat <yuri.zhykin@gmail.com>
-- License      :  BSD
--
-- Maintainer   :  whythat <yuri.zhykin@gmail.com>
-- Stability    :  unstable
-- Portability  :  unportable
--
-- This module provides possibility to specify in a clear way which software
-- components are to be used as default (i.e. terminal, screen locker,
-- web-browser, file browser etc.)
--
--------------------------------------------------------------------------------


module Components (getComponentTable) where


import System.IO.Unsafe (unsafePerformIO)
import System.Directory (getHomeDirectory, doesFileExist)
import System.FilePath.Posix (joinPath)
import Control.DeepSeq (force)
import Data.Char (isSpace)
import Data.Maybe (fromMaybe)


--------------------------------------------------------------------------------
-- main functionality                                                         --
--------------------------------------------------------------------------------
-- main function
-- returns function that takes `String` key and it turn returns corresponding
-- component name using data read from configuration file or data from default
-- table `defaultComponents`
getComponentTable :: FilePath -> (String -> String)
getComponentTable fname =
  let ctbl = force $ map parseCompEntry $ lines $ rawData fname
  in \c -> fromMaybe (fromMaybe "" (lookup c defaultComponents)) (lookup c ctbl)


-- parsing agorithm to obtain key-value pairs for component table
parseCompEntry :: String -> (String, String)
parseCompEntry line =
  let ws = break (== '=') line
      res@(key, val) = (trim (fst ws), trim (drop 1 (snd ws)))
  in if (head val == '\'' && last val == '\'') then res else ("", "")


-- default table that contains <component, component-name> pairs
-- TODO: make it more generic if possible
defaultComponents :: [(String, String)]
defaultComponents =
  [ ("terminal", "x-terminal-emulator")
  , ("filebrowser", "xdg-open /home/$USERNAME") -- dirty but simple
  ]


-- unsafe IO action that reads data from configuration file if it exists and
-- if not, returns empty `String` value (safety of this is based on an
-- assumption that important computations that use this value all consider
-- empty string case
rawData :: FilePath -> String
rawData comprc = unsafePerformIO $
                 getFullPath comprc >>= \x ->
                 doesFileExist x >>= \c ->
                 if c then readFile x else return ""
--------------------------------------------------------------------------------





--------------------------------------------------------------------------------
-- utils & helpers                                                            --
--------------------------------------------------------------------------------
-- expand path that makes use of ~ relative notation
getFullPath :: FilePath -> IO FilePath
getFullPath s = getHomeDirectory >>= (\x -> return $ fullPath x s)


-- pure helper for `getFullPath` that simply concatenates paths if needed
fullPath :: FilePath -> FilePath -> FilePath
fullPath home_path s | head s == '~' = joinPath [home_path, drop 2 s]
                     | otherwise     = s


-- remove leading and trailing spaces from a string
trim :: String -> String
trim = f . f where f = reverse . dropWhile isSpace
--------------------------------------------------------------------------------                   

