from __future__ import annotations

import struct
from typing import Any, Literal, cast

from .bfunc import get_value_inside


def decode(value: bytes):
    import chardet

    data = chardet.detect(value)
    encoding = data['encoding'] or 'utf8'
    return value.decode(encoding)


class BytesWriter:
    def __init__(self, endian: _EndianType):
        self.endian = endian
        self.formats: list[str] = []
        self.values: list[Any] = []

    def to_bytes(self):
        return struct.pack(
            f'{self.endian}{"".join(self.formats)}',
            *self.values,
        )

    def _write(self, format: str, value: int | float | bool | str | bytes):
        self.formats.append(format)
        self.values.append(value)

    def _write_list(self, func: Any, ary: list[Any]):
        self.write_uint(len(ary))
        for value in ary:
            func(value)
        return self

    # ---------------------------------------------------------------------------

    def write_short(self, value: int):
        "int16"
        self._write('h', get_value_inside(value, -32768, 32767))
        return self

    def write_ushort(self, value: int):
        "int16"
        self._write('H', get_value_inside(value, 0, 65535))
        return self

    def write_int(self, value: int):
        "int32"
        self._write('i', get_value_inside(value, -2147483648, 2147483647))
        return self

    def write_uint(self, value: int):
        "int32"
        self._write('I', get_value_inside(value, 0, 4294967295))
        return self

    def write_long(self, value: int):
        "int64"
        self._write('q', get_value_inside(value, -9223372036854775808, 9223372036854775807))
        return self

    def write_ulong(self, value: int):
        "int64"
        self._write('Q', get_value_inside(value, 0, 18446744073709551615))
        return self

    def write_float(self, value: float):
        self._write('f', value)
        return self

    def write_double(self, value: float):
        self._write('d', value)
        return self

    def write_bool(self, value: bool):
        self._write('?', value)
        return self

    def write_str(self, value: str):
        value_bytes = value.encode('utf8')
        count = len(value_bytes)
        self.write_ushort(count)
        self._write(f'{count}s', value_bytes)
        return self

    # ---------------------------------------------------------------------------

    def write_list_short(self, ary: list[int]):
        "int16[]"
        return self._write_list(self.write_short, ary)

    def write_list_ushort(self, ary: list[int]):
        "int16[]"
        return self._write_list(self.write_ushort, ary)

    def write_list_int(self, ary: list[int]):
        "int32[]"
        return self._write_list(self.write_int, ary)

    def write_list_uint(self, ary: list[int]):
        "int32[]"
        return self._write_list(self.write_uint, ary)

    def write_list_long(self, ary: list[int]):
        "int64[]"
        return self._write_list(self.write_long, ary)

    def write_list_ulong(self, ary: list[int]):
        "int64[]"
        return self._write_list(self.write_ulong, ary)

    def write_list_float(self, ary: list[float]):
        return self._write_list(self.write_float, ary)

    def write_list_double(self, ary: list[float]):
        return self._write_list(self.write_double, ary)

    def write_list_bool(self, ary: list[bool]):
        return self._write_list(self.write_bool, ary)

    def write_list_str(self, ary: list[str]):
        return self._write_list(self.write_str, ary)


class BytesReader:
    offset: int
    data: bytes

    def __init__(self, endian: _EndianType, data: bytes):
        self.endian = endian
        self.offset = 0
        self.data = data

    def _read(self, fmt: str):
        result = struct.unpack_from(f'{self.endian}{fmt}', self.data, self.offset)[0]
        self.offset += struct.calcsize(fmt)
        return result

    def _read_list(self, func: Any):
        ary: list[Any] = []
        count = self.read_uint()
        for _ in range(count):
            ary.append(func())
        return ary

    # ---------------------------------------------------------------------------

    def read_short(self):
        "int16"
        return cast(int, self._read('h'))

    def read_ushort(self):
        "int16"
        return cast(int, self._read('H'))

    def read_int(self):
        "int32"
        return cast(int, self._read('i'))

    def read_uint(self):
        "int32"
        return cast(int, self._read('I'))

    def read_long(self):
        "int64"
        return cast(int, self._read('q'))

    def read_ulong(self):
        "int64"
        return cast(int, self._read('Q'))

    def read_float(self):
        return cast(float, self._read('f'))

    def read_double(self):
        return cast(float, self._read('d'))

    def read_bool(self):
        return cast(bool, self._read('?'))

    def read_str(self):
        count = self.read_ushort()
        return cast(str, self._read(f'{count}s').decode())

    # ---------------------------------------------------------------------------

    def read_list_short(self):
        "int16[]"
        return cast(list[int], self._read_list(self.read_short))

    def read_list_ushort(self):
        "int16[]"
        return cast(list[int], self._read_list(self.read_ushort))

    def read_list_int(self):
        "int32[]"
        return cast(list[int], self._read_list(self.read_int))

    def read_list_uint(self):
        "int32[]"
        return cast(list[int], self._read_list(self.read_uint))

    def read_list_long(self):
        "int64[]"
        return cast(list[int], self._read_list(self.read_long))

    def read_list_ulong(self):
        "int64[]"
        return cast(list[int], self._read_list(self.read_ulong))

    def read_list_float(self):
        return cast(list[float], self._read_list(self.read_float))

    def read_list_double(self):
        return cast(list[float], self._read_list(self.read_double))

    def read_list_bool(self):
        return cast(list[bool], self._read_list(self.read_bool))

    def read_list_str(self):
        return cast(list[str], self._read_list(self.read_str))


# ------------------------------------------------------------------------------------


_EndianType = Literal[
    # https://docs.python.org/zh-cn/3/library/struct.html#byte-order-size-and-alignment
    '@',  # 按原字节
    '=',  # 按原字节
    '<',  # 小端
    '>',  # 大端
    '!',  # 网络（=大端）
]
