# -*- coding: utf-8 -*-
"""
Display temperatures, voltages, fans, and more from hardware sensors.

Configuration parameters:
    cache_timeout: refresh interval for this module (default 10)
    chips: specify a list of chips to use (default [])
    format: display format for this module (default '{format_chip}')
    format_chip: display format for chips (default '{name} {format_sensor}')
    format_chip_separator: show separator if more than one (default ' ')
    format_sensor: display format for sensors
        (default '[\?color=darkgray {name}] [\?color=auto.input&show {input}]')
    format_sensor_separator: show separator if more than one (default ' ')
    sensors: specify a list of sensors to use (default [])
    thresholds: specify color thresholds to use (default {'auto.input': True})

Format placeholders:
    {format_chip}   format for chips

Format_chip placeholders:
    {name}          chip name, eg coretemp-isa-0000, nouveau-pci-0500
    {adapter}       adapter type, eg ISA adapter, PCI adapter
    {format_sensor} format for sensors

Format_sensor placeholders:
    {name}          sensor name, eg core_0, gpu_core, temp1, fan1

    See `sensors -u` for a full list of placeholders for `format_chip`,
    `format_sensors` without the prefixes, `chips` and `sensors` options.

    See https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface
    for more information on the sensor placeholders.

Color options for `auto.input` threshold:
    color_zero: zero value or less (color red)
    color_min: minimum value (color lightgreen)
    color_excl_input: input value excluded from threshold (color None)
    color_input: input value (color lime)
    color_near_max: input value near maximum value (color yellow)
    color_max: maximum value (color orange)
    color_near_crit: input value near critical value (color lightcoral)
    color_crit: critical value (color red)

Color thresholds:
    format_sensor:
        xxx: print a color based on the value of `xxx` placeholder
        auto.input: print a color based on the value of `input` placeholder
            against a customized threshold

Requires:
    lm_sensors: a tool to read temperature/voltage/fan sensors
    sensors-detect: see `man sensors-detect # --auto` to read about
        using defaults or to compile a list of kernel modules

Examples:
```
# identify possible chips, sensors, placeholders, etc
    [user@py3status ~] $ sensors -u
    ----------------------------- # ──────────────────────────────────────
    coretemp-isa-0000             # chip {name}         # chip: coretemp*
    Adapter: ISA adapter          #  ├── {adapter} type
    ----                          #  │------------------------------------
    Core 0:                       #  ├── sensor {name}  # sensor: core_0
      temp2_input: 48.000         #  │    ├── {input}
      temp2_max: 81.000           #  │    ├── {max}
      temp2_crit: 91.000          #  │    ├── {crit}
      temp2_crit_alarm: 0.000     #  │    └── {crit_alarm}
    Core 1:                       #  └── sensor {name}  # sensor: core_1
      temp3_input: 48.000         #       ├── {input}
      temp3_max: 81.000           #       ├── {max}
      temp3_crit: 91.000          #       ├── {crit}
      temp3_crit_alarm: 0.000     #       └── {crit_alarm}
                                  # ──────────────────────────────────────
    k10temp-pci-00c3              # chip {name}         # chip: k10temp*
    Adapter: PCI adapter          #  ├── {adapter} type
    ----                          #  │------------------------------------
    temp1:                        #  ├── sensor {name}  # sensor: temp1
      temp1_input: 30.000         #  │    ├── {input}
      temp1_max: -71.000          #  │    ├── {max}
      temp1_min: -15.000          #  │    ├── {min}
      temp1_alarm: 1.000          #  │    ├── {alarm}
      temp1_offset: 0.000         #  │    ├── {offset}
      temp1_beep: 0.000           #  │    └── {beep}
    intrusion0:                   #  └── sensor {name}  # sensor: intrusion0
      intrusion0_alarm: 0.000     #       └── {alarm}

    Solid lines denotes chips. Dashed lines denotes sensors.
    Sensor names are lowercased and its spaces replaced with underscores.
    The numbered prefixes, eg `temp1_*` are removed to keep names clean.

# specify chips to use
lm_sensors {
    chips = ['coretemp-isa-0000']  # full
        OR
    chips = ['coretemp*']  # fnmatch
}

# specify sensors to use
lm_sensors {
    sensors = ['core_0', 'core_1', 'core_2', 'core_3']  # full
        OR
    sensors = ['core_*']  # fnmatch
}

# show name per chip, eg CPU 35°C 36°C 37°C 39°C GPU 52°C
lm_sensors {
    format_chip = '[\?if=name=coretemp-isa-0000 CPU ]'
    format_chip += '[\?if=name=nouveau-pci-0500 GPU ]'
    format_chip += '{format_sensor}'
    format_sensor = '\?color=auto.input {input}°C'
    sensors = ['core*', 'temp*']
}

# show name per sensor, eg CPU1 35°C CPU2 36°C CPU3 37°C CPU4 39°C GPU 52°C
lm_sensors {
    format_chip = '{format_sensor}'
    format_sensor = '[\?if=name=core_0 CPU1 ]'
    format_sensor += '[\?if=name=core_1 CPU2 ]'
    format_sensor += '[\?if=name=core_2 CPU3 ]'
    format_sensor += '[\?if=name=core_3 CPU4 ]'
    format_sensor += '[\?if=name=gpu_core GPU ]'
    format_sensor += '[\?color=auto.input {input}°C]'
    sensors = ['core*', 'temp*']
}
```

@author lasers

SAMPLE OUTPUT
[
    {'full_text': 'coretemp-isa-000 '},
    {'full_text': 'core_0 ', 'color': '#a9a9a9'},
    {'full_text': '39 ', 'color': '#00ff00'},
    {'full_text': 'core_1 ', 'color': '#a9a9a9'},
    {'full_text': '40', 'color': '#00ff00'},
]

chip_names
[
    {'full_text': 'CPU '},
    {'full_text': '62°C ', 'color': '#00ff00'},
    {'full_text': '76°C ', 'color': '#ffff00'},
    {'full_text': '83°C ', 'color': '#ffa500'},
    {'full_text': '92°C ', 'color': '#ff0000'},
    {'full_text': 'GPU '},
    {'full_text': '52°C', 'color': '#00ff00'},
]

sensor_names
[
    {'full_text': 'CPU1 '},
    {'full_text': '62°C ', 'color': '#00ff00'},
    {'full_text': 'CPU2 '},
    {'full_text': '76°C ', 'color': '#ffff00'},
    {'full_text': 'TEMP1 '},
    {'full_text': '30 ', 'color': '#ffa500'},
    {'full_text': 'TEMP2 '},
    {'full_text': '27 ', 'color': '#ffa500'},
    {'full_text': 'GPU '},
    {'full_text': '52°C', 'color': '#00ff00'},
]
"""

from fnmatch import fnmatch
from collections import OrderedDict

STRING_NOT_INSTALLED = "not installed"


class Py3status:
    """
    """

    # available configuration parameters
    cache_timeout = 10
    chips = []
    format = "{format_chip}"
    format_chip = "{name} {format_sensor}"
    format_chip_separator = " "
    format_sensor = "[\?color=darkgray {name}] [\?color=auto.input&show {input}]"
    format_sensor_separator = " "
    sensors = []
    thresholds = {"auto.input": True}

    def post_config_hook(self):
        if not self.py3.check_commands("sensors"):
            raise Exception(STRING_NOT_INSTALLED)

        placeholders = self.py3.get_placeholders_list(self.format_sensor)
        format_sensor = {x: ":g" for x in placeholders if x != "name"}
        self.sensor_placeholders = [x for x in placeholders if x != "name"]
        self.format_sensor = self.py3.update_placeholder_formats(
            self.format_sensor, format_sensor
        )

        self.first_run = True
        self.lm_sensors_command = "sensors -u"
        if not self.py3.format_contains(self.format_chip, "adapter"):
            self.lm_sensors_command += "A"  # don't print adapters

        if self.chips:
            lm_sensors_data = self._get_lm_sensors_data()
            chips = []
            for _filter in self.chips:
                for chunk in lm_sensors_data.split("\n\n")[:-1]:
                    for line in chunk.splitlines():
                        if fnmatch(line, _filter):
                            chips.append(line)
                        break
            self.lm_sensors_command += " {}".format(" ".join(chips))

        self.sensors = {"list": [], "name": {}, "sensors": self.sensors}

        self.thresholds_auto = False
        self.thresholds_man = self.py3.get_color_names_list(self.format_sensor)
        if (
            all("auto.input" in x for x in [self.thresholds, self.thresholds_man])
            and "input" in placeholders
        ):
            self.color_zero = self.py3.COLOR_ZERO or "red"
            self.color_input = self.py3.COLOR_INPUT or "lime"
            self.color_min = self.py3.COLOR_MIN or "lightgreen"
            self.color_excl_input = self.py3.COLOR_EXCL_INPUT or None
            self.color_near_max = self.py3.COLOR_NEAR_MAX or "yellow"
            self.color_max = self.py3.COLOR_MAX or "orange"
            self.color_near_crit = self.py3.COLOR_NEAR_CRIT or "lightcoral"
            self.color_crit = self.py3.COLOR_CRIT or "red"

            self.thresholds_auto = self.thresholds["auto.input"]
            del self.thresholds["auto.input"]

        if "auto.input" in self.thresholds_man:
            self.thresholds_man.remove("auto.input")

    def _get_lm_sensors_data(self):
        return self.py3.command_output(self.lm_sensors_command)

    def lm_sensors(self):
        lm_sensors_data = self._get_lm_sensors_data()
        new_chip = []

        for chunk in lm_sensors_data.split("\n\n")[:-1]:
            chip = {"sensors": OrderedDict()}
            first_line = True
            sensor_name = None
            new_sensor = []

            for line in chunk.splitlines():
                if line.startswith("  "):
                    if not sensor_name:
                        continue
                    key, value = line.split(": ")
                    prefix, key = key.split("_", 1)
                    chip["sensors"][sensor_name][key] = value
                elif first_line:
                    chip["name"] = line
                    first_line = False
                elif "Adapter:" in line:
                    chip["adapter"] = line[9:]
                else:
                    try:
                        sensor_name = self.sensors["name"][line]
                    except KeyError:
                        sensor_name = line[:-1].lower().replace(" ", "_")
                        self.sensors["name"][line] = sensor_name
                    if self.sensors["sensors"]:
                        if self.first_run:
                            for _filter in self.sensors["sensors"]:
                                if fnmatch(sensor_name, _filter):
                                    self.sensors["list"].append(sensor_name)
                        if sensor_name not in self.sensors["list"]:
                            sensor_name = None
                            continue
                    chip["sensors"][sensor_name] = {}

            for name, sensor in chip["sensors"].items():
                sensor["name"] = name

                for x in self.thresholds_man:
                    if x in sensor:
                        self.py3.threshold_get_color(sensor[x], x)

                if self.thresholds_auto:
                    auto_input = []
                    _input = sensor.get("input")
                    if self.first_run and _input is not None:
                        _input = float(_input)
                        _min = float(sensor.get("min", 0))
                        _max = float(sensor.get("max", 0))
                        _crit = float(sensor.get("crit", 0))
                        auto_input.append((0, self.color_zero))
                        if _min or _max or _crit:
                            _color_input = self.color_input
                        else:
                            _color_input = self.color_excl_input
                        auto_input.append((0.001, _color_input))
                        if _min >= _input:
                            auto_input.append((_min, self.color_min))
                        if _max:
                            _near_max = _max - _max / 100.0 * 10.0
                            auto_input.append((_near_max, self.color_near_max))
                            auto_input.append((_max, self.color_max))
                        if _crit:
                            _near_crit = _crit - _crit / 100.0 * 10.0
                            auto_input.append((_near_crit, self.color_near_crit))
                            auto_input.append((_crit, self.color_crit))

                    key = "{}/{}".format(chip["name"], sensor["name"])
                    self.py3.threshold_get_color(
                        _input, ("auto.input", key, auto_input)
                    )

                for x in self.sensor_placeholders:
                    if x not in sensor:
                        sensor[x] = None

                new_sensor.append(self.py3.safe_format(self.format_sensor, sensor))

            format_sensor_separator = self.py3.safe_format(self.format_sensor_separator)
            format_sensor = self.py3.composite_join(format_sensor_separator, new_sensor)

            chip["format_sensor"] = format_sensor
            del chip["sensors"]

            new_chip.append(self.py3.safe_format(self.format_chip, chip))

        format_chip_separator = self.py3.safe_format(self.format_chip_separator)
        format_chip = self.py3.composite_join(format_chip_separator, new_chip)
        self.first_run = False

        return {
            "cached_until": self.py3.time_in(self.cache_timeout),
            "full_text": self.py3.safe_format(
                self.format, {"format_chip": format_chip}
            ),
        }


if __name__ == "__main__":
    """
    Run module in test mode.
    """
    from py3status.module_test import module_test

    module_test(Py3status)
