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

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

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

{- |
Module      :  Plugins.Monitors.PacmanUpdates
Copyright   :  (c) 2024, 2026 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 (
#if __GLASGOW_HASKELL__ >= 908
    {-# DEPRECATED "This ctor is DEPRECATED; please use `PacmanUpdates` type and `PacmanUpdatesK`, `PacmanUpdatesPredicateK` and `PacmanUpdatesNoK` constructors instead." #-}
#endif
    pattern PacmanUpdates
  , PacmanUpdates ()
  , PacmanUpdatesKernelCheck (..)
  , pattern PacmanUpdatesK
  , pattern PacmanUpdatesPredicateK
  , 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`, `PacmanUpdatesPredicateK`, 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 NoKernelCheck
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


-- | Different types of kernel checks.
data PacmanUpdatesKernelCheck = NoKernelCheck | PredicateKernelCheck

-- | PacmanUpdates plugin parametrized over the `PacmanUpdatesKernelCheck` kind.
data PacmanUpdates (b :: PacmanUpdatesKernelCheck)
  = 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 :: PacmanUpdatesKernelCheck)) where
  alias = const "pacman"
  rate (Make r _ _) = r
  run = Xmobar.Plugins.PacmanUpdates.run'

class Updates (b :: PacmanUpdatesKernelCheck) 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 NoKernelCheck where
  type Arg NoKernelCheck = Void
  type Printer NoKernelCheck = Bool -> Either String Int -> String
  run' (Make _ _ printer)
      = printer
          <$> kernIsOld
          <*> (fmap V.length <$> checkUpdates)

-- | Constructing the plugin requires an additional `String -> Bool` predicate
-- that receives each available package name and returns `True` for kernel
-- packages; 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 PredicateKernelCheck where
  type Arg PredicateKernelCheck = String -> Bool
  type Printer PredicateKernelCheck = Bool -> Either String (Int, Bool) -> String
  run' (Make _ checkKern printer)
      = printer
          <$> kernIsOld
          <*> (fmap (V.length &&& V.any checkKern) <$> checkUpdates)

-- | Pattern synonym to construct a `PacmanUpdates PredicateKernelCheck` that
-- detects updates for packages matched by the given @(String -> Bool)@
-- predicate. This can be used to detect kernel updates for distributions
-- with versioned kernel package names (e.g. Manjaro's @linux618@)
pattern PacmanUpdatesPredicateK :: Rate -> Arg PredicateKernelCheck -> Printer PredicateKernelCheck -> PacmanUpdates PredicateKernelCheck
pattern PacmanUpdatesPredicateK r a p = Make r a p

-- | A convenience wrapper around PacmanUpdatesPredicateK with the predicate @(== kernName)@
-- Construction only: the kernel name cannot be recovered when matching.
pattern PacmanUpdatesK :: Rate -> String -> Printer PredicateKernelCheck -> PacmanUpdates PredicateKernelCheck
pattern PacmanUpdatesK r kernName p <-
    (error "PacmanUpdatesK: build-only pattern synonym (a ctor synonym)." -> (r, kernName, p))
  where PacmanUpdatesK r kernName p = PacmanUpdatesPredicateK r (== kernName) p

-- | Pattern synonym used to construct a `PacmanUpdates NoKernelCheck`.
pattern PacmanUpdatesNoK :: Rate -> Printer NoKernelCheck -> PacmanUpdates NoKernelCheck
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."
