#!/usr/bin/python
# -*- encoding: utf-8; py-indent-offset: 4 -*-
# +------------------------------------------------------------------+
# |             ____ _               _        __  __ _  __           |
# |            / ___| |__   ___  ___| | __   |  \/  | |/ /           |
# |           | |   | '_ \ / _ \/ __| |/ /   | |\/| | ' /            |
# |           | |___| | | |  __/ (__|   <    | |  | | . \            |
# |            \____|_| |_|\___|\___|_|\_\___|_|  |_|_|\_\           |
# |                                                                  |
# | Copyright Mathias Kettner 2013             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 output:
# BIOS Information
# 	Vendor: LENOVO
# 	Version: 6FET49WW (1.19 )
# 	Release Date: 10/17/2008
# 	Address: 0xE0000
# 	Runtime Size: 128 kB
# 	ROM Size: 8192 kB
# 	Characteristics:
# 		PCI is supported
# 		PC Card (PCMCIA) is supported
# 		PNP is supported
# 		BIOS is upgradeable
# 		BIOS shadowing is allowed
# 		ESCD support is available
# 		Boot from CD is supported
# 		Selectable boot is supported
# 		BIOS ROM is socketed
# 		EDD is supported
# 		ACPI is supported
# 		USB legacy is supported
# 		BIOS boot specification is supported
# 		Targeted content distribution is supported
# 	BIOS Revision: 1.25
# 	Firmware Revision: 1.1
#
# System Information
# 	Manufacturer: LENOVO
# 	Product Name: 4061AR7
# 	Version: ThinkPad W500
# 	Serial Number: L3AFB3L
# 	UUID: AD137E01-4A86-11CB-A580-BE0E287D2679
# 	Wake-up Type: Power Switch
# 	SKU Number: Not Specified
# 	Family: ThinkPad W500
#
# ... any many other sections...


# Note: on Linux \t is replaced by : and then the split
# is done by :. On Windows the \t comes 1:1 and no splitting
# is being done. So we need to split manually here
def inv_dmidecode(info):
    section_name = None
    section_lines = []
    for line in info:
        # Windows plugin keeps tabs and has no separator
        if len(line) == 1:
            parts = line[0].replace("\t", ":").split(":")
            line = [x.strip() for x in parts]
        if len(line) == 1:
            if section_name:
                inv_dmidecode_parse_section(section_name, section_lines)
            section_name = line[0]
            section_lines = []
        else:
            section_lines.append(line[1:])
    if section_name:
        inv_dmidecode_parse_section(section_name, section_lines)


def inv_dmidecode_parse_section(name, lines):
    lines = [[w.strip() for w in words] for words in lines]
    if name == "BIOS Information":
        inv_dmidecode_parse_bios(lines)
    elif name == "System Information":
        inv_dmidecode_parse_system(lines)
    elif name == "Chassis Information":
        inv_dmidecode_parse_chassis(lines)
    elif name == "Processor Information":
        inv_dmidecode_parse_processor(lines)


#    elif name == "Memory Controller Information":
#        inv_dmidecode_parse_mem_controller(lines)
#    elif name == "Memory Module Information":
#        inv_dmidecode_parse_mem_module(lines)
    elif name == "Physical Memory Array":
        inv_dmidecode_parse_physical_mem_array(lines)
    elif name == "Memory Device":
        inv_dmidecode_parse_mem_device(lines)

    # TODO: Summe ueber alle Arrays ausrechnen


def inv_dmidecode_parse_date(value):
    try:
        # 10/17/2008
        return time.mktime(time.strptime(value, "%m/%d/%Y"))
    except Exception:
        return


def inv_dmidecode_parse_bios(lines):
    inv_dmidecode_parse_generic(
        "software.bios.", lines, {
            "Vendor": "vendor",
            "Version": "version",
            "Release Date": ("date", inv_dmidecode_parse_date),
            "BIOS Revision": "revision",
            "Firmware Revision": "firmware",
        })


def inv_dmidecode_parse_system(lines):
    inv_dmidecode_parse_generic(
        "hardware.system.", lines, {
            "Manufacturer": "manufacturer",
            "Product Name": "product",
            "Version": "version",
            "Serial Number": "serial",
            "UUID": "uuid",
            "Family": "family",
        })


def inv_dmidecode_parse_chassis(lines):
    inv_dmidecode_parse_generic("hardware.chassis.", lines, {
        "Manufacturer": "manufacturer",
        "Type": "type",
    })


# Note: This node is also being filled by lnx_cpuinfo
def inv_dmidecode_parse_processor(lines):
    cpu_info = {}
    for line in lines:
        if line[0] == "Manufacturer":
            cpu_info["vendor"] = {
                "GenuineIntel": "intel",
                "Intel(R) Corporation": "intel",
                "AuthenticAMD": "amd",
            }.get(line[1], line[1])
        elif line[0] == "Max Speed":  # 2530 MHz
            cpu_info["max_speed"] = dmidecode_parse_speed(line[1])
        elif line[0] == "Voltage":
            cpu_info["voltage"] = dmidecode_parse_voltage(line[1])
        elif line[0] == "Status":
            if line[1] == "Unpopulated":
                return

    # Only update our CPU information if the socket is populated
    inv_tree("hardware.cpu.").update(cpu_info)


# def inv_dmidecode_parse_mem_controller(lines):
#     # TODO: Can we have multiple memory controllers
#     node = inv_tree("hardware.memory.")
#     for line in lines:
#         if line[0] == "Maximum Memory Module Size":
#             node["max_module_size"] = dmidecode_parse_size(line[1])
#         elif line[0] == "Maximum Total Memory Size":
#             node["max_memory_size"] = dmidecode_parse_size(line[1])
#         elif line[0] == "Memory Module Voltage":
#             node["module_voltage"] = dmidecode_parse_voltage(line[1])
#
# def inv_dmidecode_parse_mem_module(lines):
#     node = inv_tree_list("hardware.memory.modules:")
#     module = {}
#     node.append(module)
#     for line in lines:
#         if line[0] == "Socket Designation":
#             module["disignation"] = line[1]
#         elif line[0] == "Type":
#             module["type"] = line[1]
#         elif line[0] == "Installed Size":
#             module["size"] = dmidecode_parse_size(line[1])
#         elif line[0] == "Enabled Size":
#             module["enabled_size"] = dmidecode_parse_size(line[1])
#         elif line[0] == "Current Speed":
#             time_sec = dmidecode_parse_time(line[1])
#             speed = 1.0 / time_sec
#             module["current_speed"] = speed


def inv_dmidecode_parse_physical_mem_array(lines):
    # We expect several possible arrays
    node = inv_tree_list("hardware.memory.arrays:")

    # If we have a dummy entry from previous Memory Devices (see below)
    # then we fill that entry rather than creating a new one
    if len(node) == 1 and node[0].keys() == ["devices"]:
        array = node[0]
    else:
        array = {"devices": []}
        node.append(array)

    for line in lines:
        if line[0] == "Location":
            array["location"] = line[1]
        elif line[0] == "Use":
            array["use"] = line[1]
        elif line[0] == "Error Correction Type":
            array["error_correction"] = line[1]
        elif line[0] == "Maximum Capacity":
            array["maximum_capacity"] = dmidecode_parse_size(line[1])


def inv_dmidecode_parse_mem_device(lines):
    node = inv_tree_list("hardware.memory.arrays:")
    device = {}

    inv_dmidecode_parse_generic(
        device,
        lines,
        {
            "Total Width": "total_width",  # 64 bits
            "Data Width": "data_width",  # 64 bits
            "Form Factor": "form_factor",  # SODIMM
            "Set": "set",  # None
            "Locator": "locator",  # PROC 1 DIMM 2
            "Bank Locator": "bank_locator",  # Bank 2/3
            "Type": "type",  # DDR2
            "Type Detail": "type_detail",  # Synchronous
            "Manufacturer": "manufacturer",  # Not Specified
            "Serial Number": "serial",  # Not Specified
            "Asset Tag": "asset_tag",  # Not Specified
            "Part Number": "part_number",  # Not Specified
            "Speed": "speed",  # 667 MHz
            "Size": "size",  # 2048 MB
        })

    # Do we already have an entry for a memory array? Then
    # we assume that this device belongs to the most recently
    # read array. Otherwise we create a dummy entry and replace
    # that later with actual information
    # If there are already arrays we try to find the right index of
    # the phy. array
    index = None
    if device.get("locator") and device["locator"].startswith("PROC"):
        index = device["locator"].split()[1]

    if node:
        try:
            array = node[int(index) - 1]
        except:
            array = node[-1]
    else:
        array = {"devices": []}
        node.append(array)

    if device["size"] != "No Module Installed":
        # Convert speed and size into numbers
        device["speed"] = dmidecode_parse_speed(device.get("speed", "Unknown"))
        device["size"] = dmidecode_parse_size(device.get("size", "Unknown"))
        array["devices"].append(device)


def inv_dmidecode_parse_generic(node, lines, keyinfo):
    if isinstance(node, str):
        node = inv_tree(node)
    for line in lines:
        if line[0] in keyinfo:
            key = keyinfo[line[0]]
            if line[1] != "Not Specified":
                value = line[1]
                if isinstance(key, tuple):
                    key, transform = key
                    value = transform(value)
                    if value is None:
                        continue
                node[key] = value


def dmidecode_parse_size(v):  # into Bytes (int)
    if not v or v == "Unknown":
        return None

    parts = v.split()
    if parts[1].lower() == "tb":
        return int(parts[0]) * 1024 * 1024 * 1024 * 1024
    elif parts[1].lower() == "gb":
        return int(parts[0]) * 1024 * 1024 * 1024
    elif parts[1].lower() == "mb":
        return int(parts[0]) * 1024 * 1024
    elif parts[1].lower() == "kb":
        return int(parts[0]) * 1024
    return int(parts[0])


def dmidecode_parse_speed(v):  # into Hz (float)
    if not v or v == "Unknown":
        return None

    parts = v.split()
    if parts[1] == "GHz":
        return float(parts[0]) * 1000000000.0
    elif parts[1] == "MHz":
        return float(parts[0]) * 1000000.0
    elif parts[1] == "kHz":
        return float(parts[0]) * 1000.0
    elif parts[1] == "Hz":
        return float(parts[0])


def dmidecode_parse_voltage(v):
    if not v or v == "Unknown":
        return None
    return float(v.split()[0])


def dmidecode_parse_time(v):  # 155 ns
    parts = v.split()
    if parts[1] == "ns":
        return float(parts[0]) / 1000000000.0
    return float(parts[0])  # assume seconds


inv_info['dmidecode'] = {
    "inv_function": inv_dmidecode,
}
