#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2017             mk@mathias-kettner.de |
# +------------------------------------------------------------------+
#
# This file is part of Check_MK.
# The official homepage is at http://mathias-kettner.de/check_mk.
#
# check_mk 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 in version 2.  check_mk is  distributed
# in the hope that it will be useful, but WITHOUT ANY WARRANTY;  with-
# out even the implied warranty of  MERCHANTABILITY  or  FITNESS FOR A
# PARTICULAR PURPOSE. See the  GNU General Public License for more de-
# tails. You should have  received  a copy of the  GNU  General Public
# License along with GNU Make; see the file  COPYING.  If  not,  write
# to the Free Software Foundation, Inc., 51 Franklin St,  Fifth Floor,
# Boston, MA 02110-1301 USA.


def parse_emka_modules(info):
    # basModuleCoIx == 0
    map_module_types = {
        "0": "vacant",
        "8": "U8, keypad",
        "9": "U9, card module (proximity)",
        "10": "U10, phone module (modem)",
        "11": "U11/U32, up to 8 handles / single point latches",
        "12": "U12/U33, up to 2 handles / single point latches",
        "13": "U13, 4 sensors and 4 relays",
        "14": "U14, communication module",
        "15": "fultifunction module M15",
        "16": "fultifunction module M16",
    }

    # independent of module type, basModuleCoIx > 0
    map_component_types = {
        "1": "alarm",
        "2": "handle",
        "3": "sensor",
        "4": "relay",
        "5": "keypad",
        "6": "card_terminal",
        "7": "phone_modem",
        "8": "analogous_output",
    }

    parsed = {"basic_components": {}}
    for oidend, status, ty, mod_info, remark in info[0]:
        mo_index, co_index = oidend.split(".")
        if mo_index == "0":
            itemname = "Master %s" % mod_info.split(",")[0]
        else:
            itemname = "Perip %s %s" % (mo_index, mod_info)

        if co_index == "0":
            parsed["basic_components"].setdefault(
                itemname.strip(), {
                    "type": map_module_types[co_index],
                    "activation": status,
                    "_location_": "0.%s" % mo_index,
                })
            continue

        table = map_component_types[ty]
        if remark == "":
            itemname = oidend
        else:
            itemname = "%s %s" % (remark, oidend)

        parsed.setdefault(table, {})
        parsed[table].setdefault(itemname, {"_location_": oidend})

    for oidend, module_link, value, mode in info[1]:
        table = map_component_types[oidend.split(".", 1)[0]]
        location = ".".join(module_link.split(".")[-2:])
        for entry, attrs in parsed.get(table, {}).items():
            item_location = attrs["_location_"]
            if item_location != location:
                continue

            attrs["value"] = value
            if mode:
                attrs["mode"] = mode

    for oidend, threshold in info[2]:
        location, threshold_ty = oidend.split(".")
        if threshold_ty == "1":
            ty = "levels_lower"
        else:
            ty = "levels"

        for entry, attrs in parsed.get("sensor", {}).items():
            if attrs["_location_"].startswith("%s." % location):
                attrs[ty] = (threshold, threshold)

    # Explanation from ELM2-MIB:
    # Empty string -> default, that means value in mV.
    # Universal: {[factor]}[unit]=$mV*[multiplicator]/[divisor]+[offset]
    # [multiplicator], [divisor], [offset] must be integers
    # Example: {0.1}%=$mV*20/100-100
    # default: {1}mV=$mV*1/1+0"
    # From the walk we get an ascii coded list separated by null bytes.
    # Results in: "=#\xb0C0.02-30.0" where
    # \xb0C => Temperature
    # 0.02  => 2/100 [multiplicator]/[divisor]
    # -30.0          [offset]
    # Notice, may also "=#\xb0C0.0230.0"
    sensors = parsed.get("sensor", {})
    for oidend, equation_bin in info[3]:
        equation = []
        part = []
        for entry in equation_bin:
            if entry:
                part.append(entry)
            elif part:
                equation.append("".join(map(chr, part)))
                part = []

        if not equation:
            continue

        if equation[0].endswith("#\xb0C"):
            sensor_ty = "sensor_temp"
        elif equation[0].endswith("#%RF"):
            sensor_ty = "sensor_humid"
        else:
            sensor_ty = "sensor_volt"

        equation = equation[1:]
        if len(equation) == 2:
            m, a = map(float, equation)
        else:
            m, a = 1.0, 0.0

        def scale_f(x, m=m, a=a):
            return float(x) * m + a

        location = str(chr(int(oidend.split('.', 1)[0])))
        for sensor, attrs in parsed.get("sensor", {}).items():
            if attrs["_location_"].endswith(".%s" % location):
                parsed.setdefault(sensor_ty, {})
                parsed[sensor_ty].setdefault(
                    sensor, {
                        "value": scale_f(attrs["value"]),
                        "levels": map(scale_f, attrs["levels"]),
                        "levels_lower": map(scale_f, attrs["levels_lower"]),
                    })
                break

    # Cleanup
    if "sensor" in parsed:
        del parsed["sensor"]

    return parsed


#   .--component-----------------------------------------------------------.
#   |                                                         _            |
#   |         ___ ___  _ __ ___  _ __   ___  _ __   ___ _ __ | |_          |
#   |        / __/ _ \| '_ ` _ \| '_ \ / _ \| '_ \ / _ \ '_ \| __|         |
#   |       | (_| (_) | | | | | | |_) | (_) | | | |  __/ | | | |_          |
#   |        \___\___/|_| |_| |_| .__/ \___/|_| |_|\___|_| |_|\__|         |
#   |                           |_|                                        |
#   +----------------------------------------------------------------------+
#   |                              main check                              |
#   '----------------------------------------------------------------------'


def inventory_emka_modules(parsed):
    for entry, attrs in parsed["basic_components"].items():
        if attrs["activation"] != "i":
            yield entry, None


def check_emka_modules(item, params, parsed):
    map_activation_states = {
        '-': (0, "vacant"),
        '?': (0, "detect modus"),
        'x': (0, "excluded"),
        'e': (2, "error"),
        'c': (2, "collision detected"),
        'w': (1, "wait for dynamic address"),
        'P': (1, "polling"),
        'i': (0, "inactive"),
        't': (2, "timeout"),
        'T': (2, "timeout alarm"),
        'A': (2, "alarm active"),
        'L': (0, "alarm latched"),
        '#': (0, "OK"),
    }

    if item in parsed["basic_components"]:
        attrs = parsed["basic_components"][item]
        state, state_readable = map_activation_states[attrs["activation"]]
        return state, 'Activation status: %s, Type: %s' % \
               (state_readable, attrs["type"])


check_info['emka_modules'] = {
    'parse_function'        : parse_emka_modules,
    'inventory_function'    : inventory_emka_modules,
    'check_function'        : check_emka_modules,
    'service_description'   : 'Module %s',
    'snmp_info'             : [('.1.3.6.1.4.1.13595.2.1.3.3.1', [
                                    OID_END,
                                    "3",    # ELM2-MIB::basModuleStatus
                                    "4",    # ELM2-MIB::basModuleType
                                    "5",    # ELM2-MIB::basModuleInfo
                                    "7",    # ELM2-MIB::basModuleRemark
                               ]),
                                # In this order: alarm, handle, sensor, relay
                               ('.1.3.6.1.4.1.13595.2.2', [ '1', '2', '3', '4' ], [
                                    OID_END,
                                    "1.3",  # ELM2-MIB::coHandleModuleLink
                                    "1.4",  # ELM2-MIB::co*[Status/Value]
                                    "1.15", # ELM2-MIB::coSensorMode
                               ]),
                               ('.1.3.6.1.4.1.13595.2.2.3.1', [
                                    OID_END,
                                    "7",    # ELM2-MIB::coSensorThreshold
                               ]),
                               ('.1.3.6.1.4.1.13595.2.2.3.1', [
                                    OID_END,
                                    BINARY("18"),   # ELM2-MIB::coSensorScaling
                              ])],
    'snmp_scan_function'    : lambda oid: "emka" in oid(".1.3.6.1.2.1.1.1.0").lower() and \
                              oid(".1.3.6.1.2.1.1.2.0").startswith(".1.3.6.1.4.1.13595"),
}


#.
#   .--alarm---------------------------------------------------------------.
#   |                          _                                           |
#   |                     __ _| | __ _ _ __ _ __ ___                       |
#   |                    / _` | |/ _` | '__| '_ ` _ \                      |
#   |                   | (_| | | (_| | |  | | | | | |                     |
#   |                    \__,_|_|\__,_|_|  |_| |_| |_|                     |
#   |                                                                      |
#   '----------------------------------------------------------------------'


def inventory_emka_modules_alarm(parsed):
    for entry, attrs in parsed.get("alarm", {}).items():
        if attrs["value"] != "2":
            yield entry, None


def check_emka_modules_alarm(item, params, parsed):
    map_states = {
        "1": (3, "unknown"),
        "2": (0, "inactive"),
        "3": (2, "active"),
        "4": (0, "latched"),
    }

    if item in parsed.get("alarm", {}):
        state, state_readable = map_states[parsed["alarm"][item]["value"]]
        return state, "Status: %s" % state_readable


check_info["emka_modules.alarm"] = {
    "inventory_function": inventory_emka_modules_alarm,
    "check_function": check_emka_modules_alarm,
    "service_description": "Alarm %s",
}


#.
#   .--handle--------------------------------------------------------------.
#   |                   _                     _ _                          |
#   |                  | |__   __ _ _ __   __| | | ___                     |
#   |                  | '_ \ / _` | '_ \ / _` | |/ _ \                    |
#   |                  | | | | (_| | | | | (_| | |  __/                    |
#   |                  |_| |_|\__,_|_| |_|\__,_|_|\___|                    |
#   |                                                                      |
#   '----------------------------------------------------------------------'


def inventory_emka_modules_handle(parsed):
    for entry, spec in parsed.get("handle", {}).iteritems():
        if 'value' in spec:
            yield entry, None


def check_emka_modules_handle(item, params, parsed):
    map_states = {
        "1": (0, "closed"),
        "2": (1, "opened"),
        "3": (3, "unlocked"),
        "4": (3, "delay"),
        "5": (2, "open time ex")
    }

    if item in parsed.get("handle", {}):
        state, state_readable = map_states[parsed["handle"][item]["value"]]
        return state, "Status: %s" % state_readable


check_info["emka_modules.handle"] = {
    "inventory_function": inventory_emka_modules_handle,
    "check_function": check_emka_modules_handle,
    "service_description": "Handle %s",
}


#.
#   .--voltage-------------------------------------------------------------.
#   |                             _ _                                      |
#   |                 __   _____ | | |_ __ _  __ _  ___                    |
#   |                 \ \ / / _ \| | __/ _` |/ _` |/ _ \                   |
#   |                  \ V / (_) | | || (_| | (_| |  __/                   |
#   |                   \_/ \___/|_|\__\__,_|\__, |\___|                   |
#   |                                        |___/                         |
#   '----------------------------------------------------------------------'


def inventory_emka_modules_sensor_volt(parsed):
    for entry in parsed.get("sensor_volt", {}):
        yield entry, {}


def check_emka_modules_sensor_volt(item, params, parsed):
    scale_f = lambda x: float(x) / 1000
    if item in parsed.get("sensor_volt", {}):
        attrs = parsed["sensor_volt"][item]
        value = attrs["value"] / 1000
        return check_elphase(item, params, {item: {"voltage": value}})


check_info["emka_modules.sensor_volt"] = {
    "inventory_function": inventory_emka_modules_sensor_volt,
    "check_function": check_emka_modules_sensor_volt,
    "service_description": "Phase %s",
    "includes": ["elphase.include"],
    "group": "el_inphase",
    "has_perfdata": True,
}


#.
#   .--temperature---------------------------------------------------------.
#   |      _                                      _                        |
#   |     | |_ ___ _ __ ___  _ __   ___ _ __ __ _| |_ _   _ _ __ ___       |
#   |     | __/ _ \ '_ ` _ \| '_ \ / _ \ '__/ _` | __| | | | '__/ _ \      |
#   |     | ||  __/ | | | | | |_) |  __/ | | (_| | |_| |_| | | |  __/      |
#   |      \__\___|_| |_| |_| .__/ \___|_|  \__,_|\__|\__,_|_|  \___|      |
#   |                       |_|                                            |
#   '----------------------------------------------------------------------'


def inventory_emka_modules_sensor_temp(parsed):
    for entry in parsed.get("sensor_temp", {}):
        yield entry, {}


def check_emka_modules_sensor_temp(item, params, parsed):
    if item in parsed.get("sensor_temp", {}):
        attrs = parsed["sensor_temp"][item]
        value = attrs["value"]
        return check_temperature(value,
                                 params,
                                 "emka_modules_sensor_temp.%s" % item,
                                 dev_levels=attrs["levels"],
                                 dev_levels_lower=attrs["levels_lower"])


check_info["emka_modules.sensor_temp"] = {
    "inventory_function": inventory_emka_modules_sensor_temp,
    "check_function": check_emka_modules_sensor_temp,
    "service_description": "Temperature %s",
    "includes": ["temperature.include"],
    "group": "temperature",
    "has_perfdata": True,
}


#.
#   .--humidity------------------------------------------------------------.
#   |              _                     _     _ _ _                       |
#   |             | |__  _   _ _ __ ___ (_) __| (_) |_ _   _               |
#   |             | '_ \| | | | '_ ` _ \| |/ _` | | __| | | |              |
#   |             | | | | |_| | | | | | | | (_| | | |_| |_| |              |
#   |             |_| |_|\__,_|_| |_| |_|_|\__,_|_|\__|\__, |              |
#   |                                                  |___/               |
#   '----------------------------------------------------------------------'


def inventory_emka_modules_sensor_humid(parsed):
    for entry in parsed.get("sensor_humid", {}):
        yield entry, {}


def check_emka_modules_sensor_humid(item, params, parsed):
    if item in parsed.get("sensor_humid", {}):
        attrs = parsed["sensor_humid"][item]
        value = attrs["value"]
        return check_humidity(value, params)


check_info["emka_modules.sensor_humid"] = {
    "inventory_function": inventory_emka_modules_sensor_humid,
    "check_function": check_emka_modules_sensor_humid,
    "service_description": "Humidity %s",
    "includes": ["humidity.include"],
    "group": "humidity",
    "has_perfdata": True,
}


#.
#   .--relay---------------------------------------------------------------.
#   |                                _                                     |
#   |                       _ __ ___| | __ _ _   _                         |
#   |                      | '__/ _ \ |/ _` | | | |                        |
#   |                      | | |  __/ | (_| | |_| |                        |
#   |                      |_|  \___|_|\__,_|\__, |                        |
#   |                                        |___/                         |
#   '----------------------------------------------------------------------'


def inventory_emka_modules_relay(parsed):
    for entry, attrs in parsed.get("relay", {}).items():
        if attrs["value"] != "1":
            yield entry, None


def check_emka_modules_relay(item, params, parsed):
    map_states = {
        "1": (0, "off"),
        "2": (0, "on"),
    }

    if item in parsed.get("relay", {}):
        state, state_readable = map_states[parsed["relay"][item]["value"]]
        return state, "Status: %s" % state_readable


check_info["emka_modules.relay"] = {
    "inventory_function": inventory_emka_modules_relay,
    "check_function": check_emka_modules_relay,
    "service_description": "Relay %s",
}
