{-# LANGUAGE CPP                       #-}
{-# LANGUAGE ForeignFunctionInterface  #-}
{-# LANGUAGE GHCForeignImportPrim      #-}
{-# LANGUAGE JavaScriptFFI             #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedStrings         #-}
{-# LANGUAGE ScopedTypeVariables       #-}

module Network.Top.Util(
  -- *Exceptions
  strictTry,try,tryE,forceE,SomeException

  -- *Time
  ,milliseconds,seconds,minutes
  ,withTimeout

  -- *Threads
  ,async,cancel,threadDelay

  -- *Monads
  ,liftIO,forever,when,unless

  -- *Logging (with native ghcjs support)
  ,dbg,warn,info,err,dbgS,logLevel

#ifdef ghcjs_HOST_OS
  ,Priority(..)
#else
  ,logLevelOut
  ,module X
#endif

  -- *Other
  ,eitherToMaybe


) where

import           Control.Concurrent       (threadDelay)
import           Control.Concurrent.Async (async, cancel)
import           Control.DeepSeq
import           Control.Exception        (SomeException, try)
import qualified Control.Exception        as E
import           Control.Monad
import           Control.Monad.IO.Class
import           GHC.IO.Handle            (Handle)
import           System.Timeout

---------- Logging

#ifdef ghcjs_HOST_OS

------------ GHC-JS Version
import Data.IORef
import System.IO.Unsafe
import qualified Data.JSString as S

foreign import javascript unsafe "console.log($1)" clog :: S.JSString -> IO ()

foreign import javascript unsafe "console.info($1)" cinfo :: S.JSString -> IO ()

foreign import javascript unsafe "console.warn($1)" cwarn :: S.JSString -> IO ()

foreign import javascript unsafe "console.error($1)" cerr :: S.JSString -> IO ()

data Priority = DEBUG | INFO | WARNING | ERROR deriving (Show,Eq,Ord)

gLogLevel :: IORef Priority
{-# NOINLINE gLogLevel #-}
gLogLevel = unsafePerformIO (newIORef DEBUG)

logLevel :: Priority -> IO ()
dbgS :: String -> IO ()
dbg :: [String] -> IO ()
info :: [String] -> IO ()
warn :: [String] -> IO ()
err :: [String] -> IO ()

logLevel = writeIORef gLogLevel

dbgS s = do
  l <- readIORef gLogLevel
  when (l == DEBUG) $ clog . S.pack $ s

dbg = dbgS . unwords

info ss =  do
  l <- readIORef gLogLevel
  when (l <=INFO) $ cinfo . S.pack . unwords $ ss

warn ss = do
  l <- readIORef gLogLevel
  when (l <=WARNING) $ cwarn . S.pack . unwords $ ss

err = cerr . S.pack . unwords

#else
------------ GHC Version
import           System.Log.Handler.Simple (verboseStreamHandler)
import           System.Log.Logger         as X

-- |Setup the global logging level
logLevel :: Priority -> IO ()
logLevel = updateGlobalLogger rootLoggerName . setLevel

logLevelOut :: Priority -> Handle -> IO ()
logLevelOut level handle = do
  out <- verboseStreamHandler handle level
  updateGlobalLogger rootLoggerName (setHandlers [out] . setLevel level)

-- |Log a message at DEBUG level
dbgS :: String -> IO ()
dbgS = debugM "top"

-- |Log multiple messages at DEBUG level
dbg :: MonadIO m => [String] -> m ()
dbg = liftIO . dbgS . unwords

-- |Log multiple messages at INFO level
info :: MonadIO m => [String] -> m ()
info = liftIO . infoM "top" . unwords

-- |Log multiple messages at WARNING level
warn :: MonadIO m => [String] -> m ()
warn = liftIO . warningM "top" . unwords

-- |Log multiple messages at ERROR level
err :: MonadIO m => [String] -> m ()
err = liftIO . errorM "top" . unwords

#endif

---------- Exceptions

-- |forceE == either error id
forceE :: Either String c -> c
forceE = either error id -- throwIO

-- |Like `try` but with returned exception fixed to `SomeException`
tryE :: IO a -> IO (Either SomeException a)
tryE = try

-- |Strict try, `deepseq`s the returned value
strictTry :: NFData a => IO a -> IO (Either E.SomeException a)
strictTry op = E.catch (op >>= \v -> return . Right $! deepseq v v) (\(err:: E.SomeException) -> return . Left $ err)

-- |Run an IO op with a timeout
withTimeout :: Int                  -- ^Timeout (in seconds)
            -> IO a                 -- ^Op to execute
            -> IO (Either String a) -- ^Right if op completed correctly, Left otherwise
-- withTimeout secs op = maybe (Left "Timeout") Right <$> timeout (seconds secs) op
withTimeout secs op = do
  em <- try $ timeout (seconds secs) op
  return $ case em of
    Left (e::SomeException) -> Left (show e)
    Right m -> maybe (Left "Timeout") Right m

-- |Convert an Either to a Maybe
eitherToMaybe :: Either t a -> Maybe a
eitherToMaybe (Right a) = Just a
eitherToMaybe (Left _) = Nothing

---------- Time

-- |Convert minutes to microseconds (μs)
minutes :: Num c => c -> c
minutes = seconds . (60*)

-- |Convert seconds to microseconds (μs)
seconds :: Num a => a -> a
seconds = (* 1000000)

-- |Convert milliseconds to microseconds (μs)
milliseconds :: Num a => a -> a
milliseconds = (* 1000)