#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2014             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.


# Example SNMP walk:
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.1 Temperature.DescName
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.2 Temperature.Value
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.3 Temperature.Offset
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.4 Temperature.SetPtHighAlarm
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.5 Temperature.SetPtHighWarning
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.6 Temperature.SetPtLowWarning
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.7 Temperature.SetPtLowAlarm
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.8 Temperature.Hysteresis
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.9 Temperature.Status
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.10 Temperature.Category
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.11 Access.DescName
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.12 Access.Value
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.13 Access.Sensitivity
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.14 Access.Delay
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.15 Access.Status
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.16 Access.Category
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.17 Input 1.DescName
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.18 Input 1.Value
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.19 Input 1.Logic
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.20 Input 1.Delay
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.21 Input 1.Status
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.22 Input 1.Category
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.23 Input 2.DescName
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.24 Input 2.Value
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.25 Input 2.Logic
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.26 Input 2.Delay
# .1.3.6.1.4.1.2606.7.4.2.2.1.3.1.27 Input 2.Status
# ...
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.1 Temperature
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.2 31.00 <B0>C
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.3 -3.70 <B0>C
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.4 50.00 <B0>C
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.5 40.00 <B0>C
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.6 10.00 <B0>C
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.7 5.00 <B0>C
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.8 0.10 %
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.9 OK
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.10 0
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.11 Door
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.12 0
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.13 0
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.14 10 s
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.15 Closed
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.16 0
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.17 Input_1
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.18 1
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.19 0:Off / 1:On
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.20 0.5 s
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.21 On
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.22 0
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.23 Input_2
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.24 0
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.25 0:Off / 1:On
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.26 0.5 s
# .1.3.6.1.4.1.2606.7.4.2.2.1.10.1.27 Off

# .1.3.6.1.4.1.2606.7.4.1.2.1.2.1 CMCIII-PU
# .1.3.6.1.4.1.2606.7.4.1.2.1.2.2 CMCIII-GAT
# .1.3.6.1.4.1.2606.7.4.1.2.1.2.3 CMCIII-GAT
# .1.3.6.1.4.1.2606.7.4.1.2.1.2.4 CMCIII-IO3
# .1.3.6.1.4.1.2606.7.4.1.2.1.2.5 CMCIII-SEN
# .1.3.6.1.4.1.2606.7.4.1.2.1.2.6 CMCIII-SEN
# .1.3.6.1.4.1.2606.7.4.1.2.1.2.7 CMCIII-SEN
# .1.3.6.1.4.1.2606.7.4.1.2.1.2.8 CMCIII-ACC
# .1.3.6.1.4.1.2606.7.4.1.2.1.2.9 CMCIII-ACC
# .1.3.6.1.4.1.2606.7.4.1.2.1.2.10 CMCIII-ACC
# .1.3.6.1.4.1.2606.7.4.1.2.1.2.11 CMCIII-ACC
# .1.3.6.1.4.1.2606.7.4.1.2.1.3.1 CMCIII-PU
# .1.3.6.1.4.1.2606.7.4.1.2.1.3.2 CAN_BUS_UNIT_I
# .1.3.6.1.4.1.2606.7.4.1.2.1.3.3 CAN_BUS_UNIT_II
# .1.3.6.1.4.1.2606.7.4.1.2.1.3.4 CMCIII-IO3
# .1.3.6.1.4.1.2606.7.4.1.2.1.3.5 Access Rack_1
# .1.3.6.1.4.1.2606.7.4.1.2.1.3.6 Access Rack_2
# .1.3.6.1.4.1.2606.7.4.1.2.1.3.7 Access Rack_3
# .1.3.6.1.4.1.2606.7.4.1.2.1.3.8 Left Door
# .1.3.6.1.4.1.2606.7.4.1.2.1.3.9 Right Door
# .1.3.6.1.4.1.2606.7.4.1.2.1.3.10 Side Exit
# .1.3.6.1.4.1.2606.7.4.1.2.1.3.11 Emergency Exit
#

# Example for info:
# [['1.1', 'Temperature.DescName', 'Temperature'],
# ['1.2', 'Temperature.Value', '31.00 \xb0C'],
# ['1.3', 'Temperature.Offset', '-3.70 \xb0C'],
# ['1.4', 'Temperature.SetPtHighAlarm', '50.00 \xb0C'],
# ['1.5', 'Temperature.SetPtHighWarning', '40.00 \xb0C'],
# ['1.6', 'Temperature.SetPtLowWarning', '10.00 \xb0C'],
# ['1.7', 'Temperature.SetPtLowAlarm', '5.00 \xb0C'],
# ['1.8', 'Temperature.Hysteresis', '0.10 %'],
# ['1.9', 'Temperature.Status', 'OK'],
# ['1.10', 'Temperature.Category', '0'],
# ['1.11', 'Access.DescName', 'Door'],
# ['1.12', 'Access.Value', '0'],
# ['1.13', 'Access.Sensitivity', '0'],
# ['1.14', 'Access.Delay', '10 s'],
# ['1.15', 'Access.Status', 'Closed'],
# ['1.25', 'Input 2.Logic', '0:Off / 1:On'],
# ['1.26', 'Input 2.Delay', '0.5 s'],
# ['1.27', 'Input 2.Status', 'Off'],
# ['1.28', 'Input 2.Category', '0'],
# ['1.29', 'Output.DescName', 'Alarm Relay'],
# ['1.30', 'Output.Relay', 'Off'],
# ['1.31', 'Output.Logic', '0:Off / 1:On'],
# ['1.32', 'Output.Status', 'Off'],
# ['1.33', 'Output.Category', '0'],
# ['1.34', 'System.V24 Port.DescName', 'V24 Unit'],
# ['1.35', 'System.V24 Port.Device', 'NONE'],
# ['1.36', 'System.V24 Port.Message', '--'],

# Convert info dictionary:

# {('1', 'Access'): {'Category': '0',
#                    'Delay': '10 s',
#                    'DescName': 'Door',
#                    'Sensitivity': '0',
#                    'Status': 'Closed',
#                    'Value': '0'},
#  ('1', 'Input 1'): {'Category': '0',
#                     'Delay': '0.5 s',
#                     'DescName': 'Input_1',
#                     'Logic': '0:Off / 1:On',
#                     'Status': 'On',
#                     'Value': '1'},
#  ('1', 'Input 2'): {'Category': '0',
#                     'Delay': '0.5 s',
#                     'DescName': 'Input_2',
#                     'Logic': '0:Off / 1:On',
#                     'Status': 'Off',
#                     'Value': '0'},


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


def parse_cmciii_inputs(info_inputs):
    unit_items = {}
    temperatures_group = None
    for endoid, varname, value in info_inputs:
        unit, eid = endoid.split(".") # "1.8" -> "1"
        item_name, key = varname.rsplit(".", 1)
        item = (unit, item_name)

        if temperatures_group:
            if item_name.endswith("Temperature"):
                unit_items[item][key] = value
            elif item_name.endswith("-In"):
                unit_items[temperatures_group].setdefault("In", {})[key] = value
            elif item_name.endswith("-Out"):
                unit_items[temperatures_group].setdefault("Out", {})[key] = value
            else:
                temperatures_group = None

        if not temperatures_group:
            unit_items.setdefault(item, {})[key] = value

        if key == "DescName" and value.endswith("Temperatures"):
            temperatures_group = item
    return unit_items


def parse_cmciii_units(info_units):
    units = []
    num = 0
    for unit_type, descname, state in info_units:
        num =+ 1
        # no blanks in names since we use blanks in items
        # later to split between unit_name and item_name
        descname = descname.replace(" ", "_")
        if not descname:
            descname = unit_type + "-" + str(num)
        units.append((unit_type, descname, state))
    return units


def parse_cmciii(info):
    return parse_cmciii_inputs(info[0]), parse_cmciii_units(info[1])


def inventory_cmciii_state(parsed):
    for unit_type, unit_name, state in parsed[1]:
        yield unit_name, None


def check_cmciii_state(item, params, parsed):
    units = parsed[1]
    unit_number = cmciii_get_unit_number(units, item)
    entry = units[int(unit_number) - 1]
    states = {
       '1' : (3, "not available"),
       '2' : (0, "OK"),
       '3' : (1, "detect"),
       '4' : (2, "lost"),
       '5' : (0, "changed"),
       '6' : (2, "error"),
    }
    status, status_text = states[entry[2]]

    return status, "Unit state is %s" % status_text


check_info['cmciii'] = {
    "check_function"      : check_cmciii_state,
    "inventory_function"  : inventory_cmciii_state,
    "service_description" : "State %s",
    "parse_function"      : parse_cmciii,
    "snmp_scan_function"  : lambda oid: ".1.3.6.1.4.1.2606.7" in oid(".1.3.6.1.2.1.1.2.0"),
    "includes"            : [ "cmciii.include" ],
    "snmp_info"           : [
        [ ".1.3.6.1.4.1.2606.7.4.2.2.1", [
            OID_END,
            3, # Variable names in this subtree, e.g. Temperature.SetPtHighAlarm
            10, # Actual values in this subtree
            ]
        ],
        [ ".1.3.6.1.4.1.2606.7.4.1.2.1", [
            2, # Type of unit
            3, # Descriptive name of unit
            6, # State
            ]
        ]
    ]
}

#.
#   .--psm current---------------------------------------------------------.
#   |                                                           _          |
#   |      _ __  ___ _ __ ___     ___ _   _ _ __ _ __ ___ _ __ | |_        |
#   |     | '_ \/ __| '_ ` _ \   / __| | | | '__| '__/ _ \ '_ \| __|       |
#   |     | |_) \__ \ | | | | | | (__| |_| | |  | | |  __/ | | | |_        |
#   |     | .__/|___/_| |_| |_|  \___|\__,_|_|  |_|  \___|_| |_|\__|       |
#   |     |_|                                                              |
#   +----------------------------------------------------------------------+


def inventory_cmciii_psm_current(parsed):
    unit_items, units = parsed
    for (unit, item_name), entry in unit_items.iteritems():
        unit_type, unit_name = units[int(unit)-1][0:2]
        if item_name.startswith("PSM_") and item_name.endswith(".Unit"):
            item_name = item_name[:-5]
            yield "%s %s" % (unit_name, item_name), None


def check_cmciii_psm_current(item, params, parsed):
    unit_name, item_name = item.split(" ", 1)
    if item_name.endswith(".Current"):
        #support for items dicovered in older check_mk versions
        item_name = item_name[:-8]

    item_name += ".Unit"
    unit_items, units = parsed
    unit = cmciii_get_unit_number(units, unit_name)
    entry = unit_items.get((unit, item_name))
    if entry:
        descr       = entry['DescName']
        typ         = entry['Unit Type']
        status      = entry['Status']
        current     = entry['Value'].split()[0]
        serial      = entry['Serial Number']
        position    = entry['Mounting Position']
        max_current = float(entry['SetPtHighAlarm'].split()[0])
        min_current = float(entry['SetPtLowAlarm'].split()[0])
        if status == "OK":
            state = 0
            statind = ""
        else:
            state = 2
            statind = status + " "

        perfdata = [ ( "current", current, 0, 0, min_current, max_current) ]

        infotext =  "%s%s: Current %s (%s/%s), Type %s, Serial %s, Position %s" \
            % ( statind, descr, current, min_current, max_current, typ, serial, position)

        return (state, infotext, perfdata)


check_info['cmciii.psm_current'] = {
    "check_function"      : check_cmciii_psm_current,
    "inventory_function"  : inventory_cmciii_psm_current,
    "has_perfdata"        : True,
    "service_description" : "Current %s",
}

#.
#   .--psm plugs-----------------------------------------------------------.
#   |                                        _                             |
#   |            _ __  ___ _ __ ___    _ __ | |_   _  __ _ ___             |
#   |           | '_ \/ __| '_ ` _ \  | '_ \| | | | |/ _` / __|            |
#   |           | |_) \__ \ | | | | | | |_) | | |_| | (_| \__ \            |
#   |           | .__/|___/_| |_| |_| | .__/|_|\__,_|\__, |___/            |
#   |           |_|                   |_|            |___/                 |
#   +----------------------------------------------------------------------+

def inventory_cmciii_psm_plugs(parsed):
    inventory = []
    unit_items, units = parsed
    plug_re = regex(r'^PSM_.*\.Plug\d$')
    for (unit, item_name), entry in unit_items.iteritems():
        unit_type, unit_name = units[int(unit)-1][0:2]
        if plug_re.match(item_name):
            inventory.append(  ("%s %s" % (unit_name, item_name), None)  )
    return inventory

def check_cmciii_psm_plugs(item, params, parsed):
    unit_name, item_name = item.split(" ", 1)
    unit_items, units = parsed
    unit = cmciii_get_unit_number(units, unit_name)
    entry = unit_items.get((unit, item_name))
    if not entry:
        return 3, "No such PSM_Plug found"

    descr = entry.get('DescName')
    status = entry.get('Status')
    if status == "OK":
        state = 0
    else:
        state = 2

    infotext =  "%s: %s" % ( descr, status)

    return (state, infotext)

check_info['cmciii.psm_plugs'] = {
    "check_function"      : check_cmciii_psm_plugs,
    "inventory_function"  : inventory_cmciii_psm_plugs,
    "service_description" : "%s",
}

#.
#   .--IO------------------------------------------------------------------.
#   |                              ___ ___                                 |
#   |                             |_ _/ _ \                                |
#   |                              | | | | |                               |
#   |                              | | |_| |                               |
#   |                             |___\___/                                |
#   |                                                                      |
#   +----------------------------------------------------------------------+

def inventory_cmciii_io(parsed):
    inventory = []
    unit_items, units = parsed

    for (unit, item_name), entry in unit_items.iteritems():
        unit_type, unit_name = units[int(unit)-1][0:2]
        if unit_type.startswith('CMCIII-IO') and\
                (item_name.startswith('Input') or item_name.startswith('Output')):
            inventory.append(  ("%s %s" % (unit_name, item_name), None)  )

    return inventory

def check_cmciii_io(item, params, parsed):
    unit_name, item_name = item.split(" ", 1)
    unit_items, units = parsed
    unit = cmciii_get_unit_number(units, unit_name)
    entry = unit_items.get((unit, item_name))
    if not entry:
        return 3, "No such IO channel found"

    descr = entry.get('DescName')
    status = entry.get('Status')
    value = entry.get('Value')
    logic = entry.get('Logic')
    relay = entry.get('Relay')
    delay = entry.get('Delay')
    grouping = entry.get('Grouping')

    if relay: # output relay
        if status == "OK":
            state = 0
            sym = ""
        else:
            state = 2
            sym = "(!!)"
        infotext =  "%s: %s%s, Relay %s, Grouping %s, Logic %s" % \
                         ( descr, status, sym, relay, grouping, logic)
    else: # input relay
        if status == "OK":
            state = 0
            sym = ""
        elif status == "Off":
            state = 0
            sym = ""
        elif status == "On":
            state = 1
            sym = "(!)"
        else:
            state = 2
            sym = "(!!)"
        infotext =  "%s: %s, Status %s%s, Logic %s, Delay %s" % \
                        ( descr, status, value, sym, logic, delay)

    return (state, infotext)

check_info['cmciii.io'] = {
    "check_function"      : check_cmciii_io,
    "inventory_function"  : inventory_cmciii_io,
    "service_description" : "%s",
}

#.
#   .--access--------------------------------------------------------------.
#   |                                                                      |
#   |                      __ _  ___ ___ ___  ___ ___                      |
#   |                     / _` |/ __/ __/ _ \/ __/ __|                     |
#   |                    | (_| | (_| (_|  __/\__ \__ \                     |
#   |                     \__,_|\___\___\___||___/___/                     |
#   |                                                                      |
#   +----------------------------------------------------------------------+

def inventory_cmciii_access(parsed):
    unit_items, units = parsed
    for (unit, item_name), entry in unit_items.iteritems():
        unit_type, unit_name = units[int(unit)-1][0:2]
        if item_name == "Access":
            yield unit_name + " Access", None

def check_cmciii_access(item, params, parsed):
    unit_items, units = parsed
    unit_name = item.replace(' Access', '')
    unit = cmciii_get_unit_number(units, unit_name)
    entry = unit_items.get((unit, "Access"))
    if not entry:
        return 3, "No such Access data found"

    descr = entry.get('DescName')
    status = entry.get('Status')
    value = entry.get('Value')
    delay = entry.get('Delay')
    sensitivity = entry.get('Sensitivity')

    if status == "Closed":
        state = 0
        sym = ""
    else:
        state = 2
        sym = "(!!)"

    infotext =  "%s %s%s, Value %s, Delay %s, Sens. %s " % \
                         ( descr, status, sym, value, delay, sensitivity)

    return (state, infotext)

check_info['cmciii.access'] = {
    "check_function"      : check_cmciii_access,
    "inventory_function"  : inventory_cmciii_access,
    "service_description" : "%s",
}

#.
#   .--temp----------------------------------------------------------------.
#   |                       _                                              |
#   |                      | |_ ___ _ __ ___  _ __                         |
#   |                      | __/ _ \ '_ ` _ \| '_ \                        |
#   |                      | ||  __/ | | | | | |_) |                       |
#   |                       \__\___|_| |_| |_| .__/                        |
#   |                                        |_|                           |
#   +----------------------------------------------------------------------+


# generate a name for a cmciii temperature item.
# There are a few conditions the name has to fulfill:
# a) It has to be presentable to the user
# b) It has to be unique
# c) If direction and spot are set, they have to be included in the name unmodified
#    and as separate words
# d) The word "Temperature" mustn't be included
# e) the output name should start with a specification of the temperature
#     "zone" (i.e. Ambient, CPU, System, External, ...)
def format_cmciii_temp_name(unit_name, item_name, direction=None, spot=None):
    unit_name = unit_name.replace('Liquid_Cooling_Package', 'LCP')
    item_name = item_name.replace('Temperature', '').replace('.', ' ').strip(' .-')
    for d in [ "In", "Out" ]:
        if " " + d in item_name:
            direction = d
            item_name = item_name.replace(" " + d, "")
    if not item_name:
        item_name = "Ambient"
    if direction is not None:
        location = " %s %s" % (direction, spot or "")
    else:
        location = ""
    result = "%s %s%s" % (item_name, unit_name, location)

    return result.strip()


def inventory_cmciii_temp(parsed):
    entries, units = parsed
    for (unit, item_name), entry in entries.iteritems():
        unit_type, unit_name = units[int(unit)-1][0:2]
        if item_name.endswith("Temperature") and "Value" in entry.keys():
            yield format_cmciii_temp_name(unit_name, item_name), {}


def translate_cmciii_temp_status(status_name):
    return {
        "ok"       : 0,
        "warning"  : 1,
        "low warn" : 1,
        "high warn": 1,
        "too low"  : 2,
        "too high" : 2,
    }[status_name.lower()]


def parse_cmciii_temp(val):
    # TODO: This hack should not be neccessary anymore. Might just be
    # due to a few garbled stored walks in our archive.
    if " B0 " in val:
        # is encoded string
        val = val.replace(" ", "").decode("hex")
    val, t_unit = val.split(" ", 1)
    # omit special characters in unit, because they
    # cause problems in rrdtools
    regchar_re = regex(r'[^\w]')
    t_unit = regchar_re.sub("", t_unit)
    return float(val), t_unit


def check_cmciii_temp(item, params, unit_items):
    if params == None:
        params = {} # compatibility to "old discovered" services
    entries, units = unit_items
    entry = None
    unit_name = None

    for (unit, item_name), candidate_entry in entries.iteritems():
        unit_type, unit_name = units[int(unit) - 1][0:2]
        if item_name.endswith("Temperature") and "Value" in candidate_entry.keys():
            if format_cmciii_temp_name(unit_name, item_name) == item \
               or unit_name + " " + item_name == item: # compatibility to "old discovered" services
                entry = candidate_entry
                break
    if entry:
        status = entry['Status']
        offset = entry.get('Offset')  # Offset is indeed optional

        value, t_unit = parse_cmciii_temp(entry['Value'])
        highalarm = parse_cmciii_temp(entry['SetPtHighAlarm'])[0]
        highwarning = parse_cmciii_temp(entry['SetPtHighWarning'])[0]
        lowalarm = parse_cmciii_temp(entry['SetPtLowAlarm'])[0]
        lowwarning = parse_cmciii_temp(entry['SetPtLowWarning'])[0]

        status, message, perfdata = check_temperature(value, params, "cmciii_temp_%s" % item,
                dev_unit=t_unit.lower(),
                dev_levels=(highwarning, highalarm),
                dev_levels_lower=(lowwarning, lowalarm),
                dev_status=translate_cmciii_temp_status(status),
                dev_status_name="Unit: %s" % status)

        if offset:
            # as far as I understand it the offset is a user-configurable
            # value that was already applied to the temperature reading we
            # received.
            # Therefore, to get the actual temperature reading, the calculation
            # would be: real_reading = value - offset_value
            offset_value, offset_unit = parse_cmciii_temp(offset)
            offset_value = float(offset_value)
            # ignore zero-offsets
            if abs(offset_value) > 0.001:
                in_unit = offset_unit.lower()
                out_unit = params.get("output_unit", "c")
                message += " ; Offset: %.1f %s" %\
                    (convert_unit_relative(offset_value, in_unit, out_unit),
                     temp_unitsym[out_unit])

        return status, message, perfdata


check_info['cmciii.temp'] = {
    "check_function"      : check_cmciii_temp,
    "inventory_function"  : inventory_cmciii_temp,
    "service_description" : "Temperature %s",
    "has_perfdata"        : True,
    "group"               : "temperature",
    "includes"            : [ "temperature.include" ]
}


def inventory_cmciii_temp_in_out(parsed):
    unit_items, units = parsed

    for (unit, item_name), entry in unit_items.iteritems():
        unit_type, unit_name = units[int(unit)-1][0:2]
        if item_name.endswith('Temperature'):
            for direction in ["In", "Out"]:
                for spot in ["Bottom", "Middle", "Top"]:
                    if "%s-%s" % (direction, spot[:3]) in entry.keys():
                        yield format_cmciii_temp_name(unit_name, item_name, direction, spot), {}


# check for sensors that produces temperature levels in multiple spots
def check_cmciii_temp_in_out(item, params, parsed):
    unit_items, units = parsed

    if " Out" in item:
        direction = "Out"
    else:
        direction = "In"

    if "Bottom" in item:
        spot = "Bottom"
    elif "Top" in item:
        spot = "Top"
    else:
        spot = "Middle"

    entry = None
    unit_name = None

    for (unit, item_name), candidate_entry in unit_items.iteritems():
        unit_type, unit_name = units[int(unit) - 1][0:2]
        if item_name.endswith("Temperature"):
            if format_cmciii_temp_name(unit_name, item_name, direction, spot) == item:
                entry = candidate_entry
                break

    if entry:
        status = entry['Status']

        value, t_unit = parse_cmciii_temp(entry["%s-%s" % (direction, spot[:3])])
        highalarm     = parse_cmciii_temp(entry[direction]['SetPtHighAlarm'])[0]
        highwarning   = parse_cmciii_temp(entry[direction]['SetPtHighWarning'])[0]
        lowalarm      = parse_cmciii_temp(entry[direction]['SetPtLowAlarm'])[0]
        lowwarning    = parse_cmciii_temp(entry[direction]['SetPtLowWarning'])[0]

        return check_temperature(value, params, "cmciii_temp_in_out_%s" % item,
                dev_unit=t_unit.lower(),
                dev_levels=(highwarning, highalarm),
                dev_levels_lower=(lowwarning, lowalarm),
                dev_status=translate_cmciii_temp_status(status),
                dev_status_name="Unit: %s" % status)

check_info['cmciii.temp_in_out'] = {
    "check_function"      : check_cmciii_temp_in_out,
    "inventory_function"  : inventory_cmciii_temp_in_out,
    "service_description" : "Temperature %s",
    "has_perfdata"        : True,
    "group"               : "temperature",
    "includes"            : [ "temperature.include" ]
}


#.
#   .--can current---------------------------------------------------------.
#   |                                                         _            |
#   |         ___ __ _ _ __     ___ _   _ _ __ _ __ ___ _ __ | |_          |
#   |        / __/ _` | '_ \   / __| | | | '__| '__/ _ \ '_ \| __|         |
#   |       | (_| (_| | | | | | (__| |_| | |  | | |  __/ | | | |_          |
#   |        \___\__,_|_| |_|  \___|\__,_|_|  |_|  \___|_| |_|\__|         |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   |                                                                      |
#   '----------------------------------------------------------------------'
def inventory_cmciii_can_current(parsed):
    inventory = []
    unit_items, units = parsed
    for (unit, item_name), entry in unit_items.iteritems():
        unit_type, unit_name = units[int(unit)-1][0:2]
        if item_name.startswith('System.CAN') and item_name.endswith('Current'):
            inventory.append(  ("%s %s" % (unit_name, item_name), None)  )
    return inventory

def check_cmciii_can_current(item, params, parsed):
    unit_name, item_name = item.split(" ", 1)
    unit_items, units = parsed
    unit = cmciii_get_unit_number(units, unit_name)
    entry = unit_items.get((unit, item_name))
    if not entry:
        return

    descr = entry.get('DescName')
    status = entry.get('Status')
    value = re.sub(r' ','',entry.get('Value'))
    hysteresis = entry.get('Hysteresis')
    highalarm = entry.get('SetPtHighAlarm').split(" ")[0]
    highwarning = entry.get('SetPtHighWarning').split(" ")[0]

    if status == "OK":
        state = 0
        status = ""
    else:
        state = 2

    perfdata = [ ("current", value, highwarning, highalarm ) ]

    infotext =  "%s %s %s - Limits %s/%s, Hysteresis %s" % \
                         ( descr, value, status, highwarning, highalarm, hysteresis)

    return (state, infotext, perfdata)

check_info['cmciii.can_current'] = {
    "check_function"      : check_cmciii_can_current,
    "inventory_function"  : inventory_cmciii_can_current,
    "service_description" : "%s",
    "has_perfdata"        : True,
}

#.
#   .--sensor--------------------------------------------------------------.
#   |                                                                      |
#   |                    ___  ___ _ __  ___  ___  _ __                     |
#   |                   / __|/ _ \ '_ \/ __|/ _ \| '__|                    |
#   |                   \__ \  __/ | | \__ \ (_) | |                       |
#   |                   |___/\___|_| |_|___/\___/|_|                       |
#   |                                                                      |
#   +----------------------------------------------------------------------+
#   |                                                                      |
#   '----------------------------------------------------------------------'

def inventory_cmciii_sensor(parsed):
    inventory = []
    unit_items, units = parsed
    for (unit, item_name), entry in unit_items.iteritems():
        unit_type, unit_name = units[int(unit)-1][0:2]
        if unit_type.startswith('CMCIII-SEN'):
            inventory.append((unit_name+" Sensor", None))
    return inventory

def check_cmciii_sensor(item, params, parsed):
    unit_items, units = parsed
    unit_name = re.sub(r' Sensor','',item)
    unit = cmciii_get_unit_number(units, unit_name)
    entry = unit_items.get((unit, "Input"))
    if not entry:
        return 3, "No such sensor unit found"

    status = entry.get('Status')

    if status == "Closed":
        state = 0
    else:
        state = 2

    return (state, status)

check_info['cmciii.sensor'] = {
    "check_function"      : check_cmciii_sensor,
    "inventory_function"  : inventory_cmciii_sensor,
    "service_description" : "%s",
}

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

def inventory_cmciii_humidity(parsed):
    inventory = []
    unit_items, units = parsed
    for (unit, item_name), entry in unit_items.iteritems():
        unit_type, unit_name = units[int(unit)-1][0:2]
        if item_name == 'Humidity' and unit_type == 'CMCIII-HUM':
            inventory.append(  ("%s %s" % (unit_name, item_name), None)  )

    return inventory

def parse_hum(val):
    return val.split(" ", 1)

def check_cmciii_humidity(item, params, parsed):
    unit_name, item_name = item.split(" ", 1)
    unit_items, units = parsed
    unit = cmciii_get_unit_number(units, unit_name)
    entry = unit_items.get((unit, item_name))
    if not entry:
        return

    descr = entry.get('DescName')
    status = entry.get('Status')
    offset = entry.get('Offset')

    value = parse_hum(entry.get('Value'))[0]
    highalarm = parse_hum(entry.get('SetPtHighAlarm'))[0]
    highwarning = parse_hum(entry.get('SetPtHighWarning'))[0]
    lowalarm = parse_hum(entry.get('SetPtLowAlarm'))[0]
    lowwarning = parse_hum(entry.get('SetPtLowWarning'))[0]

    if status == "OK":
        state = 0
        status = ""
    else:
        state = 2

    perfdata = [ ("hum", value, 0, 0 ) ]

    infotext =  "%s %s%% %s - Limits low %s/%s high %s/%s, Offset %s" % \
                         ( descr, value, status, lowalarm, lowwarning, highwarning, highalarm, offset )

    return (state, infotext, perfdata)

check_info['cmciii.humidity'] = {
    "check_function"      : check_cmciii_humidity,
    "inventory_function"  : inventory_cmciii_humidity,
    "service_description" : "%s",
    "has_perfdata"        : True,
}
#.
#   .--phase---------------------------------------------------------------.
#   |                           _                                          |
#   |                     _ __ | |__   __ _ ___  ___                       |
#   |                    | '_ \| '_ \ / _` / __|/ _ \                      |
#   |                    | |_) | | | | (_| \__ \  __/                      |
#   |                    | .__/|_| |_|\__,_|___/\___|                      |
#   |                    |_|                                               |
#   '----------------------------------------------------------------------'

def reparse_cmciii_phase(parsed):
    inputs, units = parsed

    output = {}
    for name, value in inputs.items():
        if name[1].find("Phase") != -1:
            parts = name[1].split('.')
            if len(parts) == 3:
                device, phase, value_name = parts
            else:
                device, phase, value_name, value_name_add = parts
                value_name = (value_name, value_name_add)

            unit_id = int(name[0])
            unit    = units[unit_id-1][1]
            phase   = phase[-1]

            output.setdefault((unit, device, phase), {})
            output[(unit, device, phase)][value_name] = value



    return output


def inventory_cmciii_phase(parsed):
    parsed = reparse_cmciii_phase(parsed)
    for device, unit, phase in parsed.keys():
        yield "%s %s Phase %s" % (device, unit, phase), {}


def check_cmciii_phase(item, params, parsed):
    device, unit, word, phase = item.split()
    data = reparse_cmciii_phase(parsed)[(device, unit, phase)]

    parsed = {}
    parsed[item] = {}
    for key, values in data.items():
        if values.get("Value"):
            if type(key) == tuple:
                phase_key = key[0].lower()
            else:
                phase_key = key.lower()
            parsed[item][phase_key] = float(data[key]['Value'].split()[0]), None

    return check_elphase(item, params, parsed)


check_info['cmciii.phase'] = {
    "check_function"      : check_cmciii_phase,
    "inventory_function"  : inventory_cmciii_phase,
    "service_description" : "Input %s",
    "has_perfdata"        : True,
    "includes"            : [ "elphase.include" ],
    "group"               : "el_inphase",
}
#.
