"""SECS variable base type."""
from __future__ import annotations


class Base:
    """Base class for SECS variables.

    Due to the python types, wrapper classes for variables are required.
    If constructor is called with Base or subclass only the value is copied.
    """

    format_code = -1
    preferred_types: list[type] | None

    def __init__(self, value=None):
        """Initialize a secs variable."""
        self.value = value

    def __call__(self, *args, **kwargs):
        return self

    def set(self, value):
        """Set the internal value to the provided value.

        :param value: new value
        :type value: various
        """
        raise NotImplementedError("Function set not implemented on " + self.__class__.__name__)

    def encode_item_header(self, length):
        """Encode item header depending on the number of length bytes required.

        :param length: number of bytes in data
        :type length: integer
        :returns: encoded item header bytes
        :rtype: string
        """
        if length < 0:
            raise ValueError(f"Encoding {self.__class__.__name__} not possible, data length too small {length}")
        if length > 0xFFFFFF:
            raise ValueError(f"Encoding {self.__class__.__name__} not possible, data length too big {length}")

        if length > 0xFFFF:
            length_bytes = 3
            format_byte = (self.format_code << 2) | length_bytes
            return bytes(bytearray((format_byte, (length & 0xFF0000) >> 16, (length & 0x00FF00) >> 8,
                                    (length & 0x0000FF))))
        if length > 0xFF:
            length_bytes = 2
            format_byte = (self.format_code << 2) | length_bytes
            return bytes(bytearray((format_byte, (length & 0x00FF00) >> 8, (length & 0x0000FF))))

        length_bytes = 1
        format_byte = (self.format_code << 2) | length_bytes
        return bytes(bytearray((format_byte, (length & 0x0000FF))))

    def decode_item_header(self, data, text_pos=0):
        """Encode item header depending on the number of length bytes required.

        :param data: encoded data
        :type data: string
        :param text_pos: start of item header in data
        :type text_pos: integer
        :returns: start position for next item, format code, length item of data
        :rtype: (integer, integer, integer)
        """
        if len(data) == 0:
            raise ValueError(f"Decoding for {self.__class__.__name__} without any text")

        # parse format byte
        format_byte = bytearray(data)[text_pos]

        format_code = (format_byte & 0b11111100) >> 2
        length_bytes = format_byte & 0b00000011

        text_pos += 1

        # read 1-3 length bytes
        length = 0
        for _ in range(length_bytes):
            length <<= 8
            length += bytearray(data)[text_pos]

            text_pos += 1

        if 0 <= self.format_code != format_code:
            raise ValueError(
                f"Decoding data for {self.__class__.__name__} ({self.format_code}) has invalid format {format_code}")

        return text_pos, format_code, length

    @property
    def is_dynamic(self) -> bool:
        """Check if this instance is Dynamic or derived."""
        return False

    @property
    def preferred_type(self):
        """Get the preferred type for this variable."""
        return self.preferred_types[0]
