# Copyright (c) 2026 동일비전(Dongil Vision Korea). All Rights Reserved.
"""
╔══════════════════════════════════════════════════════════════════════════════╗
║  ALASKA v2.3 - SmValue                                                       ║
║  Fast Shared Memory Value (No Manager overhead)                              ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Version : 2.3.0                                                             ║
║  Date    : 2026-02-27                                                        ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Summary:                                                                    ║
║    Manager.Value 소켓 IPC 오버헤드(0.1~0.5ms)를 제거하기 위한               ║
║    SharedMemory 기반 고속 스칼라 값                                          ║
╚══════════════════════════════════════════════════════════════════════════════╝
"""

from typing import Any
import warnings
import numpy as np
from ._base import _ShmBase

# increment() 무Lock 경고 — 프로세스당 1회
_WARNED_INCREMENT = set()


class SmValue(_ShmBase):
    """고속 공유 메모리 값 (Manager.Value보다 빠름)

    multiprocessing.Manager().Value()는 프로세스 간 통신에 소켓을 사용하여
    약 0.1~0.5ms의 오버헤드가 발생합니다. SmValue는 shared_memory를 직접
    사용하여 이 오버헤드를 제거합니다.

    동시성 주의:
        - .value 직접 대입은 단일 writer면 lock 없이 안전
        - increment()는 Read-Modify-Write이므로 다중 프로세스 시 lock 필수
          (lock 없으면 ~10% Lost Update 발생 — RC-01 시험 결과)

    Usage:
        # 생성 (메인 프로세스)
        counter = SmValue("my_counter", "i", create=True)  # int
        counter.value = 100

        # 연결 (서브 프로세스)
        counter = SmValue("my_counter", "i", create=False)
        print(counter.value)  # 100

    Supported types:
        - 'b': signed char (1 byte)
        - 'B': unsigned char (1 byte)
        - 'h': short (2 bytes)
        - 'H': unsigned short (2 bytes)
        - 'i': int (4 bytes)
        - 'I': unsigned int (4 bytes)
        - 'q': long long (8 bytes)
        - 'Q': unsigned long long (8 bytes)
        - 'f': float (4 bytes)
        - 'd': double (8 bytes)
    """
    __slots__ = ('_dtype', '_buffer', '_lock')

    _TYPE_MAP = {
        'b': (np.int8, 1),
        'B': (np.uint8, 1),
        'h': (np.int16, 2),
        'H': (np.uint16, 2),
        'i': (np.int32, 4),
        'I': (np.uint32, 4),
        'q': (np.int64, 8),
        'Q': (np.uint64, 8),
        'f': (np.float32, 4),
        'd': (np.float64, 8),
    }
    _DTYPE_TO_CODE = {dt: code for code, (dt, _) in _TYPE_MAP.items()}

    def __init__(self, name: str, typecode: str = 'i', create: bool = False,
                 initial: Any = 0, lock=None):
        """SmValue 초기화.

        Args:
            name: 공유 메모리 이름
            typecode: 데이터 타입 코드 ('i', 'f', 'd', etc.)
            create: True=생성, False=연결
            initial: 초기값 (create=True일 때)
            lock: 동시성 제어용 lock
        """
        if typecode not in self._TYPE_MAP:
            raise ValueError(f"Unsupported typecode: {typecode}")

        self._dtype, size = self._TYPE_MAP[typecode]
        self._init_shm(name, size, create)
        self._lock = lock
        self._buffer = np.ndarray((1,), dtype=self._dtype, buffer=self._shm.buf)

        if create:
            self._buffer[0] = initial

    @property
    def value(self):
        """값 읽기 (lock-free)"""
        return self._buffer[0]

    @value.setter
    def value(self, v):
        """값 쓰기 (lock-free)"""
        self._buffer[0] = v

    def get(self):
        """값 읽기 (lock 사용)"""
        if self._lock:
            with self._lock:
                return self._buffer[0]
        return self._buffer[0]

    def set(self, v):
        """값 쓰기 (lock 사용)"""
        if self._lock:
            with self._lock:
                self._buffer[0] = v
        else:
            self._buffer[0] = v

    def increment(self, delta=1):
        """원자적 증가 (다중 프로세스 시 lock 필수).

        lock 없이 호출하면 Read-Modify-Write 경합으로
        ~10% Lost Update 발생 가능 (RC-01 시험 결과).
        """
        if self._lock:
            with self._lock:
                self._buffer[0] += delta
                return self._buffer[0]
        if self._name not in _WARNED_INCREMENT:
            _WARNED_INCREMENT.add(self._name)
            warnings.warn(
                f"SmValue('{self._name}').increment() without lock "
                f"-- multi-process Lost Update risk (RC-01)",
                stacklevel=2
            )
        self._buffer[0] += delta
        return self._buffer[0]

    def reset(self, v=0):
        """값 초기화 (재시작 시 사용)."""
        self._buffer[0] = v

    # close() → _ShmBase.close() 상속 (중복 호출 방지 포함)

    def __getstate__(self):
        """Pickle 직렬화 (프로세스 간 전달용)."""
        code = self._DTYPE_TO_CODE.get(self._dtype, 'i')
        return {'name': self._name, 'typecode': code}

    def __setstate__(self, state):
        """Pickle 역직렬화 (자식 프로세스에서 SharedMemory 재연결)."""
        typecode = state['typecode']
        self._dtype, size = self._TYPE_MAP[typecode]
        self._init_shm(state['name'], size, create=False)
        self._lock = None
        self._buffer = np.ndarray((1,), dtype=self._dtype, buffer=self._shm.buf)

    # name property → _ShmBase.name 상속

    def __repr__(self):
        return f"SmValue({self._name}, {self._buffer[0]})"
