"""
:file:  scalars.py.

Contains various primitive type formatters.

:date: 27/08/2015
:authors:
    - Gilad Naaman <gilad@naaman.io>
"""

from .base import *
import struct


class ScalarMetadata(SerializerMetadata):
    __slots__ = ('endianness', 'fmt', 'validator', 'py_types')
    _FORMATTERS_INFO = {
        'B': (1, (int, ), RangeValidator(0, 255)),
        'b': (1, (int, ), RangeValidator(-128, 127)),
        'H': (2, (int, ), RangeValidator(0, 65535)),
        'h': (2, (int, ), RangeValidator(-32768, 32767)),
        'I': (4, (int, ), RangeValidator(0, 4294967295)),
        'i': (4, (int, ), RangeValidator(-2147483648, 2147483647)),
        'Q': (8, (int, ), RangeValidator(0, 18446744073709551615)),
        'q': (8, (int, ), RangeValidator(-9223372036854775808, 9223372036854775807)),
        'f': (4, (int, float), TrueValidator()),
        'd': (8, (int, float), TrueValidator()),
    }

    def __init__(self, *, fmt: str, endianness: Endianness):
        size, self.py_types, self.validator = ScalarMetadata._FORMATTERS_INFO[fmt]
        self.endianness = endianness
        self.fmt = fmt
        super(ScalarMetadata, self).__init__(size)


class ScalarMeta(SerializerMeta):
    def __new__(mcs, name, bases, classdict, fmt: str = None, endianness: Endianness = Endianness.TARGET):
        if hasattr(sys.modules[__name__], 'Scalar'):
            metadata = ScalarMetadata(fmt=fmt, endianness=endianness)
            classdict[SerializerMeta.METAATTR] = metadata
        return super(ScalarMeta, mcs).__new__(mcs, name, bases, classdict)

    def __repr__(self):
        return get_type_name(self)


class Scalar(Serializer, metaclass=ScalarMeta):
    """ Provides a handy base class for primitive-value formatters. """
    __slots__ = ()
    _hydras_metadata: ScalarMetadata

    def __init__(self, default_value=0, *args, **kwargs):
        """
        Initialize the scalar object.

        :param format_string:   The format string of the primitive, according to `struct.pack`
        :param default_value:   The default value of this formatter.
        :param endian:          The endian of this formatter.
        """
        super(Scalar, self).__init__(default_value, *args, **kwargs)

    def validate(self, value):
        if not isinstance(value, self._hydras_metadata.py_types):
            raise TypeError(f'Expected value of type {self._hydras_metadata.py_types}, but got {type(value)}')
        elif not self._hydras_metadata.validator(value):
            raise ValueError('Value outside of type bounds')

        super(Scalar, self).validate(value)

    def serialize_into(self, storage: memoryview, offset: int, value, settings: HydraSettings = None) -> int:
        struct.pack_into(self.get_format_string(settings), storage, offset, value)
        return offset + self.byte_size

    def serialize_many_into(self,
                            storage: memoryview,
                            offset: int,
                            value: List[Any],
                            min_values_count: int,
                            settings: HydraSettings) -> int:
        fmt = self.get_format_string(settings, len(value))
        struct.pack_into(fmt, storage, offset, *value)
        return offset + self.byte_size * max(len(value), min_values_count)

    def deserialize(self, raw_data, settings: HydraSettings = None):
        settings = HydraSettings.resolve(settings)

        if self._hydras_metadata.endianness == Endianness.TARGET:
            endian = settings.target_endian
        else:
            endian = self._hydras_metadata.endianness

        return struct.unpack(endian.value + self._hydras_metadata.fmt, raw_data)[0]

    def get_format_string(self, settings: HydraSettings = None, count: int = 1):
        if self._hydras_metadata.endianness == Endianness.TARGET: