"""
A minimal re-implementation of .NET's System.IO.BinaryReader primitives,
just enough to read Mids Reborn's .mhd database files.

Mids Reborn (C#) writes these files with a plain BinaryWriter, so the
on-disk format is exactly .NET's default binary encoding:
  - strings: a 7-bit-encoded length prefix, then that many UTF-8 bytes
  - Int32 / Single / Int64: little-endian, fixed width
  - Boolean: 1 byte, 0x00 / 0x01

This class mirrors that reading protocol.
"""
import struct


class MhdReader:
    def __init__(self, path):
        self._f = open(path, "rb")
        self.path = path

    def close(self):
        self._f.close()

    def tell(self):
        return self._f.tell()

    def read_7bit_encoded_int(self):
        result = 0
        shift = 0
        while True:
            b = self._f.read(1)
            if not b:
                raise EOFError(f"EOF while reading 7-bit length prefix at {self.tell()} in {self.path}")
            byte = b[0]
            result |= (byte & 0x7F) << shift
            if (byte & 0x80) == 0:
                break
            shift += 7
        return result

    def read_string(self):
        length = self.read_7bit_encoded_int()
        if length == 0:
            return ""
        data = self._f.read(length)
        if len(data) != length:
            raise EOFError(f"EOF while reading string of length {length} at {self.tell()} in {self.path}")
        return data.decode("utf-8")

    def read_int32(self):
        data = self._f.read(4)
        if len(data) != 4:
            raise EOFError(f"EOF while reading int32 at {self.tell()} in {self.path}")
        return struct.unpack("<i", data)[0]

    def read_int64(self):
        data = self._f.read(8)
        if len(data) != 8:
            raise EOFError(f"EOF while reading int64 at {self.tell()} in {self.path}")
        return struct.unpack("<q", data)[0]

    def read_single(self):
        data = self._f.read(4)
        if len(data) != 4:
            raise EOFError(f"EOF while reading single at {self.tell()} in {self.path}")
        return struct.unpack("<f", data)[0]

    def read_boolean(self):
        data = self._f.read(1)
        if len(data) != 1:
            raise EOFError(f"EOF while reading boolean at {self.tell()} in {self.path}")
        return data[0] != 0

    def expect_string(self, expected):
        """Read a string and assert it matches a known section-header marker."""
        actual = self.read_string()
        if actual != expected:
            raise ValueError(
                f"Expected header {expected!r}, got {actual!r} at offset {self.tell()} in {self.path}"
            )
        return actual

    def read_counted_list(self, read_item_fn):
        """Mids' recurring pattern: an Int32 N, then N+1 items follow."""
        count = self.read_int32() + 1
        return [read_item_fn() for _ in range(count)]

    def read_string_array(self):
        return self.read_counted_list(self.read_string)

    def read_int32_array(self):
        return self.read_counted_list(self.read_int32)
