#!/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.

# Output of old agent (< 1.1.10i2):
# AeLookupSvc        running  Application Experience Lookup Service
# Alerter            stopped  Alerter
# ALG                stopped  Application Layer Gateway Service
# AppMgmt            stopped  Application Management
# appmgr             running  Remote Server Manager

# Output of new agent (>= 1.1.10i2):
# Alerter stopped/disabled Warndienst
# ALG running/demand Gatewaydienst auf Anwendungsebene
# Apple_Mobile_Device running/auto Apple Mobile Device
# AppMgmt stopped/demand Anwendungsverwaltung
# AudioSrv running/auto Windows Audio
# BITS running/demand Intelligenter Hintergrund<FC>bertragungsdienst
# Bonjour_Service running/auto Dienst "Bonjour"

# Deprecated option since 1.6. cmk_base creates a config warning when finding rules
# for this ruleset. Can be dropped with 1.7.
inventory_services = []

# Implemented in 1.2.1i2:
# New rule-style (WATO compatible) notation:
#   [({'start_mode': 'demand', 'service': ['Netman']}, [], ['@all'], {'docu_url': ''})]
#
# <services> is list of regexes matching the service name
# <state> is the expected state to inventorize services of (running, stopped, ...)
# <start_mode> is the expected state to inventorize services of (auto, manual, ...)
#
# All above attributes can be set to None or not set to disable this filter option for the entry.
inventory_services_rules = []


def inventory_windows_services(info):
    # Handle single entries (type str)
    def add_matching_services(name, description, state, start_type, entry):
        if isinstance(entry, tuple):
            # New wato rule handling
            svc = entry[0]
            statespec = entry[1:]

        elif ' ' in entry and len(entry.split()) == 2:
            svc, statespec = entry.split()

        else:
            svc = entry
            statespec = "running"

        # First match name or description (optional since rule based config option available)
        if svc:
            if svc.startswith("~"):
                r = regex(svc[1:])
                if not r.match(name) and not r.match(description):
                    return []
            elif svc != name and svc != description:
                return []

        if isinstance(statespec, tuple):
            # New wato rule handling (always given as tuple of two)
            if (statespec[0] and statespec[0] != state) \
               or (statespec[1] and statespec[1] != start_type):
                return []

        else:
            for n in statespec.split("/"):
                if n not in [state, start_type]:
                    return []

        return [(name, {})]

    # Extract the WATO compatible rules for the current host
    rules = []
    for value in host_extra_conf(host_name(), inventory_services_rules):
        # Now extract the list of service regexes
        svcs = value.get('services', [])
        state = value.get('state', None)
        start_mode = value.get('start_mode', None)
        if svcs:
            for svc in svcs:
                rules.append(('~' + svc, state, start_mode))
        else:
            rules.append((None, state, start_mode))

    inventory = []
    for line in info:
        name = line[1]
        description = " ".join(line[3:])
        if '/' in line[2]:
            state, start_type = line[2].split('/')
        else:
            state = line[2]
            start_type = "unknown"

        for rule in rules:
            inventory += add_matching_services(name, description, state, start_type, rule)

    return inventory


# Format of parameters
# {
#    "states" : [ ( "running", "demand", 1 ),
#                  ( "stopped", None, 2 ) ],
#    "else" : 2,
# }


def check_windows_services(item, params, info):
    # Hack for old manually defined checks:
    if params is None:
        params = factory_settings["services_default_levels"]

    # A service may appear more than once (due to clusters).
    # First make a list of all matching entries with their
    # states
    found = []
    for line in info:
        # allow to match agains the internal name or agains the display name
        # of the service
        display_name = " ".join(line[3:])
        if item == line[1] or item == display_name \
           or line[1] in params['additional_servicenames'] \
           or display_name in params['additional_servicenames']:
            # newer agents also send start type as part of state,
            # e.g. running/auto
            if '/' in line[2]:
                state, start_type = line[2].split('/')
            else:
                state = line[2]
                start_type = "unknown"
            found.append((line[0], state, start_type, " ".join(line[3:])))

    if not found:
        return params["else"], "service not found"

    # We take the best found state (neccessary for clusters)
    best_desc, best_state, best_info, best_running_on = None, None, None, None
    for running_on, state, start_type, desc in found:
        for t_state, t_start_type, mon_state in params["states"]:
            if (t_state is None or t_state == state) \
             and (t_start_type is None or t_start_type == start_type):
                this_state = mon_state
                break
        else:
            this_state = params["else"]

        if best_state is None or this_state < best_state:
            best_state = this_state
            best_info = state, start_type
            best_running_on = running_on
            best_desc = desc

    infotext = best_desc + ": %s (start type is %s)" % best_info
    if best_running_on and best_state != 2:  #if best state ist critical, there should no message "running on"
        infotext += " (running on: %s)" % best_running_on

    return (best_state, infotext)


factory_settings["services_default_levels"] = {
    "states": [("running", None, 0)],
    "else": 2,
    "additional_servicenames": [],
}

check_info['services'] = {
    "check_function": check_windows_services,
    "inventory_function": inventory_windows_services,
    "service_description": "Service %s",
    "node_info": True,
    "group": "services",
    "default_levels_variable": "services_default_levels",
}

factory_settings["services_summary_default_levels"] = {"ignored": [], "state_if_stopped": 0}


def inventory_services_summary(info):
    if info[0]:
        return [(None, "services_summary_default_levels")]


def check_services_summary(item, params, info):
    blacklist = params.get("ignored", ())
    stoplist = []
    num_blacklist = 0
    num_auto = 0
    for line in info:
        # newer agents also send start type as part of state,
        # e.g. running/auto
        if '/' in line[2]:
            startstop, auto = line[2].split('/')
        else:
            startstop = line[2]
            auto = "unknown"
        srv_name = line[1]
        if auto == "auto":
            num_auto += 1
            if startstop == "stopped":
                match = False
                for srv in blacklist:
                    if re.match(srv, srv_name):
                        match = True
                if match is False:
                    stoplist.append(srv_name)
                else:
                    num_blacklist += 1

    num_stoplist = len(stoplist)
    num_srv = len(info)

    if len(stoplist) > 0:
        stopped_srvs = " (" + ", ".join(stoplist) + ")"
        state = params.get("state_if_stopped")
        if state == 1:
            sym = "(!)"
        elif state == 2:
            sym = "(!!)"
        else:
            sym = ""
    else:
        stopped_srvs = ""
        state = 0
        sym = ""


    infotext = "%d services, %d services in autostart - of which %d services are stopped%s%s, %d services stopped but ignored" % \
        ( num_srv, num_auto, num_stoplist, sym, stopped_srvs,  num_blacklist )

    return state, infotext


check_info['services.summary'] = {
    "check_function": check_services_summary,
    "inventory_function": inventory_services_summary,
    "default_levels_variable": "services_summary_default_levels",
    "service_description": "Services Summary",
    "node_info": True,
    "group": "services_summary",
}
