{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}

-----------------------------------------------------------------------------

-----------------------------------------------------------------------------

{- |
Module      :  Plugins.Monitors.PacmanUpdates
Copyright   :  (c) 2024 Enrico Maria De Angelis
            ,  (c) 2025 Alexander Pankoff
            ,  (c) 2026 Enrico Maria De Angelis
License     :  BSD-style (see LICENSE)

Maintainer  :  Enrico Maria De Angelis <enricomaria.dean6elis@gmail.com>
Stability   :  unstable
Portability :  unportable

A Pacman updates availablility plugin for Xmobar. It also informs whether a kernel update is
available (provided the name of the kernel package), and whether the running kernel is older
than the installed one.
-}
module Xmobar.Plugins.PacmanUpdates (
    {-# DEPRECATED "This ctor is DEPRECATED;\
 please use `PacmanUpdates` type and `PacmanUpdatesK`\
 and `PacmanUpdatesNoK` constructors instead." #-}
    pattern PacmanUpdates
  , PacmanUpdates ()
  , pattern PacmanUpdatesK
  , pattern PacmanUpdatesNoK) where

import System.Exit (ExitCode (..))
import System.Process (readProcessWithExitCode)
import Xmobar.Plugins.Command (Rate)
import Xmobar.Run.Exec
import Data.Tuple.Extra (fst3)
import Data.Kind (Type)
import Data.Functor ((<&>))
import Data.Void (Void)
import Control.Arrow ((&&&))
import qualified Data.Vector as V

-- | Deprecated plugin ctor (will be deleted in 2027).
-- Use `PacmanUpdatesK` or `PacmanUpdatesNoK` instead.
pattern PacmanUpdates :: (String, String, String, String) -- ^ `String`s to be shown for 0, 1, ≥ 2  updates,
                                                          -- and for error respectively (in the 3rd string, for
                                                          -- ≥ 2 updates, any occurrence of the '?' character
                                                          -- is a placeholder for the number of available updates).
                      -> Rate -- ^ `Rate` of update (see [Xmobar doc](https://codeberg.org/xmobar/xmobar/src/commit/39fd70308c3aef5402abe7152ade76ff7bb331bb/src/Xmobar/Plugins/Command.hs#L34)).
                      -> PacmanUpdates False
pattern PacmanUpdates irrelevant <- (error "PacmanUpdates: PacmanUpdates is a build-only pattern synonym (a ctor synonym)." -> irrelevant)
  where PacmanUpdates zome r
          = let (z, o, m, e) = zome
                printer = const
                        $ (++ deprecationNote)
                        . \case Left _ -> e
                                Right 0 -> z
                                Right 1 -> o
                                Right n -> m >>= \c -> if c == '?'
                                                            then show n
                                                            else pure c
                deprecationNote = " <fc=#ff0000>(<action=`xdg-open https://codeberg.org/xmobar/xmobar/pulls/765`>"
                                 ++ "deprecated plugin, click here</action>)</fc>"
            in PacmanUpdatesNoK r printer

-- | PacmanUpdates plugin parametrized over `Bool` kind: if `True` the plugin
-- will detect if there's pending update(s) for the kernel package; if `False`
-- it wont.
data PacmanUpdates (b :: Bool)
  = Make -- ^ Constructor.
         Rate -- ^ `Rate` of update (see [Xmobar doc](https://codeberg.org/xmobar/xmobar/src/commit/39fd70308c3aef5402abe7152ade76ff7bb331bb/src/Xmobar/Plugins/Command.hs#L34)).
         (Arg b) -- ^ Optional further argument. See instances of `Updates`.
         (Printer b) -- ^ Printer. See instances of `Updates` for its signature.

instance Show (PacmanUpdates b) where
  show = error "PacmanUpdates: Show instance is stub"

instance Read (PacmanUpdates b) where
  readsPrec = error "PacmanUpdates: Read instance is stub"

instance Updates b => Exec (PacmanUpdates (b :: Bool)) where
  alias = const "pacman"
  rate (Make r _ _) = r
  run = Xmobar.Plugins.PacmanUpdates.run'

class Updates (b :: Bool) where
  -- | See `Updates`'s instances.
  type Arg b = (a :: Type) | a -> b
  -- | See `Updates`'s instances.
  type Printer b = (p :: Type) | p -> b
  -- | This is the implementation of `Xmobar.Run.Exec.run`.
  run' :: PacmanUpdates b -> IO String

-- | No additional argument required for constructing the plugin;
-- the user-provided printer is fed with a `Bool` telling whether
-- the system is running an outdated kernel, and an `Int` telling
-- the number of available updates (or `Left` if an error occurred
-- when calling `checkupdates`).
instance Updates False where
  type Arg False = Void
  type Printer False = Bool -> Either String Int -> String
  run' (Make _ _ printer)
      = printer
          <$> kernIsOld
          <*> (fmap V.length <$> checkUpdates)

-- | Constructing the plugin requires an additional `String` telling the name
-- name of the kernel package; the user-provided printer is fed with a `Bool`
-- telling whether the system is running an outdated kernel, and an `(Int,
-- Bool)` pair telling the number of available updates and whether one of these
-- is a kernel update (or `Left` if an error occurred when calling
-- `checkupdates`).
instance Updates True where
  type Arg True = String
  type Printer True = Bool -> Either String (Int, Bool) -> String
  run' (Make _ kernName printer)
      = printer
          <$> kernIsOld
          <*> (fmap (V.length &&& elem kernName) <$> checkUpdates)

-- | Pattern synonym used to construct a `PacmanUpdates True`.
pattern PacmanUpdatesK :: Rate -> Arg True -> Printer True -> PacmanUpdates True
pattern PacmanUpdatesK r a p = Make r a p

-- | Pattern synonym used to construct a `PacmanUpdates False`.
pattern PacmanUpdatesNoK :: Rate -> Printer False -> PacmanUpdates False
pattern PacmanUpdatesNoK r p <- Make r _ p
  where PacmanUpdatesNoK r p = Make r undefined p

checkUpdates :: IO (Either String (V.Vector String))
checkUpdates = readProcessWithExitCode "checkupdates" [] ""
    <&> \case (ExitFailure 2, "", "") -> Right V.empty
              (ExitSuccess, stdout, "")
                  -> let pkgName = takeWhile (/= ' ')
                         pkgs = V.fromList $ fmap pkgName $ lines stdout
                     in case V.length pkgs of
                      0 -> impossible
                      _ -> Right pkgs
              (ExitFailure 1, _, _) -> Left "checkupdates: unknown cause of failure."
              _ -> impossible

kernIsOld :: IO Bool
kernIsOld = (/= ExitSuccess) . exitCode <$> readProcessWithExitCode "modinfo" ["-n", "i915"] ""
  where exitCode = fst3

impossible :: a
impossible = error "This is impossible, according to my knowledge."
