from __future__ import annotations

from typing import Callable, Generic, TypeVar, cast

T = TypeVar("T")


class CommandValueHandler(Generic[T]):
    def __init__(
        self, validator_function: Callable[[T], bool] | None = None, none_equivalent: bytes | None = None
    ) -> None:
        if none_equivalent is not None and not self._validate_byte_value(none_equivalent):
            raise ValueError("none_equivalent can only be bytes of length 1")

        self._validator_function = validator_function
        self._none_equivalent = none_equivalent

    def byte2native(self, byte_value: bytes) -> T | None:
        if not self._validate_byte_value(byte_value):
            raise ValueError("given value is not a single byte")

        if self._none_equivalent is not None and byte_value == self._none_equivalent:
            return None

        native_type = self.__orig_class__.__args__[0]  # type: ignore
        return cast(T, native_type(ord(byte_value)))

    def native2byte(self, native_value: T) -> bytes:
        if native_value is None:
            if self._none_equivalent is not None:
                return self._none_equivalent
            else:
                raise ValueError("None not a supported value")

        if not self._validate_native_value(native_value):
            raise ValueError("value not allowed by validator function")

        if isinstance(native_value, bool):
            return b"\x01" if native_value else b"\x00"
        elif isinstance(native_value, int):
            return bytes([native_value])
        else:
            raise ValueError("not a supported value type: %s" % str(type(native_value)))

    def _validate_native_value(self, native_value: T) -> bool:
        if self._validator_function is None:
            return True
        return self._validator_function(native_value)

    @staticmethod
    def _validate_byte_value(byte_value: bytes) -> bool:
        if not isinstance(byte_value, bytes):
            return False
        return len(byte_value) == 1

    @classmethod
    def chr2int(cls, v: bytes) -> int:
        if not cls._validate_byte_value(v):
            raise ValueError("not bytes of length 1")
        return v[0]

    @staticmethod
    def int2chr(v: int) -> bytes:
        if not 0 <= v <= 255:
            raise ValueError("given value must be >= 0 and <= 255")
        return v.to_bytes(1, byteorder="big")
