(define-module (services my-tlp)
  #:use-module (gnu services)
  #:use-module (gnu services base)
  #:use-module (gnu services configuration)
  #:use-module (gnu services shepherd)
  #:use-module (guix packages)
  #:use-module (guix gexp)
  #:use-module (guix records)
  #:use-module (gnu packages linux)
  #:export (tlp-service-type
            tlp-configuration))

(define (uglify-field-name field-name)
  (let ((str (symbol->string field-name)))
    (string-join (string-split
                  (string-upcase
                   (if (string-suffix? "?" str)
                       (substring str 0 (1- (string-length str)))
                       str))
                  #\-)
                 "_")))

(define (serialize-field field-name val)
  (format #t "~a=~a\n" (uglify-field-name field-name) val))

(define (serialize-boolean field-name val)
  (serialize-field field-name (if val "1" "0")))
(define-maybe boolean)

(define (serialize-string field-name val)
  (serialize-field field-name val))
(define-maybe string)

(define (space-separated-string-list? val)
  (and (list? val)
       (and-map (lambda (x)
                  (and (string? x) (not (string-index x #\space))))
                val)))
(define (serialize-space-separated-string-list field-name val)
  (serialize-field field-name
                   (format #f "~s"
                           (string-join val " "))))
(define-maybe space-separated-string-list)

(define (non-negative-integer? val)
  (and (exact-integer? val) (not (negative? val))))
(define (serialize-non-negative-integer field-name val)
  (serialize-field field-name val))
(define-maybe non-negative-integer)

(define (on-off-boolean? val)
  (boolean? val))
(define (serialize-on-off-boolean field-name val)
  (serialize-field field-name (if val "on" "off")))
(define-maybe on-off-boolean)

(define (y-n-boolean? val)
  (boolean? val))
(define (serialize-y-n-boolean field-name val)
  (serialize-field field-name (if val "Y" "N")))

(define-configuration tlp-configuration
  (tlp
   (package tlp)
   "The TLP package.")

  (tlp-enable?
   (boolean #t)
   "Set to true if you wish to enable TLP.")

  (tlp-default-mode
   (string "AC")
   "Default mode when no power supply can be detected.  Alternatives are
AC and BAT.")

  (disk-idle-secs-on-ac
   (non-negative-integer 0)
   "Number of seconds Linux kernel has to wait after the disk goes idle,
before syncing on AC.")

  (disk-idle-secs-on-bat
   (non-negative-integer 2)
   "Same as @code{disk-idle-ac} but on BAT mode.")

  (max-lost-work-secs-on-ac
   (non-negative-integer 15)
   "Dirty pages flushing periodicity, expressed in seconds.")

  (max-lost-work-secs-on-bat
   (non-negative-integer 60)
   "Same as @code{max-lost-work-secs-on-ac} but on BAT mode.")

;   (cpu-scaling-governor-on-ac
;    (maybe-space-separated-string-list 'disabled)
;    "CPU frequency scaling governor on AC mode.  With intel_pstate
; driver, alternatives are powersave and performance.  With acpi-cpufreq driver,
; alternatives are ondemand, powersave, performance and conservative.")

  ; (cpu-scaling-governor-on-bat
  ;  (maybe-space-separated-string-list 'disabled)
  ;  "Same as @code{cpu-scaling-governor-on-ac} but on BAT mode.")

;   (cpu-scaling-min-freq-on-ac
;    (maybe-non-negative-integer 'disabled)
;    "Set the min available frequency for the scaling governor on AC.")
;
;   (cpu-scaling-max-freq-on-ac
;    (maybe-non-negative-integer 'disabled)
;    "Set the max available frequency for the scaling governor on AC.")
;
;   (cpu-scaling-min-freq-on-bat
;    (maybe-non-negative-integer 'disabled)
;    "Set the min available frequency for the scaling governor on BAT.")
;
;   (cpu-scaling-max-freq-on-bat
;    (maybe-non-negative-integer 'disabled)
;    "Set the max available frequency for the scaling governor on BAT.")
;
;   (cpu-min-perf-on-ac
;    (maybe-non-negative-integer 'disabled)
;    "Limit the min P-state to control the power dissipation of the CPU,
; in AC mode.  Values are stated as a percentage of the available performance.")
;
;   (cpu-max-perf-on-ac
;    (maybe-non-negative-integer 'disabled)
;    "Limit the max P-state to control the power dissipation of the CPU,
; in AC mode.  Values are stated as a percentage of the available performance.")
;
;   (cpu-min-perf-on-bat
;    (maybe-non-negative-integer 'disabled)
;    "Same as @code{cpu-min-perf-on-ac} on BAT mode.")
;
;   (cpu-max-perf-on-bat
;    (maybe-non-negative-integer 'disabled)
;    "Same as @code{cpu-max-perf-on-ac} on BAT mode.")

  ; (cpu-boost-on-ac?
  ;  (maybe-boolean 'disabled)
  ;  "Enable CPU turbo boost feature on AC mode.")
  ;
  ; (cpu-boost-on-bat?
  ;  (maybe-boolean 'disabled)
  ;  "Same as @code{cpu-boost-on-ac?} on BAT mode.")

  (sched-powersave-on-ac?
   (boolean #f)
   "Allow Linux kernel to minimize the number of CPU cores/hyper-threads
used under light load conditions.")

  (sched-powersave-on-bat?
   (boolean #t)
   "Same as @code{sched-powersave-on-ac?} but on BAT mode.")

  (nmi-watchdog?
   (boolean #f)
   "Enable Linux kernel NMI watchdog.")

;   (phc-controls
;    (maybe-string 'disabled)
;    "For Linux kernels with PHC patch applied, change CPU voltages.
; An example value would be @samp{\"F:V F:V F:V F:V\"}.")

  (energy-perf-policy-on-ac
   (string "performance")
   "Set CPU performance versus energy saving policy on AC.  Alternatives are
performance, normal, powersave.")

  (energy-perf-policy-on-bat
   (string "powersave")
   "Same as @code{energy-perf-policy-ac} but on BAT mode.")

  (disks-devices
   (space-separated-string-list '("sda"))
   "Hard disk devices.")

  (disk-apm-level-on-ac
   (space-separated-string-list '("254" "254"))
   "Hard disk advanced power management level.")

  (disk-apm-level-on-bat
   (space-separated-string-list '("128" "128"))
   "Same as @code{disk-apm-bat} but on BAT mode.")

;   (disk-spindown-timeout-on-ac
;    (maybe-space-separated-string-list 'disabled)
;    "Hard disk spin down timeout.  One value has to be specified for
; each declared hard disk.")

  ; (disk-spindown-timeout-on-bat
  ;  (maybe-space-separated-string-list 'disabled)
  ;  "Same as @code{disk-spindown-timeout-on-ac} but on BAT mode.")

;   (disk-iosched
;    (maybe-space-separated-string-list 'disabled)
;    "Select IO scheduler for disk devices.  One value has to be specified
; for each declared hard disk.  Example alternatives are cfq, deadline and noop.")

  (sata-linkpwr-on-ac
   (string "max_performance")
   "SATA aggressive link power management (ALPM) level.  Alternatives are
min_power, medium_power, max_performance.")

  (sata-linkpwr-on-bat
   (string "min_power")
   "Same as @code{sata-linkpwr-ac} but on BAT mode.")

;   (sata-linkpwr-blacklist
;    (maybe-string 'disabled)
;    "Exclude specified SATA host devices for link power management.")
;
;   (ahci-runtime-pm-on-ac?
;    (maybe-on-off-boolean 'disabled)
;    "Enable Runtime Power Management for AHCI controller and disks
; on AC mode.")
;
;   (ahci-runtime-pm-on-bat?
;    (maybe-on-off-boolean 'disabled)
;    "Same as @code{ahci-runtime-pm-on-ac} on BAT mode.")

  (ahci-runtime-pm-timeout
   (non-negative-integer 15)
   "Seconds of inactivity before disk is suspended.")

  (pcie-aspm-on-ac
   (string "performance")
   "PCI Express Active State Power Management level.  Alternatives are
default, performance, powersave.")

  (pcie-aspm-on-bat
   (string "powersave")
   "Same as @code{pcie-aspm-ac} but on BAT mode.")

  (radeon-power-profile-on-ac
   (string "high")
   "Radeon graphics clock speed level.  Alternatives are
low, mid, high, auto, default.")

  (radeon-power-profile-on-bat
   (string "low")
   "Same as @code{radeon-power-ac} but on BAT mode.")

  (radeon-dpm-state-on-ac
   (string "performance")
   "Radeon dynamic power management method (DPM).  Alternatives are
battery, performance.")

  (radeon-dpm-state-on-bat
   (string "battery")
   "Same as @code{radeon-dpm-state-ac} but on BAT mode.")

  (radeon-dpm-perf-level-on-ac
   (string "auto")
   "Radeon DPM performance level.  Alternatives are
auto, low, high.")

  (radeon-dpm-perf-level-on-bat
   (string "auto")
   "Same as @code{radeon-dpm-perf-ac} but on BAT mode.")

  (wifi-pwr-on-ac?
   (on-off-boolean #f)
   "Wifi power saving mode.")

  (wifi-pwr-on-bat?
   (on-off-boolean #t)
   "Same as @code{wifi-power-ac?} but on BAT mode.")

  (wol-disable?
   (y-n-boolean #t)
   "Disable wake on LAN.")

  (sound-power-save-on-ac
   (non-negative-integer 0)
   "Timeout duration in seconds before activating audio power saving
 on Intel HDA and AC97 devices.  A value of 0 disables power saving.")

  (sound-power-save-on-bat
   (non-negative-integer 1)
   "Same as @code{sound-powersave-ac} but on BAT mode.")

  (sound-power-save-controller?
   (y-n-boolean #t)
   "Disable controller in powersaving mode on Intel HDA devices.")

  (bay-poweroff-on-bat?
   (boolean #f)
   "Enable optical drive in UltraBay/MediaBay on BAT mode.
Drive can be powered on again by releasing (and reinserting) the eject lever
or by pressing the disc eject button on newer models.")

  (bay-device
   (string "sr0")
   "Name of the optical drive device to power off.")

  (runtime-pm-on-ac
   (string "on")
   "Runtime Power Management for PCI(e) bus devices.  Alternatives are
on and auto.")

  (runtime-pm-on-bat
   (string "auto")
   "Same as @code{runtime-pm-ac} but on BAT mode.")

  (runtime-pm-all?
   (boolean #t)
   "Runtime Power Management for all PCI(e) bus devices, except
blacklisted ones.")

  ; (runtime-pm-blacklist
  ;  (maybe-space-separated-string-list 'disabled)
  ;  "Exclude specified PCI(e) device addresses from Runtime Power Management.")

  (runtime-pm-driver-blacklist
   (space-separated-string-list '("radeon" "nouveau"))
   "Exclude PCI(e) devices assigned to the specified drivers from
Runtime Power Management.")

  (usb-autosuspend?
   (boolean #t)
   "Enable USB autosuspend feature.")

  ; (usb-blacklist
  ;  (maybe-string 'disabled)
  ;  "Exclude specified devices from USB autosuspend.")

  (usb-blacklist-wwan?
   (boolean #t)
   "Exclude WWAN devices from USB autosuspend.")

;   (usb-whitelist
;    (maybe-string 'disabled)
;    "Include specified devices into USB autosuspend, even if they are
; already excluded by the driver or via @code{usb-blacklist-wwan?}.")
;
;   (usb-autosuspend-disable-on-shutdown?
;    (maybe-boolean 'disabled)
;    "Enable USB autosuspend before shutdown.")

  (restore-device-state-on-startup?
   (boolean #f)
   "Restore radio device state (bluetooth, wifi, wwan) from previous
shutdown on system startup.")
  (start-charge-thresh-bat0
   (non-negative-integer 96)
   "Charging start threshold for battery 0 (main battery)")
  (stop-charge-thresh-bat0
   (non-negative-integer 100)
   "Charging stop threshold for battery 0 (main battery)")
  (start-charge-thresh-bat1
   (non-negative-integer 96)
   "Same as @code{start-charge-threshold-bat0} but for battery 1 (auxiliary battery)")
  (stop-charge-thresh-bat1
   (non-negative-integer 100)
   "Same as @code{stop-charge-threshold-bat0} but for battery 1 (auxiliary battery)"))


(define (tlp-shepherd-service config)
  (let* ((tlp-bin (file-append
                   (tlp-configuration-tlp config) "/bin/tlp"))
         (tlp-action (lambda args
                       #~(lambda _
                           (zero? (system* #$tlp-bin #$@args))))))
    (list (shepherd-service
           (documentation "Run TLP script.")
           (provision '(tlp))
           (requirement '(user-processes))
           (start (tlp-action "init" "start"))
           (stop  (tlp-action "init" "stop"))))))

(define (tlp-activation config)
  (let* ((config-str (with-output-to-string
                       (lambda ()
                         (serialize-configuration
                          config
                          tlp-configuration-fields))))
         (config-file (plain-file "tlp" config-str)))
    (with-imported-modules '((guix build utils))
      #~(begin
          (use-modules (guix build utils))
          (copy-file #$config-file "/etc/tlp.conf")))))

(define tlp-service-type
  (service-type
   (name 'tlp)
   (extensions
    (list
     (service-extension shepherd-root-service-type
                        tlp-shepherd-service)
     (service-extension udev-service-type
                        (compose list tlp-configuration-tlp))
     (service-extension activation-service-type
                        tlp-activation)))
   (default-value (tlp-configuration))
   (description "Run TLP, a power management tool.")))
