from xml.etree.ElementTree import Element

from xml.etree import cElementTree as eTree

from messagebus import (
    MessageBus,
    Message,
    DataType,
    DefaultToken,
)
from typing import (
    Optional,
    Dict,
)
from .token import (
    TK_POS_GET_MODEL,
    TK_POS_GETCONFIG,
)


def requires_model(f):
    def wrapper(*args, **kwargs):
        if args[0].model is None:
            args[0].model = args[0]._retrieve_new_model()

        return f(*args, **kwargs)

    return wrapper


class PosService(object):

    def __init__(
        self,               # type: PosService
        message_bus,        # type: MessageBus
        pos_id,             # type: int
    ):
        # type: (...) -> None

        self.message_bus = message_bus
        self.pos_id = pos_id

        self.component_name = "POS{}".format(self.pos_id)

        self.pos_config_cache = {}  # type: Dict[str, Optional[str]]

        self.model = None

    def get_model(self):
        # type: (...) -> Element

        if self.model is None:
            self.model = self._retrieve_new_model()

        return self.model

    def get_updated_model(self):
        # type: (...) -> Element

        self.model = self._retrieve_new_model()
        return self.model

    @requires_model
    def get_operator_session(self):
        # type: (...) -> Optional[str]

        for op in self.model.findall("Operator"):
            if op.get("state") == "LOGGEDIN":
                return op.get("sessionId")

        return None

    @requires_model
    def get_pod_type(self):
        # type: (...) -> str

        return self.model.find("WorkingMode").get("podType")

    @requires_model
    def get_pos_function(self):
        # type: (...) -> str

        return self.model.find("WorkingMode").get("posFunction")

    def get_hour_to_block_pos(
        self,    # type: PosService
    ):
        # type: (...) -> int

        hour_to_block_pos = self._get_pos_config_key(
            tag="[@name='hourToBlockPos']/integer",
        )
        return int(hour_to_block_pos or 0)

    def _get_pos_config_key(
        self,                 # type: PosService
        tag,                  # type: str
    ):
        # type: (...) -> Optional[str]

        if tag in self.pos_config_cache:
            return self.pos_config_cache[tag]

        message = Message(
            token=TK_POS_GETCONFIG,
            data_type=DataType.param,
        )
        response = self.message_bus.send_message(
            component=self.component_name,
            message=message,
        )
        if response.token != DefaultToken.TK_SYS_ACK:
            return None

        config = eTree.XML(response.data)
        complete_tag = "group[@name='POS']/group[@name='POS{}']/key{}".format(
            str(self.pos_id).zfill(2),
            tag,
        )
        tag_element = config.find(complete_tag)
        if tag_element is not None:
            self.pos_config_cache[tag] = tag_element.text
            return self.pos_config_cache[tag]

        self.pos_config_cache[tag] = None
        return self.pos_config_cache[tag]

    def _retrieve_new_model(self):
        # type: (...) -> Element

        msg = self.message_bus.send_message(
            component=self.component_name,
            message=Message(
                token=TK_POS_GET_MODEL,
                data_type=DataType.string,
                data=str(self.pos_id).encode()
            )
        )
        if msg.token != DefaultToken.TK_SYS_ACK:
            raise RuntimeError("Could not retrieve state of pos '{}'".format(self.pos_id))

        return eTree.XML(msg.data)
