{-
    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
