#!/usr/bin/env tclsh
#
# i8kmon -- Monitor the temperature on Dell laptops.
#
# Copyright (C) 2013-2017  Vitor Augusto <vitorafsr@gmail.com>
# Copyright (C) 2001-2005  Massimo Dal Zotto <dz@debian.org>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any
# later version.
#
# This program 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
# General Public License for more details.

array set config {
    sysconfig	/etc/i8kmon.conf
    userconfig	~/.i8kmon
    i8kfan	/usr/bin/i8kfan
    acpi    "acpi"
    use_conf    1
    verbose	0
    timeout	2
    t_high	80
    0		{{0 0}  -1  60  -1  65}
    1		{{1 0}  50  70  55  75}
    2		{{1 1}  60  80  65  85}
    3		{{2 2}  70 128  75 128}
}

array set status {
    leftspeed	{}
    rightspeed	{}
    nfans	2
    acpi_timer	0
    state	0
    temp	0
    lstate	-2
    rstate	-2
    lspeed	0
    rspeed	0
    lstuck	0
    rstuck	0
    ac		0
    t_low	0
    t_high	0
}

proc read_config {} {
    global config
    global status

    # read system config file
    if {[file exists $config(sysconfig)]} {
    	source $config(sysconfig)
	    if {$config(verbose) > 0} {
	        puts "reading system config file"
	    }
    }

    # read user config file
    # as it is read last, the options here have precedence
    if {$config(use_conf) != 0} {
        if {[file exists $config(userconfig)]} {
            source $config(userconfig)
	        if {$config(verbose) > 0} {
        		puts "reading user config file"
	        }
    	}
    }

    foreach key {0 1 2 3} {
	    set fans  [lindex $config($key) 0]
	    set lo_ac [lindex $config($key) 1]
	    set hi_ac [lindex $config($key) 2]
	    set lo_bt [lindex $config($key) 3]
	    set hi_bt [lindex $config($key) 4]

	    # check that for each key hi temp > lo temp
	    if {$lo_ac > $hi_ac} { set hi_ac [expr $lo_ac + 5]}
	    if {$lo_bt > $hi_bt} { set hi_bt [expr $lo_bt + 5]}

	    # set temperature to -1 to lo_ac and lo_bt at config(0)
	    if {$key == 0} {
	        set lo_ac -1
	        set lo_bt -1
	    }

	    # set temperature to 128 to hi_ac and hi_bt at config(3)
	    if {$key == 3} {
	        set hi_ac 128
	        set hi_bt 128
	    }

	    set config($key) [list $fans $lo_ac $hi_ac $lo_bt $hi_bt]
    }
}

proc status_timer {} {
    global config
    global status

    # Reschedule status timer
    after [expr $config(timeout)*1000] {status_timer}

    check_status
}

proc check_status {} {
    global config
    global status

    if {![read_i8k_status]} {
	return
    }

	fan_control
}

proc read_i8k_status {} {
    global config
    global status

    set temp [eval exec "i8kctl temp"]
    set status(temp) $temp
    set state [eval exec "i8kctl fan"]
    set status(lstate) [lindex $state 0]
    set status(rstate) [lindex $state 1]

    # retrieve probeb fans speeds stored on variables and set the correct speed values
	# using stored values improve performance as it does not requires reading
	# values all the time from the system, and also from the fact that reading
	# fan speeds is a slow call that freezes the computer in some Dell laptops models.
    if {$status(lstate) != -1} {
	set status(lspeed) [lindex $status(leftspeed) $status(lstate)]
    } else {
	set status(lspeed) -1
    }
    if {$status(rstate) != -1} {
	set status(rspeed) [lindex $status(rightspeed) $status(rstate)]
    } else {
	set status(rspeed) -1
    }

    # If AC status is not available read it from procfs
    set ac [eval exec "i8kctl ac"]
    if {$ac < 0} {
	read_ac_status
    }

    if {$config(verbose) > 0} {
        puts "temp, left fan state, right fan state, ac state: $status(temp) $status(lstate) $status(rstate) $status(ac)"
    }

    return 1
}

proc read_ac_status {} {
    global config
    global status

    # Read ac status once per minute
    if {[incr status(acpi_timer) -1] > 0} {
	return 1
    }

    set status(acpi_timer) [expr 60 / $config(timeout)]

    set acpi_ac [exec {*}$config(acpi)]
    if {[string match *on-line* $acpi_ac] || [string match *online* $acpi_ac]} {
        set status(ac) 1
    } else {
        set status(ac) 0
    }

    if {$config(verbose) > 0} {
        puts "[clock seconds] acpi: $acpi_ac"
    }

    return 0
}

proc fan_control {} {
    global config
    global status

    set index [expr $status(ac) ? 1 : 3]
    set state $status(state)
    set temp  $status(temp)

	# store state because it maybe will change below
	set actual_state $state

    set status(t_low)  [lindex $config($state) $index]
    set status(t_high) [lindex $config($state) [expr $index+1]]

    while {$temp < 128 && $temp >= $status(t_high)} {
	    if {$config(verbose) > 0} {
	        puts -nonewline "# ($temp>=$status(t_high)), "
	    }
	    incr state
	    set status(t_low)  [lindex $config($state) $index]
	    set status(t_high) [lindex $config($state) [expr $index+1]]
	    if {$config(verbose) > 0} {
	        puts "state=$state, low=$status(t_low), high=$status(t_high)"
	    }
    }

    while {$temp > 0 && $temp <= $status(t_low)} {
	    if {$config(verbose) > 0} {
	        puts -nonewline "# ($temp<=$status(t_low)), "
	    }
	    incr state -1
	    set status(t_low)  [lindex $config($state) $index]
	    set status(t_high) [lindex $config($state) [expr $index+1]]
	    if {$config(verbose) > 0} {
	        puts "state=$state, low=$status(t_low), high=$status(t_high)"
	    }
    }

	if {$state != $actual_state} {
        set_fan $state
	}
}

proc set_fan {state} {
    global config
    global status

	set status(state) $state
    set args [lindex $config($state) 0]

    set left [lindex $args 0]
    set right [lindex $args 1]

    if {$status(lstate) == -1 || $left == $status(lstate)} {
        set left "-"
    } else {
        set status(lstate) $left
    }

    if {$status(rstate) == -1 || $right == $status(rstate)} {
        set right "-"
    } else {
        set status(rstate) $right
    }

    if {$status(nfans) < 2} { set right "-" }

    if {$config(verbose) > 0} {
        puts "$config(i8kfan) $left $right"
    }

    exec $config(i8kfan) $left $right
}

proc usage {} {
    global argv0

    regsub -all {^.*/} $argv0 {} progname
    puts "Usage:  $progname \[<options>...]

Options:

   -nc|--nouserconfig       don\x27t read \$HOME/.i8kmon
    -v|--verbose            report log to stdout
    -t|--timeout <secs>     set poll timeout

"
}

proc parse_options {} {
    global config
    global status
    global argv

    for {set i 0} {$i < [llength $argv]} {incr i} {
	set arg [lindex $argv $i]
	switch -- $arg {
	    -\? - -h - -help - --help {
		usage
		exit
	    }
        --nouserconfig - -nc {
        set config(use_conf) 0
        }
	    --verbose - -v {
		set config(verbose) 1
	    }
	    --timeout - -t {
		set config(timeout) [lindex $argv [incr i]]
	    }
	    -- {
		continue
	    }
	    default {
		puts stderr "invalid option: $arg"
		exit 1
	    }
	}
    }

    if {$config(verbose) > 0} {
	puts "i8kmon"
	parray config
	parray status
    }
}

proc probe_fan_speed {} {
    global status
	global config

	if {$status(leftspeed) != {} || $status(rightspeed) != {}} {
		return
	}

    exec $config(i8kfan) 1 1
    after 1000
    set speeds [eval exec "i8kctl speed"]
    set lspeed1 [lindex $speeds 0]
    set rspeed1 [lindex $speeds 1]

    exec $config(i8kfan) 2 2
    after 1000
    set speeds [eval exec "i8kctl speed"]
    set lspeed2 [lindex $speeds 0]
    set rspeed2 [lindex $speeds 1]

    exec $config(i8kfan) 3 3
    after 1000
    set speeds [eval exec "i8kctl speed"]
    set lspeed3 [lindex $speeds 0]
    set rspeed3 [lindex $speeds 1]

    set status(leftspeed) "0 $lspeed1 $lspeed2 $lspeed3"
    set status(rightspeed) "0 $rspeed1 $rspeed2 $rspeed3"

	if {$config(verbose) > 1} {
		puts "Probed fan speeds are left=$status(leftspeed), right=status(rightspeed)"
	}
}

# probe external tools
proc probe_tools {} {

    # The possibility of choosing 'acpi' or 'acpitool' is for compatibility
    # between different architectures: amd64, i386, kFreeBSD
    # This code below is strictly related on package dependency stated at
    # keyword 'Depends:' on file 'debian/control'
    if {![catch {exec acpi}]} {
        set config(acpi) "acpi"
    } elseif {[catch {exec acpitool}]} {
        set config(acpi) "acpitool"
    } else {
        puts stderr "Package dependency problem: neither 'acpi' nor 'acpitool' package is installed"
    }
}

proc main {} {
    global status

    read_config
    probe_fan_speed
    probe_tools
    parse_options
    set_fan $status(state)
    status_timer
}

if {$tcl_interactive == 0} {
    main
    vwait forever
}

