{-# LANGUAGE FlexibleContexts #-}
module Hbro.Util where

-- {{{ Imports
import Hbro.Types

import Control.Concurrent
import Control.Exception
import Control.Monad hiding(mapM_)
import Control.Monad.Error hiding(mapM_)
import Control.Monad.Reader hiding(mapM_)
--import Control.Monad.IO.Class
import Control.Monad.Trans.Control

import Data.Foldable
import Data.Functor
import Data.IORef
import Data.List

import Graphics.Rendering.Pango.Enums
import Graphics.UI.Gtk.Display.Label
import Graphics.UI.Gtk.Gdk.EventM
import Graphics.UI.Gtk.General.General
import Graphics.UI.Gtk.WebKit.Download as W
import Graphics.UI.Gtk.WebKit.NetworkRequest as W

import Network.URI (URI)
import qualified Network.URI as N

import Prelude hiding(log, mapM_)

import System.Console.CmdArgs
import System.FilePath
import qualified System.Info as Sys
-- import System.IO
import System.IO.Error hiding(try)
import System.Posix.Process
import System.Posix.Types
import System.Process
-- }}}

-- | Alias for 'liftIO'
io :: MonadIO m => IO a -> m a
io = liftIO

-- | Like 'forkIO' using 'MVar' as thread control
fork :: (MonadIO m, MonadBaseControl IO m) => m () -> m (MVar ())
fork f = do
    mvar <- io newEmptyMVar
    void . liftBaseWith $ \runInIO -> forkIO $ finally (void $ runInIO f) (putMVar mvar ())
    return mvar

-- | Like '</>' with first argument in IO to build platform-dependent paths.
(>/>) :: (MonadIO m) => IO FilePath -> FilePath -> m FilePath
(>/>) a b = io $ (</> b) <$> a

logNormal, logVerbose :: (MonadIO m) => String -> m ()
logNormal  = io . whenNormal . putStrLn
logVerbose = io . whenLoud   . putStrLn

-- {{{ Process management
-- | Run external command and won't kill when parent process exit.
spawn :: MonadIO m => String -> [String] -> m ()
spawn command options = io . void $ createProcess (proc command options) { std_in = CreatePipe,  std_out = CreatePipe, std_err = CreatePipe, close_fds = True }

-- | Return the list of process IDs corresponding to all running instances of the browser.
getAllProcessIDs :: MonadIO m => m [ProcessID]
getAllProcessIDs = do
    (_, pids, _)  <- io $ readProcessWithExitCode "pidof" ["hbro"] []
    (_, pids', _) <- io $ readProcessWithExitCode "pidof" ["hbro-" ++ Sys.os ++ "-" ++ Sys.arch] []
    myPid         <- io $ getProcessID

    return $ delete myPid . map (read :: String -> ProcessID) . nub . words $ pids ++ " " ++ pids'
-- }}}

-- | Set a temporary markup text to a label that disappears after some delay.
labelSetMarkupTemporary :: {-IORef HandlerId ->-} Label -> String -> Int -> IO ()
labelSetMarkupTemporary {-x-} label text delay = do
    --handler <- readIORef x
    --timeoutRemove handler

    labelSetMarkup label text
    timeoutAdd (clear >> return False) delay >> return () -- >>= writeIORef x
  where
    clear = labelSetMarkup label ""

errorHandler :: FilePath -> IOError -> IO ()
errorHandler file e = do
  when (isAlreadyInUseError e) $ (whenNormal . putStrLn) ("ERROR: file <" ++ file ++ "> is already opened and cannot be reopened.")
  when (isDoesNotExistError e) $ (whenNormal . putStrLn) ("ERROR: file <" ++ file ++ "> doesn't exist.")
  when (isPermissionError   e) $ (whenNormal . putStrLn) ("ERROR: user doesn't have permission to open file <" ++ file ++ ">.")


parseURIReference :: (MonadError HError m) => String -> m URI
parseURIReference uri = maybe (throwError $ InvalidURI uri) return $ N.parseURIReference uri

parseURI :: (MonadError HError m) => String -> m URI
parseURI uri = maybe (throwError $ InvalidURI uri) return $ N.parseURI uri

networkRequestGetUri :: (MonadIO m, MonadError HError m) => NetworkRequest -> m URI
networkRequestGetUri r = parseURIReference =<< maybe (throwError $ EmptyRequestURI r) return =<< io (W.networkRequestGetUri r)

downloadGetUri :: (MonadIO m, MonadError HError m) => W.Download -> m URI
downloadGetUri d               = parseURI =<< maybe (throwError $ EmptyDownloadURI d) return =<< io (W.downloadGetUri d)

downloadGetSuggestedFilename :: (MonadIO m, MonadError HError m) => W.Download -> m String
downloadGetSuggestedFilename d = maybe (throwError $ EmptySuggestedFileName d) return =<< io (W.downloadGetSuggestedFilename d)


-- Boolean types conversion
isCaseSensitive :: CaseSensitivity -> Bool
isCaseSensitive CaseSensitive = True
isCaseSensitive _             = False

isForward :: Direction -> Bool
isForward Forward = True
isForward _       = False

isWrapped :: Wrap -> Bool
isWrapped Wrap = True
isWrapped _    = False

-- Common pango attributes
allItalic, allBold :: PangoAttribute
allItalic = AttrStyle  {paStart = 0, paEnd = -1, paStyle  = StyleItalic}
allBold   = AttrWeight {paStart = 0, paEnd = -1, paWeight = WeightBold}



notify :: (Functor m, MonadIO m, MonadReader r m, HasNotificationBar r, HasHooks r, MonadError HError m) => Int -> String -> m ()
notify duration text = do
    label   <- _label <$> asks _notificationbar
    handler <- asks _notificationTimer

    io $ labelSetAttributes label [AttrForeground {paStart = 0, paEnd = -1, paColor = Color 32767 32767 32767}]
    io $ labelSetMarkup label text
    io $ mapM_ timeoutRemove =<< readIORef handler

    newID <- io $ timeoutAdd (labelSetMarkup label "" >> return False) duration
    io $ writeIORef handler $ Just newID

-- | Convert a Modifier to a String.
stringify :: Modifier -> String
stringify Control = "C-"
--stringify' Shift   = "S-"
stringify Alt     = "M-"
stringify _       = []