{-
    Copyright 2016 Markus Ongyerth

    This file is part of Monky.

    Monky is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Monky is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Monky.  If not, see <http://www.gnu.org/licenses/>.
-}
{-|
Module      : Monky.Network.Static
Description : Allows access to information about they systems network
Maintainer  : ongy
Stability   : testing
Portability : Linux

This module should be used for static network configurations
-}
module Monky.Network.Static
  ( getReadWrite
  , NetworkHandle
  , getNetworkHandle
  , closeNetworkHandle
  )
where

import Monky.Utility
import Data.Time.Clock.POSIX
import Data.IORef
import System.IO.Error (catchIOError)


-- |Current state of network device
data NetState
  = Down -- ^It is down, consider off
  | Up -- ^It is up, consider on
  | Unknown -- ^Unknown, kernel docu says to consider on
  | Dormant -- ^Dormant, consider off

-- |Get the current network adapter state from kernel
getState :: NetworkHandle -> IO NetState
getState (NetH _ _ statef _ _ _) = do
-- the read can throw an exception if the interface disapperad, we just consider it down
  state <- catchIOError (readLine statef) (\_ -> return "down")
  return $ case state of
    "up" -> Up
    "down" -> Down
    "unknown" -> Unknown
    "dormant" -> Dormant
    _ -> error ("Don't know the network state \"" ++ state ++ "\" yet")


-- |Internal handle represanting exactly one interface
data NetworkHandle = NetH File File File (IORef Int) (IORef Int) (IORef POSIXTime)

basePath :: String
basePath = "/sys/class/net/"


readPath :: String
readPath = "/statistics/rx_bytes"


writePath :: String
writePath = "/statistics/tx_bytes"


statePath :: String
statePath = "/operstate"

-- Pure part of read/write rate calculation
calculateRates :: Int -> Int -> Int -> Int -> POSIXTime -> POSIXTime -> (Int, Int)
calculateRates oread nread owrite nwrite otime ntime =
  let cread = oread - nread
      cwrite = owrite - nwrite
      ctime = otime - ntime in
    ((cread * 8) `sdivBound` round ctime,
     (cwrite * 8) `sdivBound` round ctime)

-- IO Part of read/write rate calculation
getReadWriteReal :: NetworkHandle -> IO (Int, Int)
getReadWriteReal (NetH readf writef _ readref writeref timeref) = do
  nread <- readValue readf
  nwrite <- readValue writef
  ntime <- getPOSIXTime

  oread <- readIORef readref
  owrite <- readIORef writeref
  otime <- readIORef timeref

  writeIORef readref nread
  writeIORef writeref nwrite
  writeIORef timeref ntime

  return $ calculateRates oread nread owrite nwrite otime ntime

-- |Get the (read, write) rate of the network interface. This is averaged over the time between calls
getReadWrite :: NetworkHandle -> IO (Maybe (Int, Int))
getReadWrite h = do
  state <- getState h
  case state of
    Up -> fmap Just . getReadWriteReal $ h
    Unknown -> fmap Just . getReadWriteReal $ h
    _ -> return Nothing

-- |Get a 'NetworkHandle'
getNetworkHandle
  :: String -- ^Name of the interface to monitor
  -> IO NetworkHandle
getNetworkHandle dev = do
  readf <- fopen $path ++ readPath
  writef <- fopen $path ++ writePath
  statef <- fopen $path ++ statePath
  readref <- newIORef (1 :: Int)
  writeref <- newIORef (1 :: Int)
  timeref <- newIORef (0 :: POSIXTime)
  return $NetH readf writef statef readref writeref timeref
  where path = basePath ++ dev

-- |Close a network handle after it is no longer needed (the device disappeared)
closeNetworkHandle :: NetworkHandle -> IO ()
closeNetworkHandle (NetH readf writef statef _ _ _) =
  mapM_ fclose [readf, writef, statef]