import re
import json
from itertools import takewhile
from enum import Enum

__all__ = ["DmiParser"]

DmiParserState = Enum(
    "DmiParserState",
    (
        "GET_SECT",
        "GET_PROP",
        "GET_PROP_ITEM",
    ),
)


class DmiParserSectionHandle(object):
    """A handle looks like this

    Handle 0x0066, DMI type 148, 48 bytes
    """

    def __init__(self):
        self.id = ""
        self.type = ""
        self.bytes = 0

    def __str__(self):
        return json.dumps(self.__dict__)


class DmiParserSectionProp(object):
    """A property looks like this

    Characteristics:
            3.3 V is provided
            PME signal is supported
            SMBus signal is supported
    """

    def __init__(self, value: str):
        self.values = []

        if value:
            self.append(value)

    def __str__(self):
        return json.dumps(self.__dict__)

    def append(self, item: str):
        self.values.append(item)


class DmiParserSection(object):
    """A section looks like this

    On Board Device 1 Information
            Type: Video
            Status: Enabled
            Description: ServerEngines Pilot III
    """

    def __init__(self):
        self.handle = None
        self.name = ""
        self.props = {}

    def __str__(self):
        return json.dumps(self.__dict__)

    def append(self, key: str, prop: str):
        self.props[key] = prop


class DmiParser(object):
    """This parse dmidecode output to JSON"""

    def __init__(self, text: str, **kwargs):
        """
        text:   output of command dmidecode
        kwargs: these will pass to json.dumps
        """
        self._text = text
        self._kwargs = kwargs
        self._indentLv = lambda l: len(list(takewhile(lambda c: "\t" == c, l)))
        self._sections = []

        if type(text) is not str:
            raise TypeError("{} want a {} but got {}".format(self.__class__, type(__name__), type(text)))

        self._parse(text)

    def __str__(self):
        return json.dumps(self._sections, **self._kwargs)

    def _parse(self, text: str):
        lines = self._text.splitlines()
        rhandle = r"^Handle\s(.+?),\sDMI\stype\s(\d+?),\s(\d+?)\sbytes$"
        section = None
        prop = None
        state = None
        k, v = None, None

        for i, l in enumerate(lines):
            if i == len(lines) - 1 or DmiParserState.GET_SECT == state:
                # Add previous section if exist
                if section:
                    # Add previous prop if exist
                    if prop:
                        section.append(k, json.loads(str(prop)))
                        prop = None

                    self._sections.append(json.loads(str(section)))
                    section = None

            if not l:
                continue

            if l.startswith("Handle"):
                state = DmiParserState.GET_SECT
                handle = DmiParserSectionHandle()
                match = re.match(rhandle, l)
                handle.id, handle.type, handle.bytes = match.groups()
                continue

            if DmiParserState.GET_SECT == state:
                section = DmiParserSection()
                section.handle = json.loads(str(handle))
                section.name = l
                state = DmiParserState.GET_PROP
                continue

            if DmiParserState.GET_PROP == state:
                k, v = [x.strip() for x in l.split(":", 1)]
                prop = DmiParserSectionProp(v)
                lv = self._indentLv(l) - self._indentLv(lines[i + 1])

                if v:
                    if not lv:
                        section.append(k, json.loads(str(prop)))
                        prop = None
                    elif -1 == lv:
                        state = DmiParserState.GET_PROP_ITEM
                        continue
                else:
                    if -1 == lv:
                        state = DmiParserState.GET_PROP_ITEM
                        continue

                # Next section for this handle
                if not self._indentLv(lines[i + 1]):
                    state = DmiParserState.GET_SECT

            if DmiParserState.GET_PROP_ITEM == state:
                prop.append(l.strip())

                lv = self._indentLv(l) - self._indentLv(lines[i + 1])

                if lv:
                    section.append(k, json.loads(str(prop)))
                    prop = None

                    if lv > 1:
                        state = DmiParserState.GET_SECT
                    else:
                        state = DmiParserState.GET_PROP


if "__main__" == __name__:
    text = """# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 2.7 present.

Handle 0x0003, DMI type 2, 17 bytes
Base Board Information
	Manufacturer: Intel Corporation
	Product Name: S2600WT2R
	Version: H21573-372
	Serial Number: BQWL81150522
	Asset Tag: Base Board Asset Tag
	Features:
		Board is a hosting board
		Board is replaceable
	Location In Chassis: Part Component
	Chassis Handle: 0x0000
	Type: Motherboard
	Contained Object Handles: 0

    """

    # just print
    parser = DmiParser(text)
    # parser = DmiParser(text, sort_keys=True, indent=2)
    print("parser is {}".format(type(parser)))
    print(parser)

    # if you want a string
    dmistr = str(parser)
    print("dmistr is {}".format(type(dmistr)))
    print(dmistr)

    # if you want a data structure
    dmidata = json.loads(str(parser))
    print("dmidata is {}".format(type(dmidata)))
    print(dmidata)
