from numbers import Number
from typing import List, Protocol, Tuple

from voluptuous import Invalid


class Packer(Protocol):  # pragma: no cover
    # pylint: disable=R0903
    """
    Pack multiple raw values from the inverter
     data into one raw value
    """

    def __call__(self, *vals: float) -> float: ...


PackerBuilderResult = Tuple[Tuple[int, ...], Packer]


class PackerBuilder(Protocol):  # pragma: no cover
    # pylint: disable=R0903
    """
    Build a packer by identifying the indexes of the
    raw values to be fed to the packer
    """

    def __call__(self, *indexes: int) -> PackerBuilderResult: ...


def __u16_packer(*values: float) -> float:
    accumulator = 0.0
    stride = 1
    for value in values:
        accumulator += value * stride
        stride *= 2**16
    return accumulator


def pack_u16(*indexes: int) -> PackerBuilderResult:
    """
    Some values are expressed over 2 (or potentially
    more 16 bit [aka "short"] registers). Here we combine
    them, in order of least to most significant.
    """
    return (indexes, __u16_packer)


def startswith(something):
    def inner(actual):
        if isinstance(actual, str):
            if actual.startswith(something):
                return actual
        raise Invalid(f"{str(actual)} does not start with {something}")

    return inner


def div10(val):
    return val / 10


def div100(val):
    return val / 100


INT16_MAX = 0x7FFF
INT32_MAX = 0x7FFFFFFF


def to_signed(val):
    if val > INT16_MAX:
        val -= 2**16
    return val


def to_signed32(val):
    if val > INT32_MAX:
        val -= 2**32
    return val


def twoway_div10(val):
    return to_signed(val) / 10


def twoway_div100(val):
    return to_signed(val) / 100


def to_url(host, port):
    return f"http://{host}:{port}/"


def contains_none_zero_value(value: List[Number]):
    """Validate that at least one element is not zero.
    Args:
        value (List[Number]): list to validate
    Raises:
        Invalid: if all elements are zero
    """

    if isinstance(value, list):
        if len(value) != 0 and any((v != 0 for v in value)):
            return value
    raise Invalid("All elements in the list are zero")
