content: {-
      Copyright 2017 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/>.
  -}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE NumDecimals #-}
  {-|
  Module      : Monky.Examples.Wifi.Poll
  Description : The polling api for wifi information
  Maintainer  : ongy
  Stability   : experimental
  Portability : Linux
  
  This provides polling access to wifi information.
  For the event based version look at "Monky.Examples.Wifi.Event"
  -}
  module Monky.Examples.Wifi.Poll
      ( WifiFormat (..)
      , WifiPollHandle
      , Direction(..)
  
      , getWifiHandle
      , getWifiHandle'
  
      , guessWifiHandle
      , guessWifiHandle'
      )
  where
  
  import Data.Int (Int8)
  
  import Data.Maybe (fromMaybe)
  import Data.Word (Word8)
  import Formatting
  import Monky.Examples.Utility
  import qualified Monky.Examples.Wifi.Event as E
  import Monky.Modules
  import Monky.Wifi
  import System.Linux.Netlink.GeNetlink.NL80211
  import System.Linux.Netlink.GeNetlink.NL80211.StaInfo
  
  import Data.Text (Text)
  import qualified Data.Text as T
  
  import Control.Applicative ((<|>), (<$>), (<*>), pure)
  
  -- | The type for polling wifi information
  data WifiPollHandle = WH SSIDSocket Interface ((WifiStats, Maybe NL80211Packet) -> Text) Text
  
  -- TODO: Find out which of RX and TX is which from *our* side and document it
  -- | Helper type for WifiFormat to specify direction
  data Direction
      -- | Use TX information
      = DirTX
      -- | Use TX information
      | DirRX
      deriving (Show, Eq)
  
  -- | Enum-ish type for converting Wifi information to text
  data WifiFormat
      -- | The MCSIndex for our connection
      = FormatMCS Direction
      -- | The minimum MCSIndex for our connection
      | FormatMCSMin
      -- | The Signal width (in MHz)
      | FormatWifiWidth
      -- | The Bitrate of our connection
      | FormatBitrate Direction
      -- | Minimum of TX/RX Bitrate for this station
      | FormatBitrateMin
      -- | Signal strength from other source
      | FormatSignal
      -- | Signal strength average
      | FormatSignalAverage
  
      | FormatChannel -- ^Print the current networks channel
      | FormatName -- ^Print the ESSID of the current network, may look weird because SSIDs are
      | FormatFreq -- ^Print the frequency the current network sends on (related to channel)
      | FormatText Text -- ^Print a plaintext string
      deriving (Show, Eq)
  
  -- |Do the calculation for MBM
  -- This is taken from NetworkManager
  doMBM :: Int8 -> Word8
  doMBM e =
    let noiseFloor = -90
        signalMax  = -20
        clamped = min signalMax $ max noiseFloor $ e
     in fromIntegral $ 100 - (signalMax - clamped)
  
  getFromDir :: Direction -> StaInfo -> Maybe StaRate
  getFromDir DirTX = staTXRate
  getFromDir DirRX = staRXRate
  
  pollToEvt :: WifiFormat -> E.WifiFormat
  pollToEvt FormatChannel  = E.FormatChannel
  pollToEvt FormatName     = E.FormatName
  pollToEvt FormatFreq     = E.FormatFreq
  pollToEvt (FormatText t) = E.FormatText t
  pollToEvt x = error $ "Tried to convert " ++ show x ++ "to Evt? This really shouldn't ever happen"
  
  getExtFun :: WifiFormat -> (WifiStats, StaInfo) -> Text
  getExtFun (FormatMCS dir) (_, info) =
      case getFromDir dir info of
          Nothing -> "No Rate"
          Just x -> case rateMCS x <|> rateVHTMCS x of
              Nothing -> "No MCS"
              Just y -> sformat int y
  getExtFun FormatMCSMin (_, info) = fromMaybe "No Rate" $ do
      rx <- staRXRate info
      tx <- staTXRate info
      rmcs <- getMCS rx
      tmcs <- getMCS tx
      pure . sformat int $ min rmcs tmcs
      where getMCS x = rateMCS x <|> rateVHTMCS x
  getExtFun FormatWifiWidth (_, info) = -- Width is also in TXRate
      case staTXRate info of
          Nothing -> "No Rate"
          Just x -> case rateWidthFlag x of
              Width5MHz -> "5MHz"
              Width10MHz -> "10MHz"
              Width20MHz -> "20MHz"
              Width40MHz -> "40MHz"
              Width80MHz -> "80MHz"
              Width160MHz -> "160MHz"
  getExtFun (FormatBitrate dir) (_, info) = -- Bitrate is in TXRate
      case getFromDir dir info of
          Nothing -> "No Rate"
          Just x -> maybe
              "No Bitrate"
              (flip convertUnitSI "b" . (* 1e5))
              (rateBitrate x)
  getExtFun FormatBitrateMin (_, info) = -- Bitrate from RX/TX Rate
      let tx = rateBitrate =<< staTXRate info
          rx = rateBitrate =<< staRXRate info
      in case min <$> rx <*> tx of
          Nothing -> "No rates"
          Just x -> convertUnitSI (x * 1e5) "b"
  getExtFun FormatSignal (_, info) =
      case staSignalMBM info of
          Nothing -> "No strength"
          Just x -> sformat int . doMBM $ fromIntegral x
  getExtFun FormatSignalAverage (_, info) =
      case staSignalMBMA info of
          Nothing -> "No strength"
          Just x -> sformat int . doMBM $ fromIntegral x
  getExtFun x (stats, _) = E.getTextify (pollToEvt x) stats
  
  getExtFunction :: [WifiFormat] -> (WifiStats, StaInfo) -> Text
  getExtFunction xs = T.concat . (sequence . map getExtFun $ xs)
  
  {- |This function is the easiest, but also a bit limited way to get extended wifi information.
  
  With this, all "normal" information will be first, and the extended information will be appended.
  -}
  getCombiFun :: [WifiFormat] -> ((WifiStats, Maybe NL80211Packet) -> Text)
  getCombiFun xs (stat, ext) =
      let fun = getExtFunction xs
          info = staInfoFromPacket =<< ext
       in case info of
              Just x -> fun (stat, x)
              Nothing -> "Couldn't get wifi station info"
  
  instance PollModule WifiPollHandle where
    getOutput (WH s i f d) = do
      ret <- getCurrentWifiStats s i
      case ret of
          Nothing -> pure . pure $ MonkyPlain d
          Just x -> do
              ext <- getExtendedWifi s i x
              pure . pure . MonkyPlain $ f (x, ext)
  
  -- | Lower level version of 'getWifiHandle' for more level of control
  getWifiHandle'
      :: ((WifiStats, Maybe NL80211Packet) -> Text)
      -> Text
      -> String
      -> IO WifiPollHandle
  getWifiHandle' f d n = do
      s <- getSSIDSocket
      i <- fromMaybe (error ("Could not find interface: " ++ n)) <$> getInterface s n
      return (WH s i f d)
  
  -- |Get a wifi handle
  getWifiHandle
    :: [WifiFormat] -- ^Format "String" for output generation
    -> Text -- ^Text that should be displayed when wifi is disconnected
    -> String -- ^Name of the interface
    -> IO WifiPollHandle
  getWifiHandle f d n =
    getWifiHandle' (getCombiFun f) d n
  
  -- | Lower level version of 'guessWifiHandle' for more control
  guessWifiHandle'
      :: ((WifiStats, Maybe NL80211Packet) -> Text)
      -> Text -- ^Text that should be displayed when wifi is disconnected
      -> IO WifiPollHandle
  guessWifiHandle' f d = do
      s <- getSSIDSocket
      i <- fromMaybe (error "Couldn't find any NL80211 interface") <$> guessInterface s
      return (WH s i f d)
  
  {- | Get a wifi handle, guess the interface
  
  Guess isn't quite the right word here. This asks the NL80211 subsystem for a
  list of devices and picks the first one.
  -}
  guessWifiHandle
      :: [WifiFormat] -- ^Format "String" for output generation
      -> Text -- ^Text that should be displayed when wifi is disconnected
      -> IO WifiPollHandle
  guessWifiHandle f d =
      guessWifiHandle' (getCombiFun f) d
  