"""Module for sending command to gw."""
from enum import Enum
from typing import List

from pyvlx.const import (
    Command, NodeParameter, Originator, Priority, RunStatus, StatusReply)
from pyvlx.exception import PyVLXException
from pyvlx.parameter import FunctionalParams, Parameter, Position

from .frame import FrameBase


class FrameCommandSendRequest(FrameBase):
    """Frame for sending command to gw."""

    PAYLOAD_LEN = 66

    def __init__(
            self,
            functional_parameter: FunctionalParams | None = None,
            node_ids: List[int] | None = None,
            parameter: Parameter = Parameter(),
            active_parameter: int = 0,
            session_id: int | None = None,
            originator: Originator = Originator.USER,
    ):
        """Init Frame."""
        super().__init__(Command.GW_COMMAND_SEND_REQ)
        self.node_ids = node_ids if node_ids is not None else []
        self.parameter = parameter
        self.active_parameter = active_parameter
        self.fpi1 = 0
        self.fpi2 = 0
        self.functional_parameter: FunctionalParams = {}
        self.session_id = session_id
        self.originator = originator
        self.priority = Priority.USER_LEVEL_2
        # Set the functional parameter indicator bytes to show which functional
        # parameters are included in the frame. The functional parameter dictionary
        # is checked for NodeParameter.FP1..NodeParameter.FP3 to set the appropriate
        # indicator values and the corresponding self.functional_parameter entries.
        # Other functional parameters (FP4..FP16) are ignored for now.
        if functional_parameter is None:
            functional_parameter = {}
        for fp in (NodeParameter.FP1, NodeParameter.FP2, NodeParameter.FP3):
            if fp in functional_parameter:
                self.functional_parameter[fp] = functional_parameter[fp]
                self.fpi1 += 2 ** (8 - fp.value)

                # this distinction is only needed when fp9..fp16 are actually used, so commented out for now
                # if i < 9:
                #     self.fpi1 += 2 ** (8 - i)
                # if i >= 9:
                #     self.fpi2 += 2 ** (16 - i)
            else:
                self.functional_parameter[fp] = Parameter(raw=bytes(2))

    def get_payload(self) -> bytes:
        """Return Payload."""
        # Session id
        assert self.session_id is not None
        ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
        ret += bytes([self.originator.value])
        ret += bytes([self.priority.value])
        ret += bytes(
            [self.active_parameter]
        )  # ParameterActive pointing to main parameter (MP)
        # FPI 1+2
        ret += bytes([self.fpi1])
        ret += bytes([self.fpi2])
        # Main parameter + functional parameter FP1 to FP3
        ret += bytes(self.parameter)
        ret += bytes(self.functional_parameter[NodeParameter.FP1])
        ret += bytes(self.functional_parameter[NodeParameter.FP2])
        ret += bytes(self.functional_parameter[NodeParameter.FP3])
        # Functional parameter FP4 to FP16 are ignored for now
        ret += bytes(26)

        # Nodes array: Number of nodes + node array + padding
        ret += bytes([len(self.node_ids)])  # index array count
        ret += bytes(self.node_ids) + bytes(20 - len(self.node_ids))

        # Priority  Level Lock
        ret += bytes([0])
        # Priority Level information 1+2
        ret += bytes([0, 0])
        # Locktime
        ret += bytes([0])
        return ret

    def from_payload(self, payload: bytes) -> None:
        """Init frame from binary data."""
        self.session_id = payload[0] * 256 + payload[1]
        self.originator = Originator(payload[2])
        self.priority = Priority(payload[3])

        len_node_ids = payload[41]
        if len_node_ids > 20:
            raise PyVLXException("command_send_request_wrong_node_length")
        self.node_ids = []
        for i in range(len_node_ids):
            self.node_ids.append(payload[42] + i)

        self.parameter = Parameter(payload[7:9])

    def __str__(self) -> str:
        """Return human readable string."""
        functional_parameter = ""
        for key, value in self.functional_parameter.items():
            functional_parameter += f"{key.name}: {Position(Parameter(bytes(value)))}, "
        return (
            f'<{type(self).__name__} node_ids="{self.node_ids}" active_parameter="{self.active_parameter}" parameter="{self.parameter}" '
            f'fpi1="0x{self.fpi1:02x}" fpi2="0x{self.fpi2:02x}" functional_parameter="{functional_parameter}" '
            f'session_id="{self.session_id}" originator="{self.originator}"/>'
        )


class CommandSendConfirmationStatus(Enum):
    """Enum class for status of command send confirmation."""

    REJECTED = 0
    ACCEPTED = 1


class FrameCommandSendConfirmation(FrameBase):
    """Frame for confirmation of command send frame."""

    PAYLOAD_LEN = 3

    def __init__(self, session_id: int | None = None, status: CommandSendConfirmationStatus | None = None):
        """Init Frame."""
        super().__init__(Command.GW_COMMAND_SEND_CFM)
        self.session_id = session_id
        self.status = status

    def get_payload(self) -> bytes:
        """Return Payload."""
        assert self.session_id is not None
        assert self.status is not None
        ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
        ret += bytes([self.status.value])
        return ret

    def from_payload(self, payload: bytes) -> None:
        """Init frame from binary data."""
        self.session_id = payload[0] * 256 + payload[1]
        self.status = CommandSendConfirmationStatus(payload[2])

    def __str__(self) -> str:
        """Return human readable string."""
        return f'<{type(self).__name__} session_id="{self.session_id}" status="{self.status}"/>'


class FrameCommandRunStatusNotification(FrameBase):
    """Frame for run status notification in scope of command send frame."""

    PAYLOAD_LEN = 13

    def __init__(
            self,
            *,
            session_id: int | None = None,
            status_id: int | None = None,
            index_id: int | None = None,
            node_parameter: int | None = None,
            parameter_value: int | None = None,
            run_status: RunStatus | None = None,
            status_reply: StatusReply | None = None,
    ):
        """Init Frame."""
        super().__init__(Command.GW_COMMAND_RUN_STATUS_NTF)
        self.session_id = session_id
        self.status_id = status_id
        self.index_id = index_id
        self.node_parameter = node_parameter
        self.parameter_value = parameter_value
        self.run_status = run_status
        self.status_reply = status_reply

    def get_payload(self) -> bytes:
        """Return Payload."""
        assert self.session_id is not None
        ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
        assert self.status_id is not None
        ret += bytes([self.status_id])
        assert self.index_id is not None
        ret += bytes([self.index_id])
        assert self.node_parameter is not None
        ret += bytes([self.node_parameter])
        assert self.parameter_value is not None
        ret += bytes([self.parameter_value >> 8 & 255, self.parameter_value & 255])
        assert self.run_status is not None
        ret += bytes([self.run_status.value])
        assert self.status_reply is not None
        ret += bytes([self.status_reply.value])

        # XXX: Missing implementation of information_code
        ret += bytes(4)
        return ret

    def from_payload(self, payload: bytes) -> None:
        """Init frame from binary data."""
        self.session_id = payload[0] * 256 + payload[1]
        self.status_id = payload[2]
        self.index_id = payload[3]
        self.node_parameter = payload[4]
        self.parameter_value = payload[5] * 256 + payload[6]
        self.run_status = RunStatus(payload[7])
        self.status_reply = StatusReply(payload[8])

    def __str__(self) -> str:
        """Return human readable string."""
        return (
            f'<{type(self).__name__} session_id="{self.session_id}" status_id="{self.status_id}" '
            f'index_id="{self.index_id}" node_parameter="{self.node_parameter}" parameter_value="{self.parameter_value}" '
            f'run_status="{self.run_status}" status_reply="{self.status_reply}"/>'
        )


class FrameCommandRemainingTimeNotification(FrameBase):
    """Frame for notification of remaining time in scope of command send frame."""

    PAYLOAD_LEN = 6

    def __init__(self, session_id: int | None = None, index_id: int | None = None, node_parameter: int | None = None, seconds: int = 0):
        """Init Frame."""
        super().__init__(Command.GW_COMMAND_REMAINING_TIME_NTF)
        self.session_id = session_id
        self.index_id = index_id
        self.node_parameter = node_parameter
        self.seconds = seconds

    def get_payload(self) -> bytes:
        """Return Payload."""
        assert self.session_id is not None
        ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
        assert self.index_id is not None
        ret += bytes([self.index_id])
        assert self.node_parameter is not None
        ret += bytes([self.node_parameter])
        ret += bytes([self.seconds >> 8 & 255, self.seconds & 255])
        return ret

    def from_payload(self, payload: bytes) -> None:
        """Init frame from binary data."""
        self.session_id = payload[0] * 256 + payload[1]
        self.index_id = payload[2]
        self.node_parameter = payload[3]
        self.seconds = payload[4] * 256 + payload[5]

    def __str__(self) -> str:
        """Return human readable string."""
        return (
            f'<{type(self).__name__} session_id="{self.session_id}" index_id="{self.index_id}" '
            f'node_parameter="{self.node_parameter}" seconds="{self.seconds}"/>'
        )


class FrameSessionFinishedNotification(FrameBase):
    """Frame for notification of session finishid in scope of command send frame."""

    PAYLOAD_LEN = 2

    def __init__(self, session_id: int | None = None):
        """Init Frame."""
        super().__init__(Command.GW_SESSION_FINISHED_NTF)
        self.session_id = session_id

    def get_payload(self) -> bytes:
        """Return Payload."""
        assert self.session_id is not None
        ret = bytes([self.session_id >> 8 & 255, self.session_id & 255])
        return ret

    def from_payload(self, payload: bytes) -> None:
        """Init frame from binary data."""
        self.session_id = payload[0] * 256 + payload[1]

    def __str__(self) -> str:
        """Return human readable string."""
        return f'<{type(self).__name__} session_id="{self.session_id}"/>'
