#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2019 tribe29 GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.

# NOTE: Careful when replacing the *-import below with a more specific import. This can cause
# problems because it might remove variables from the check-context which are necessary for
# resolving legacy discovery results such as [("SUMMARY", "diskstat_default_levels")]. Furthermore,
# it might also remove variables needed for accessing discovery rulesets.
from cmk.base.check_legacy_includes.temperature import *  # pylint: disable=wildcard-import,unused-wildcard-import

# <<<lnx_thermal>>>
# thermal_zone0 enabled acpitz 57000 127000 critical
# thermal_zone1 enabled acpitz 65000 100000 critical 95500 passive

# <<<lnx_thermal>>>
# thermal_zone0 enabled acpitz 47000 90000 critical 79000 passive

# <<<lnx_thermal>>>
# thermal_zone0 enabled acpitz 38000 98000 critical
# thermal_zone1 pkg-temp-0  44000 0 passive 0 passive

factory_settings["lnx_thermal_default_levels"] = {
    "levels": (70.0, 80.0),
    "device_levels_handling": "devdefault",
}


def parse_lnx_thermal(info):
    """
    Supported format:
    - Temperature is either the 3rd or 4th element in an info row.
    - After temperature follows pairwise trip point temperature and trip point type.
    - Considered trip points are critical, passive, hot.
    - A known, not considered trip point is active.
    - In case trip point values are 0 or negative (known default values) they are ignored.
    """
    if not info:
        return {}
    parsed = {}
    for line in info:
        temperature, trip_points = _parse_temp_value_str(line)
        if temperature is None:
            continue

        zone_identifier = 0
        zone_raw = line[zone_identifier]
        zone_formatted = zone_raw.replace("thermal_zone", "Zone ")
        parsed[zone_formatted] = {}
        temperature_and_trip_points = ("temperature", "passive", "critical", "hot")
        for key in temperature_and_trip_points:
            parsed[zone_formatted].setdefault(key, None)
        parsed[zone_formatted]["temperature"] = temperature
        for tp_name, tp_value in trip_points.items():
            if tp_name in trip_points and tp_value > 0:
                parsed[zone_formatted][tp_name] = tp_value
            else:
                # ignore trip point types not supported yet
                pass

    return parsed


def _parse_temp_value_str(line):
    for type_idx, temp_idx, trip_point_key_idx, trip_point_value_idx in [
        (1, 2, 4, 3),  # missing mode
        (2, 3, 5, 4),
    ]:
        try:
            unscaled_temp = int(line[temp_idx])
        except (IndexError, ValueError):
            continue

        # From kernel docs (sysfs-api): unit is milli-degrees
        # but on SunXI the unit is degree (may be a BUG)
        if line[type_idx] == "sunxi-therm":
            factor = 1.0
        else:
            factor = 1000.0

        try:
            trip_point_keys = line[trip_point_key_idx::2]
            trip_point_values = [int(x) / factor for x in line[trip_point_value_idx::2]]
            trip_points = dict(zip(trip_point_keys, trip_point_values))
        except (IndexError, ValueError):
            trip_points = {}

        return unscaled_temp / factor, trip_points

    return None, None


def inventory_lnx_thermal(parsed):
    for item in parsed:
        yield item, {}


@get_parsed_item_data
def check_lnx_thermal(item, params, data):
    """
    - Trip points hot and critical are considered for the device crit level. In case both trip
      points are given the lower value is considered for the device crit level.
    - Trip point passive is considered for the device warn level.
    - In case both hot and critical trip points are given the lower trip point value
      is considered for the device crit level.
    - Trip point temperatures are provided via performance data.
    """
    temperature = data.get("temperature")
    warn_level = data.get("passive")
    hot_value = data.get("hot")
    critical_value = data.get("critical")

    if temperature is None:
        return

    if hot_value and critical_value:
        crit_level = min(hot_value, critical_value)
    else:
        crit_level = hot_value or critical_value

    if not crit_level or not warn_level:
        crit_level = crit_level or warn_level
        warn_level = warn_level or crit_level

    if warn_level and crit_level:
        levels = (warn_level, crit_level)
    else:
        levels = None

    return check_temperature(temperature, params, "lnx_thermal_%s" % item, dev_levels=levels)


check_info["lnx_thermal"] = {
    "parse_function": parse_lnx_thermal,
    "inventory_function": inventory_lnx_thermal,
    "check_function": check_lnx_thermal,
    "service_description": "Temperature %s",
    "has_perfdata": True,
    "group": "temperature",
    "default_levels_variable": "lnx_thermal_default_levels",
}
