{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}

-- | This module provides toml configuration for the battery percentage service.
module Navi.Services.Battery.Percentage.Toml
  ( BatteryPercentageToml (..),
    BatteryPercentageNoteToml (..),
    PercentageData (..),
  )
where

import DBus.Notify (UrgencyLevel)
import Data.List.NonEmpty qualified as NE
import Data.Set (Set)
import Data.Set qualified as Set
import Data.Text qualified as T
import Navi.Data.NaviNote (Timeout, timeoutOptDecoder)
import Navi.Data.PollInterval (PollInterval, pollIntervalOptDecoder)
import Navi.Event.Toml
  ( ErrorNoteToml,
    MultiRepeatEventToml (MultiSomeRepeatsToml),
    errorNoteOptDecoder,
    multiRepeatEventOptDecoder,
  )
import Navi.Prelude
import Navi.Services.Battery.Common (batteryAppDecoder)
import Navi.Utils (urgencyLevelOptDecoder)
import Pythia.Data.Percentage (mkPercentage, _MkPercentage)
import Pythia.Data.Percentage qualified as Percentage
import Pythia.Services.Battery (BatteryApp, Percentage)

-- | Exact percentage or range. Equality/Ord is based on the _lower_ part of
-- the range only. This allows us to have a total order and make lookups
-- sensible.
data PercentageData
  = -- | Exact percentage.
    PercentageExact Percentage
  | -- | Percentage range. The LHS should be <= RHS.
    PercentageRange Percentage Percentage
  deriving stock (Int -> PercentageData -> ShowS
[PercentageData] -> ShowS
PercentageData -> String
(Int -> PercentageData -> ShowS)
-> (PercentageData -> String)
-> ([PercentageData] -> ShowS)
-> Show PercentageData
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> PercentageData -> ShowS
showsPrec :: Int -> PercentageData -> ShowS
$cshow :: PercentageData -> String
show :: PercentageData -> String
$cshowList :: [PercentageData] -> ShowS
showList :: [PercentageData] -> ShowS
Show)

instance Eq PercentageData where
  PercentageData
x == :: PercentageData -> PercentageData -> Bool
== PercentageData
y = PercentageData -> Percentage
toPercentage PercentageData
x Percentage -> Percentage -> Bool
forall a. Eq a => a -> a -> Bool
== PercentageData -> Percentage
toPercentage PercentageData
y

instance Ord PercentageData where
  PercentageData
x <= :: PercentageData -> PercentageData -> Bool
<= PercentageData
y = PercentageData -> Percentage
toPercentage PercentageData
x Percentage -> Percentage -> Bool
forall a. Ord a => a -> a -> Bool
<= PercentageData -> Percentage
toPercentage PercentageData
y

toPercentage :: PercentageData -> Percentage
toPercentage :: PercentageData -> Percentage
toPercentage (PercentageExact Percentage
p) = Percentage
p
toPercentage (PercentageRange Percentage
l Percentage
_) = Percentage
l

-- | TOML for each individual battery percentage.
data BatteryPercentageNoteToml = MkBatteryPercentageNoteToml
  { -- | The percentage (range) for this alert.
    BatteryPercentageNoteToml -> PercentageData
percentage :: PercentageData,
    -- | The urgency for this alert.
    BatteryPercentageNoteToml -> Maybe UrgencyLevel
urgency :: Maybe UrgencyLevel,
    -- | The timeout for this alert.
    BatteryPercentageNoteToml -> Maybe Timeout
mTimeout :: Maybe Timeout
  }
  deriving stock (BatteryPercentageNoteToml -> BatteryPercentageNoteToml -> Bool
(BatteryPercentageNoteToml -> BatteryPercentageNoteToml -> Bool)
-> (BatteryPercentageNoteToml -> BatteryPercentageNoteToml -> Bool)
-> Eq BatteryPercentageNoteToml
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: BatteryPercentageNoteToml -> BatteryPercentageNoteToml -> Bool
== :: BatteryPercentageNoteToml -> BatteryPercentageNoteToml -> Bool
$c/= :: BatteryPercentageNoteToml -> BatteryPercentageNoteToml -> Bool
/= :: BatteryPercentageNoteToml -> BatteryPercentageNoteToml -> Bool
Eq, Int -> BatteryPercentageNoteToml -> ShowS
[BatteryPercentageNoteToml] -> ShowS
BatteryPercentageNoteToml -> String
(Int -> BatteryPercentageNoteToml -> ShowS)
-> (BatteryPercentageNoteToml -> String)
-> ([BatteryPercentageNoteToml] -> ShowS)
-> Show BatteryPercentageNoteToml
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> BatteryPercentageNoteToml -> ShowS
showsPrec :: Int -> BatteryPercentageNoteToml -> ShowS
$cshow :: BatteryPercentageNoteToml -> String
show :: BatteryPercentageNoteToml -> String
$cshowList :: [BatteryPercentageNoteToml] -> ShowS
showList :: [BatteryPercentageNoteToml] -> ShowS
Show)

makeFieldLabelsNoPrefix ''BatteryPercentageNoteToml

-- | @since 0.1
instance DecodeTOML BatteryPercentageNoteToml where
  tomlDecoder :: Decoder BatteryPercentageNoteToml
tomlDecoder = do
    PercentageData
percentage <- Decoder PercentageData
percentageDataDecoder
    Maybe Timeout
mTimeout <- Decoder (Maybe Timeout)
timeoutOptDecoder
    Maybe UrgencyLevel
urgency <- Decoder (Maybe UrgencyLevel)
urgencyLevelOptDecoder
    pure
      $ MkBatteryPercentageNoteToml
        { PercentageData
percentage :: PercentageData
percentage :: PercentageData
percentage,
          Maybe Timeout
mTimeout :: Maybe Timeout
mTimeout :: Maybe Timeout
mTimeout,
          Maybe UrgencyLevel
urgency :: Maybe UrgencyLevel
urgency :: Maybe UrgencyLevel
urgency
        }

percentageDataDecoder :: Decoder PercentageData
percentageDataDecoder :: Decoder PercentageData
percentageDataDecoder = do
  Maybe PercentageData
mExact <- Decoder (Maybe PercentageData)
exactDecoder
  Maybe PercentageData
mRange <- Decoder (Maybe PercentageData)
rangeDecoder
  case (Maybe PercentageData
mExact, Maybe PercentageData
mRange) of
    (Just PercentageData
exact, Maybe PercentageData
Nothing) -> PercentageData -> Decoder PercentageData
forall a. a -> Decoder a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure PercentageData
exact
    (Maybe PercentageData
Nothing, Just PercentageData
range) -> PercentageData -> Decoder PercentageData
forall a. a -> Decoder a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure PercentageData
range
    (Just PercentageData
_, Just PercentageData
_) -> String -> Decoder PercentageData
forall a. String -> Decoder a
forall (m :: Type -> Type) a. MonadFail m => String -> m a
fail String
"Expected 'percent' or ('lower' and 'upper'), not both!"
    (Maybe PercentageData
Nothing, Maybe PercentageData
Nothing) -> String -> Decoder PercentageData
forall a. String -> Decoder a
forall (m :: Type -> Type) a. MonadFail m => String -> m a
fail String
"Expected 'percent' or ('lower' and 'upper'), received neither!"

exactDecoder :: Decoder (Maybe PercentageData)
exactDecoder :: Decoder (Maybe PercentageData)
exactDecoder = (Percentage -> PercentageData)
-> Maybe Percentage -> Maybe PercentageData
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
fmap Percentage -> PercentageData
PercentageExact (Maybe Percentage -> Maybe PercentageData)
-> Decoder (Maybe Percentage) -> Decoder (Maybe PercentageData)
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
<$> Decoder Percentage -> Text -> Decoder (Maybe Percentage)
forall a. Decoder a -> Text -> Decoder (Maybe a)
getFieldOptWith Decoder Percentage
percentageDecoder Text
"percent"

rangeDecoder :: Decoder (Maybe PercentageData)
rangeDecoder :: Decoder (Maybe PercentageData)
rangeDecoder = do
  Maybe Percentage
mLow <- Decoder Percentage -> Text -> Decoder (Maybe Percentage)
forall a. Decoder a -> Text -> Decoder (Maybe a)
getFieldOptWith Decoder Percentage
percentageDecoder Text
"lower"
  Maybe Percentage
mHigh <- Decoder Percentage -> Text -> Decoder (Maybe Percentage)
forall a. Decoder a -> Text -> Decoder (Maybe a)
getFieldOptWith Decoder Percentage
percentageDecoder Text
"upper"

  case (Maybe Percentage
mLow, Maybe Percentage
mHigh) of
    (Just Percentage
low, Just Percentage
high) -> do
      let msg :: String
msg =
            Text -> String
unpackText
              (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ [Text] -> Text
forall a. Monoid a => [a] -> a
mconcat
                [ Text
"Percentage 'upper = ",
                  Percentage -> Text
forall a. Display a => a -> Text
display Percentage
high,
                  Text
"' < 'lower = ",
                  Percentage -> Text
forall a. Display a => a -> Text
display Percentage
low,
                  Text
"'."
                ]
      Bool -> Decoder () -> Decoder ()
forall (f :: Type -> Type). Applicative f => Bool -> f () -> f ()
when (Percentage
high Percentage -> Percentage -> Bool
forall a. Ord a => a -> a -> Bool
< Percentage
low) (Decoder () -> Decoder ()) -> Decoder () -> Decoder ()
forall a b. (a -> b) -> a -> b
$ String -> Decoder ()
forall a. String -> Decoder a
forall (m :: Type -> Type) a. MonadFail m => String -> m a
fail String
msg

      pure $ PercentageData -> Maybe PercentageData
forall a. a -> Maybe a
Just (PercentageData -> Maybe PercentageData)
-> PercentageData -> Maybe PercentageData
forall a b. (a -> b) -> a -> b
$ Percentage -> Percentage -> PercentageData
PercentageRange Percentage
low Percentage
high
    (Maybe Percentage, Maybe Percentage)
_ -> Maybe PercentageData -> Decoder (Maybe PercentageData)
forall a. a -> Decoder a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure Maybe PercentageData
forall a. Maybe a
Nothing

percentageDecoder :: Decoder Percentage
percentageDecoder :: Decoder Percentage
percentageDecoder =
  Decoder Word8
forall a. DecodeTOML a => Decoder a
tomlDecoder Decoder Word8
-> (Word8 -> Decoder Percentage) -> Decoder Percentage
forall a b. Decoder a -> (a -> Decoder b) -> Decoder b
forall (m :: Type -> Type) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Word8
x ->
    case Word8 -> Maybe Percentage
Percentage.mkPercentage Word8
x of
      Just Percentage
n -> Percentage -> Decoder Percentage
forall a. a -> Decoder a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure Percentage
n
      Maybe Percentage
Nothing ->
        String -> Decoder Percentage
forall a. String -> Decoder a
forall (m :: Type -> Type) a. MonadFail m => String -> m a
fail
          (String -> Decoder Percentage) -> String -> Decoder Percentage
forall a b. (a -> b) -> a -> b
$ Text -> String
unpackText
          (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ [Text] -> Text
forall a. Monoid a => [a] -> a
mconcat
            [ Text
"Unexpected percent: ",
              Word8 -> Text
forall a. Show a => a -> Text
showt Word8
x,
              Text
". Expected integer in [0, 100]."
            ]

-- | TOML for the battery percentage service.
data BatteryPercentageToml = MkBatteryPercentageToml
  { -- | All alerts for this service.
    BatteryPercentageToml -> NonEmpty BatteryPercentageNoteToml
alerts :: NonEmpty BatteryPercentageNoteToml,
    -- | Determines how we should query the system for battery information.
    BatteryPercentageToml -> BatteryApp
app :: BatteryApp,
    -- | Determines how we handle errors.
    BatteryPercentageToml -> Maybe ErrorNoteToml
errorNote :: Maybe ErrorNoteToml,
    -- | The poll interval.
    BatteryPercentageToml -> Maybe PollInterval
pollInterval :: Maybe PollInterval,
    -- | Determines how we treat repeat alerts. We are given exact percentages,
    -- so if the user wants a repeatEvent to apply to a range, they should
    -- give the _lower_ bound.
    BatteryPercentageToml -> Maybe (MultiRepeatEventToml Percentage)
repeatEvent :: Maybe (MultiRepeatEventToml Percentage)
  }
  deriving stock (BatteryPercentageToml -> BatteryPercentageToml -> Bool
(BatteryPercentageToml -> BatteryPercentageToml -> Bool)
-> (BatteryPercentageToml -> BatteryPercentageToml -> Bool)
-> Eq BatteryPercentageToml
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: BatteryPercentageToml -> BatteryPercentageToml -> Bool
== :: BatteryPercentageToml -> BatteryPercentageToml -> Bool
$c/= :: BatteryPercentageToml -> BatteryPercentageToml -> Bool
/= :: BatteryPercentageToml -> BatteryPercentageToml -> Bool
Eq, Int -> BatteryPercentageToml -> ShowS
[BatteryPercentageToml] -> ShowS
BatteryPercentageToml -> String
(Int -> BatteryPercentageToml -> ShowS)
-> (BatteryPercentageToml -> String)
-> ([BatteryPercentageToml] -> ShowS)
-> Show BatteryPercentageToml
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> BatteryPercentageToml -> ShowS
showsPrec :: Int -> BatteryPercentageToml -> ShowS
$cshow :: BatteryPercentageToml -> String
show :: BatteryPercentageToml -> String
$cshowList :: [BatteryPercentageToml] -> ShowS
showList :: [BatteryPercentageToml] -> ShowS
Show)

makeFieldLabelsNoPrefix ''BatteryPercentageToml

-- | @since 0.1
instance DecodeTOML BatteryPercentageToml where
  tomlDecoder :: Decoder BatteryPercentageToml
tomlDecoder = do
    NonEmpty BatteryPercentageNoteToml
alerts <- Decoder (NonEmpty BatteryPercentageNoteToml)
-> Text -> Decoder (NonEmpty BatteryPercentageNoteToml)
forall a. Decoder a -> Text -> Decoder a
getFieldWith Decoder (NonEmpty BatteryPercentageNoteToml)
forall a. DecodeTOML a => Decoder a
tomlDecoder Text
"alert"
    BatteryApp
app <- Decoder BatteryApp -> Text -> Decoder BatteryApp
forall a. Decoder a -> Text -> Decoder a
getFieldWith Decoder BatteryApp
batteryAppDecoder Text
"app"
    Maybe ErrorNoteToml
errorNote <- Decoder (Maybe ErrorNoteToml)
errorNoteOptDecoder
    Maybe PollInterval
pollInterval <- Decoder (Maybe PollInterval)
pollIntervalOptDecoder
    Maybe (MultiRepeatEventToml Percentage)
repeatEvent <- (Value -> DecodeM Percentage)
-> Decoder (Maybe (MultiRepeatEventToml Percentage))
forall a.
Ord a =>
(Value -> DecodeM a) -> Decoder (Maybe (MultiRepeatEventToml a))
multiRepeatEventOptDecoder Value -> DecodeM Percentage
decodePercentage

    case Maybe (MultiRepeatEventToml Percentage)
repeatEvent of
      Just (MultiSomeRepeatsToml Set Percentage
percentRefs) -> do
        let alertsPercents :: Set Percentage
alertsPercents = NonEmpty BatteryPercentageNoteToml -> Set Percentage
mkAlertPercents NonEmpty BatteryPercentageNoteToml
alerts
            d :: Set Percentage
d = Set Percentage -> Set Percentage -> Set Percentage
forall a. Ord a => Set a -> Set a -> Set a
Set.difference Set Percentage
percentRefs Set Percentage
alertsPercents
            msg :: String
msg =
              [String] -> String
forall a. Monoid a => [a] -> a
mconcat
                [ String
"Found repeat-events that referenced non-extant alert ",
                  String
"percentages. All references should correspond to an alert ",
                  String
"'percent' or 'lower': ",
                  Set Percentage -> String
showSet Set Percentage
d
                ]
        Bool -> Decoder () -> Decoder ()
forall (f :: Type -> Type). Applicative f => Bool -> f () -> f ()
unless (Set Percentage -> Bool
forall a. Set a -> Bool
Set.null Set Percentage
d) (Decoder () -> Decoder ()) -> Decoder () -> Decoder ()
forall a b. (a -> b) -> a -> b
$ String -> Decoder ()
forall a. String -> Decoder a
forall (m :: Type -> Type) a. MonadFail m => String -> m a
fail String
msg
      Maybe (MultiRepeatEventToml Percentage)
_ -> () -> Decoder ()
forall a. a -> Decoder a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure ()

    pure
      $ MkBatteryPercentageToml
        { NonEmpty BatteryPercentageNoteToml
alerts :: NonEmpty BatteryPercentageNoteToml
alerts :: NonEmpty BatteryPercentageNoteToml
alerts,
          BatteryApp
app :: BatteryApp
app :: BatteryApp
app,
          Maybe ErrorNoteToml
errorNote :: Maybe ErrorNoteToml
errorNote :: Maybe ErrorNoteToml
errorNote,
          Maybe PollInterval
pollInterval :: Maybe PollInterval
pollInterval :: Maybe PollInterval
pollInterval,
          Maybe (MultiRepeatEventToml Percentage)
repeatEvent :: Maybe (MultiRepeatEventToml Percentage)
repeatEvent :: Maybe (MultiRepeatEventToml Percentage)
repeatEvent
        }
    where
      decodePercentage :: Value -> DecodeM Percentage
decodePercentage (Integer Integer
s) = case Word8 -> Maybe Percentage
mkPercentage (Integer -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral Integer
s) of
        Just Percentage
p -> Percentage -> DecodeM Percentage
forall a. a -> DecodeM a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure Percentage
p
        Maybe Percentage
Nothing -> String -> DecodeM Percentage
forall a. String -> DecodeM a
forall (m :: Type -> Type) a. MonadFail m => String -> m a
fail (String -> DecodeM Percentage) -> String -> DecodeM Percentage
forall a b. (a -> b) -> a -> b
$ String
"Failed to parse percentage: " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Integer -> String
forall a. Show a => a -> String
show Integer
s
      decodePercentage Value
other = Value -> DecodeM Percentage
forall a. Value -> DecodeM a
typeMismatch Value
other

      mkAlertPercents :: NonEmpty BatteryPercentageNoteToml -> Set Percentage
      mkAlertPercents :: NonEmpty BatteryPercentageNoteToml -> Set Percentage
mkAlertPercents =
        [Percentage] -> Set Percentage
forall a. Ord a => [a] -> Set a
Set.fromList
          ([Percentage] -> Set Percentage)
-> (NonEmpty BatteryPercentageNoteToml -> [Percentage])
-> NonEmpty BatteryPercentageNoteToml
-> Set Percentage
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. NonEmpty Percentage -> [Percentage]
forall a. NonEmpty a -> [a]
NE.toList
          (NonEmpty Percentage -> [Percentage])
-> (NonEmpty BatteryPercentageNoteToml -> NonEmpty Percentage)
-> NonEmpty BatteryPercentageNoteToml
-> [Percentage]
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. (BatteryPercentageNoteToml -> Percentage)
-> NonEmpty BatteryPercentageNoteToml -> NonEmpty Percentage
forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
fmap (PercentageData -> Percentage
toPercentage (PercentageData -> Percentage)
-> (BatteryPercentageNoteToml -> PercentageData)
-> BatteryPercentageNoteToml
-> Percentage
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Optic' A_Lens NoIx BatteryPercentageNoteToml PercentageData
-> BatteryPercentageNoteToml -> PercentageData
forall k (is :: IxList) s a.
Is k A_Getter =>
Optic' k is s a -> s -> a
view Optic' A_Lens NoIx BatteryPercentageNoteToml PercentageData
#percentage)

      showSet :: Set Percentage -> String
      showSet :: Set Percentage -> String
showSet =
        Text -> String
unpackText
          (Text -> String)
-> (Set Percentage -> Text) -> Set Percentage -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. (Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".")
          (Text -> Text)
-> (Set Percentage -> Text) -> Set Percentage -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Text -> [Text] -> Text
T.intercalate Text
", "
          ([Text] -> Text)
-> (Set Percentage -> [Text]) -> Set Percentage -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. (Percentage -> Text) -> [Percentage] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
fmap (Word8 -> Text
forall a. Show a => a -> Text
showt (Word8 -> Text) -> (Percentage -> Word8) -> Percentage -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Optic' A_ReversedPrism NoIx Percentage Word8 -> Percentage -> Word8
forall k (is :: IxList) s a.
Is k A_Getter =>
Optic' k is s a -> s -> a
view Optic' A_ReversedPrism NoIx Percentage Word8
_MkPercentage)
          ([Percentage] -> [Text])
-> (Set Percentage -> [Percentage]) -> Set Percentage -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
forall {k} (cat :: k -> k -> Type) (b :: k) (c :: k) (a :: k).
Category cat =>
cat b c -> cat a b -> cat a c
. Set Percentage -> [Percentage]
forall a. Set a -> [a]
Set.toList