# Copyright (c) 2026 동일비전(Dongil Vision Korea). All Rights Reserved.
"""
╔══════════════════════════════════════════════════════════════════════════════╗
║  ALASKA v2.6 - SmRingBuffer                                                 ║
║  Lock-free Ring Buffer for Fast IPC                                          ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Version : 2.6.0                                                             ║
║  Date    : 2026-03-03                                                        ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Summary:                                                                    ║
║    Single Producer Single Consumer (SPSC) 패턴 lock-free 링 버퍼            ║
║    Layout: [write_idx: int64] [read_idx: int64] [data: uint8[N]]            ║
║  Changes (v2.6.0):                                                           ║
║    - numpy 완전 제거 → struct + memoryview 직접 접근                         ║
║    - write: np.array([len]).view(uint8) → struct.pack_into (heap 할당 0)     ║
║    - read: .view(np.uint32)[0] → struct.unpack_from (heap 할당 0)            ║
║    - index 읽기: int(ndarray[0]) → struct.unpack_from (numpy→int 변환 제거)  ║
║    - data 복사: ndarray slice → memoryview slice (중간객체 제거)              ║
╚══════════════════════════════════════════════════════════════════════════════╝
"""

from typing import Optional
import struct
import os

from ._base import _ShmBase


class SpscViolationError(RuntimeError):
    """SPSC 위반 감지 — 다중 writer 또는 다중 reader 접근 시 발생

    SmRingBuffer는 Single Producer Single Consumer 전용입니다.
    다중 producer 필요 시 SmQueue(lock=mp.Lock())를 사용하세요.
    """
    def __init__(self, name: str, role: str, expected_pid: int, actual_pid: int):
        self.name = name
        self.role = role
        self.expected_pid = expected_pid
        self.actual_pid = actual_pid
        super().__init__(
            f"SmRingBuffer('{name}') SPSC violation: "
            f"{role} PID {expected_pid} → {actual_pid}"
        )

# Pre-built struct formatters (avoid repeated struct.Struct creation)
_ST_I64 = struct.Struct('<q')   # int64 little-endian
_ST_U32 = struct.Struct('<I')   # uint32 little-endian


class SmRingBuffer(_ShmBase):
    """고속 공유 메모리 링 버퍼 (Lock-free SPSC)

    Single Producer Single Consumer (SPSC) 전용 — 1개 프로세스만 write,
    1개 프로세스만 read 호출 가능합니다.

    주의: 다중 writer 시 53% 데이터 손상 발생 (RC-04 시험 결과).
    다중 producer 필요 시 SmQueue(lock=mp.Lock()) 사용하세요.
    SPSC 위반 시 SpscViolationError 예외가 자동 발생합니다 (M-04).

    Layout: [write_idx: int64] [read_idx: int64] [data: uint8[N]]
    - 1 슬롯 예약으로 full/empty 구분 (write_idx == read_idx → empty)
    - stored_size 카운터 없음 → 경쟁 조건 근본 해결

    Usage:
        # Producer (1개 프로세스만)
        buf = SmRingBuffer("my_buffer", size=1024, create=True)
        buf.write(b"hello")

        # Consumer (1개 프로세스만)
        buf = SmRingBuffer("my_buffer", size=1024, create=False)
        data = buf.read()  # b"hello"
    """
    __slots__ = ('_size', '_buf', '_writer_pid', '_reader_pid')

    _HEADER_SIZE = 16  # 2 * int64

    def __init__(self, name: str, size: int = 4096, create: bool = False):
        self._init_shm(name, self._HEADER_SIZE + size, create)
        self._size = size
        self._writer_pid = 0
        self._reader_pid = 0
        self._buf = self._shm.buf

        if create:
            _ST_I64.pack_into(self._buf, 0, 0)   # write_idx = 0
            _ST_I64.pack_into(self._buf, 8, 0)   # read_idx = 0

    def _used(self) -> int:
        """사용 중인 바이트 수 (write_idx - read_idx 기반)"""
        buf = self._buf
        w = _ST_I64.unpack_from(buf, 0)[0]
        r = _ST_I64.unpack_from(buf, 8)[0]
        return (w - r) % self._size

    def write(self, data: bytes) -> bool:
        """데이터 쓰기 (SPSC에서 lock-free)

        Returns:
            True=성공, False=버퍼 부족
        """
        pid = os.getpid()
        if self._writer_pid == 0:
            self._writer_pid = pid
        elif self._writer_pid != pid:
            raise SpscViolationError(self._name, "writer", self._writer_pid, pid)
        length = len(data)
        total = length + 4  # 4 bytes length prefix
        sz = self._size
        capacity = sz - 1   # 1 슬롯 예약 (full/empty 구분)
        if total > capacity:
            return False

        buf = self._buf
        hdr = self._HEADER_SIZE

        # Index 읽기 — struct.unpack_from (numpy→int 변환 제거)
        write_pos = _ST_I64.unpack_from(buf, 0)[0]
        used = (write_pos - _ST_I64.unpack_from(buf, 8)[0]) % sz
        if total > capacity - used:
            return False  # 버퍼 부족

        # Length prefix — struct.pack_into 직접 기록 (heap 할당 0)
        lp = hdr + write_pos
        end = write_pos + 4
        if end <= sz:
            _ST_U32.pack_into(buf, lp, length)
        else:
            # wrap: length prefix가 경계를 넘는 경우 (드묾)
            tmp = _ST_U32.pack(length)
            split = sz - write_pos
            buf[lp:lp + split] = tmp[:split]
            buf[hdr:hdr + 4 - split] = tmp[split:]

        # Data — memoryview 직접 복사 (numpy 중간객체 제거)
        start = (write_pos + 4) % sz
        dp = hdr + start
        end = start + length
        if end <= sz:
            buf[dp:dp + length] = data
        else:
            split = sz - start
            buf[dp:dp + split] = data[:split]
            buf[hdr:hdr + length - split] = data[split:]

        # Write index 업데이트 (data 쓰기 완료 후)
        _ST_I64.pack_into(buf, 0, (write_pos + total) % sz)
        return True

    def read(self) -> Optional[bytes]:
        """데이터 읽기 (SPSC에서 lock-free)

        Returns:
            데이터 또는 None (버퍼 비어있음)
        """
        pid = os.getpid()
        if self._reader_pid == 0:
            self._reader_pid = pid
        elif self._reader_pid != pid:
            raise SpscViolationError(self._name, "reader", self._reader_pid, pid)
        buf = self._buf
        hdr = self._HEADER_SIZE

        # Index 읽기
        read_pos = _ST_I64.unpack_from(buf, 8)[0]
        sz = self._size
        used = (_ST_I64.unpack_from(buf, 0)[0] - read_pos) % sz
        if used == 0:
            return None

        # Length prefix 디코딩
        lp = hdr + read_pos
        end = read_pos + 4
        if end <= sz:
            length = _ST_U32.unpack_from(buf, lp)[0]
        else:
            split = sz - read_pos
            tmp = bytearray(4)
            tmp[:split] = buf[lp:lp + split]
            tmp[split:] = buf[hdr:hdr + 4 - split]
            length = _ST_U32.unpack(tmp)[0]

        # Length 범위 검증
        if length > sz - 5:
            # 복구: 버퍼 비움
            _ST_I64.pack_into(buf, 8, _ST_I64.unpack_from(buf, 0)[0])
            return None

        # Data 읽기
        start = (read_pos + 4) % sz
        dp = hdr + start
        end = start + length
        if end <= sz:
            result = bytes(buf[dp:dp + length])
        else:
            split = sz - start
            result = bytes(buf[dp:dp + split]) + bytes(buf[hdr:hdr + length - split])

        # Read index 업데이트
        _ST_I64.pack_into(buf, 8, (read_pos + 4 + length) % sz)
        return result

    def available(self) -> int:
        """읽을 수 있는 대략적 바이트 수 (메시지 수 아님)"""
        return self._used()

    def close(self):
        if self._closed:
            return
        try:
            self._buf.release()
        except Exception:
            pass
        self._buf = None
        super().close()

    @property
    def size(self) -> int:
        return self._size

    def reset(self):
        """읽기/쓰기 인덱스 초기화 (재시작 시 사용).

        F2: SPSC PID 추적 초기화 + 안전 순서
        - write_idx = read_idx 로 먼저 empty 전환 (진행 중 read 보호)
        - 이후 양쪽 0 초기화
        - SPSC PID 리셋 (재시작 후 다른 프로세스 접근 허용)
        """
        self._writer_pid = 0
        self._reader_pid = 0
        # 1) empty 전환 (진행 중 reader가 새 데이터를 읽지 않도록)
        r = _ST_I64.unpack_from(self._buf, 8)[0]
        _ST_I64.pack_into(self._buf, 0, r)   # write_idx = read_idx
        # 2) 양쪽 0 초기화
        _ST_I64.pack_into(self._buf, 0, 0)
        _ST_I64.pack_into(self._buf, 8, 0)

    def __getstate__(self):
        """Pickle 직렬화 (프로세스 간 SharedMemory 전달용)."""
        return {'name': self._name, 'size': self._size}

    def __setstate__(self, state):
        """Pickle 역직렬화 (자식 프로세스에서 SharedMemory 재연결)."""
        self._init_shm(state['name'], self._HEADER_SIZE + state['size'], create=False)
        self._size = state['size']
        self._writer_pid = 0
        self._reader_pid = 0
        self._buf = self._shm.buf

    def __repr__(self):
        return f"SmRingBuffer({self._name}, size={self._size}, used={self._used()})"
