{-# LANGUAGE LambdaCase,TemplateHaskell, MultiWayIf #-}

-- | A complete re-implementation of the official herbstclient program
module Main where

import HLWM.IPC
import System.Console.GetOpt
import Data.List
import System.Environment
import System.Exit
import System.IO

data HCOptions = HCOpt {
  newline :: Bool,
  print0  :: Bool,
  lastArg :: Bool,
  idle    :: Bool,
  wait    :: Bool,
  count   :: Int,
  quiet   :: Bool,
  version :: Bool,
  help    :: Bool
}

defOpts :: HCOptions
defOpts = HCOpt {
  newline  = True,
  print0   = False,
  lastArg  = False,
  idle     = False,
  wait     = False,
  count    = 1,
  quiet    = False,
  version  = False,
  help     = False
}

options :: [OptDescr (HCOptions -> HCOptions)]
options =
  [ Option ['n'] ["no-newline"] (NoArg $ \o -> o { newline = False })
    "Do not print a newline if output does not end with a newline."
  , Option ['0'] ["print0"] (NoArg $ \o -> o { print0 = True })
    "Use the null character as delimiter between the output of hooks."
  , Option ['l'] ["last-arg"] (NoArg $ \o -> o { lastArg = True })
    "Print only the last argument of a hook."
  , Option ['i'] ["idle"] (NoArg $ \o -> o { idle = True })
    "Wait for hooks instead of executing commands."
  , Option ['w'] ["wait"] (NoArg $ \o -> o { wait = True })
    "Same as --idle but exit after first --count hooks."
  , Option ['c'] ["count"] (ReqArg (\a o -> o { count = read a }) "COUNT")
    "Let --wait exit after COUNT hooks were received and printed. The default of COUNT is 1."
  , Option ['q'] ["quiet"] (NoArg $ \o -> o { quiet = True })
    "Do not print error messages if herbstclient cannot connect to the running herbstluftwm instance."
  , Option ['v'] ["version"] (NoArg $ \o -> o { version = True })
    "Print the herbstclient version. To get the herbstluftwm version, use 'herbstclient version'."
  , Option ['h'] ["help"] (NoArg $ \o -> o { help = True }) "Print this help."
  ]

usage :: String -> String
usage name = "Usage: " ++ name ++ " [OPTION...] files..."

hcOpts :: [String] -> IO (HCOptions, [String])
hcOpts argv = do
  case getOpt Permute options argv of
   (o,n,[]  ) -> return (foldl (flip id) defOpts o, n)
   (_,_,errs) -> ioError (userError (concat errs))

putStrMaybeLn :: String -> IO ()
putStrMaybeLn str
  | "\n" `isSuffixOf` str = putStr str
  | otherwise = putStrLn str

helpString :: String -> String
helpString name = unlines $
  [ "Usage: " ++ name ++ " [OPTION...] files..."
  , "       " ++ name ++ " [OPTIONS] [--wait|--idle] [FILTER ...]"
  , "Send a COMMAND with optional arguments ARGS to a running herbstluftwm instance."
  , ""
  , usageInfo "Options:" options
  , "See the man page (herbstclient(1)) for more details."
  ]

data Wait = Infinite
          | Wait Int

newtype NullPolicy = Null Bool
newtype NLPolicy = NL Bool
newtype Quiet = Quiet Bool
newtype LastArg = LastArg Bool

withQConnection :: Quiet -> a -> (HerbstConnection -> IO a) -> IO a
withQConnection q x f = withConnection f >>= \case
  Nothing -> case q of
    Quiet True  -> return x
    Quiet False -> hPutStrLn stderr "Could not connect to server" >> return x
  Just y -> return y

waitForHooks :: Wait -> NullPolicy -> Quiet -> LastArg -> IO ()
waitForHooks w nl q la = withQConnection q () (doWait w)
  where doWait (Wait 0) _ = return () -- TODO handle negative values
        doWait w' con = do
          h <- nextHook con
          case la of
           LastArg True  | not (null h) -> putStr (last h)
           _                            -> putStr $ unwords h
          case nl of
           Null True  -> putStr "\0"
           Null False -> putStr "\n"
          case w' of
           Infinite -> doWait Infinite con
           Wait x   -> doWait (Wait (x-1)) con

send :: [String] -> NLPolicy -> Quiet -> IO ExitCode
send args nl q = withQConnection q (ExitFailure 1)$ \con -> do
  (stat, ret) <- sendCommand con args
  case nl of
   NL False -> putStr ret
   NL True  -> if null ret || last ret == '\n'
               then putStr ret else putStrLn ret
  return $ if stat == 0 then ExitSuccess else ExitFailure stat

main :: IO ()
main = do
  name <- getProgName
  (opts, args) <- getArgs >>= hcOpts
  if | help opts -> putStr $ helpString name
     | version opts -> putStrLn "A friendly haskell implementation of herbstclient"
     | idle opts -> waitForHooks Infinite (Null (print0 opts))
                                  (Quiet (quiet opts)) (LastArg (lastArg opts))
     | wait opts -> waitForHooks (Wait (count opts)) (Null (print0 opts))
                                  (Quiet (quiet opts)) (LastArg (lastArg opts))
     | otherwise  -> send args (NL (newline opts)) (Quiet (quiet opts))
                     >>= exitWith
