# Copyright (c) 2026 동일비전(Dongil Vision Korea). All Rights Reserved.
"""
╔══════════════════════════════════════════════════════════════════════════════╗
║  ALASKA v2.6 - sm_sync                                                       ║
║  Synchronization Primitives for Inter-Process Communication                 ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Version : 2.6.0                                                             ║
║  Date    : 2026-03-04                                                        ║
╠══════════════════════════════════════════════════════════════════════════════╣
║  Classes:                                                                    ║
║    SmKernelEvent   — Win32 커널 이벤트 (CPU 0% 대기, 20~50μs 반응)          ║
║    SmLockFreeEvent — 스핀락 공유 메모리 이벤트 (1μs 이내 반응)              ║
║    SmMutex         — Win32 Named Mutex (Manager.Lock 대체)                  ║
╚══════════════════════════════════════════════════════════════════════════════╝
"""

import ctypes
import time
import warnings

from .sm_value import SmValue


# ═══════════════════════════════════════════════════════════════
#  SmKernelEvent — Windows Kernel Event (No CPU overhead wait)
# ═══════════════════════════════════════════════════════════════

# Windows Constants
INFINITE = 0xFFFFFFFF
WAIT_OBJECT_0 = 0x00000000
WAIT_TIMEOUT = 0x00000102
EVENT_ALL_ACCESS = 0x1F0003


class SmKernelEvent:
    """Windows 커널 이벤트 동기화 객체 (Shared Asset)

    SmLockFreeEvent(스핀락)보다 지연 시간은 미세하게 길지만(수십 μs),
    대기 중 CPU 점유율이 0%이므로 장기 대기나 일반적인 프로세스 동기화에 최적입니다.
    내부적으로 kernel32.dll의 CreateEventW/OpenEventW를 사용합니다.
    """

    def __init__(self, name: str, create: bool = False, initial: bool = False, manual_reset: bool = True):
        r"""SmKernelEvent 초기화.

        Args:
            name: 커널 이벤트 이름 (Global\ prefix 권장)
            create: True=생성, False=기존 이벤트 오픈
            initial: 초기 상태 (True=Set, False=Reset)
            manual_reset: True이면 수동으로 clear() 호출 전까지 Set 유지 (추천)
        """
        self._name = name
        self._is_owner = create
        self._k32 = ctypes.windll.kernel32

        # Windows 이름 규칙 (Global\ prefix 추가 시 시스템 전역 공유 가능)
        win_name = name if name.startswith("Global\\") else rf"Global\{name}"

        if create:
            self._handle = self._k32.CreateEventW(None, manual_reset, initial, win_name)
            if not self._handle:
                raise OSError(f"Failed to create kernel event '{win_name}'. Error: {ctypes.get_last_error()}")
        else:
            self._handle = self._k32.OpenEventW(EVENT_ALL_ACCESS, False, win_name)
            if not self._handle:
                raise OSError(f"Failed to open kernel event '{win_name}'. Error: {ctypes.get_last_error()}")

    def set(self):
        """이벤트를 '설정' 상태(Signaled)로 변경"""
        if not self._k32.SetEvent(self._handle):
            raise OSError(f"Failed to set event. Error: {ctypes.get_last_error()}")

    def clear(self):
        """이벤트를 '해제' 상태(Non-signaled)로 변경 (manual_reset=True일 때 필요)"""
        if not self._k32.ResetEvent(self._handle):
            raise OSError(f"Failed to reset event. Error: {ctypes.get_last_error()}")

    def wait(self, timeout: float = None) -> bool:
        """이벤트가 설정될 때까지 커널 대기 (CPU 점유율 0%)

        Args:
            timeout: 대기 시간 (초). None이면 무한 대기.

        Returns:
            bool: 이벤트가 설정되면 True, 타임아웃이면 False.
        """
        dw_millis = int(timeout * 1000) if timeout is not None else INFINITE
        res = self._k32.WaitForSingleObject(self._handle, dw_millis)

        if res == WAIT_OBJECT_0:
            return True
        elif res == WAIT_TIMEOUT:
            return False
        else:
            raise OSError(f"Wait error on event. Result: {res}, Error: {ctypes.get_last_error()}")

    def is_set(self) -> bool:
        """현재 이벤트가 설정되어 있는지 즉시 확인 (Wait 0ms)"""
        res = self._k32.WaitForSingleObject(self._handle, 0)
        return res == WAIT_OBJECT_0

    def close(self):
        """커널 핸들 해제"""
        if hasattr(self, '_handle') and self._handle:
            try:
                self._k32.CloseHandle(self._handle)
            except (OSError, TypeError, AttributeError):
                pass
            self._handle = None

    def __del__(self):
        try:
            self.close()
        except Exception:
            pass

    @property
    def name(self) -> str:
        return self._name

    def __repr__(self):
        return f"SmKernelEvent({self._name}, owner={self._is_owner})"


# ═══════════════════════════════════════════════════════════════
#  SmLockFreeEvent — Ultra-low Latency Spin-lock Event
# ═══════════════════════════════════════════════════════════════

class SmLockFreeEvent:
    """공유 메모리 기반 초저지연 동기화 이벤트

    multiprocessing.Event는 내부적으로 OS 커널 락을 사용하여 수 ms의 지연이 발생할 수 있습니다.
    SmLockFreeEvent는 공유 메모리의 특정 바이트를 스핀락(Spin-lock) 방식으로 감시하여
    1μs 이내의 통지 속도를 보장합니다.

    주의 (RC-09 시험 결과):
        Producer가 set() 후 즉시 clear() 하면 waiter가 91.9% 놓칩니다.
        바이너리 플래그 특성상 set→clear 사이에 waiter가 깨어나지 못하면 손실됩니다.

        권장 패턴:
            - Producer는 set()만 호출
            - Consumer가 wait() 후 clear() (drain 패턴)
            - 또는 wait_drain() 편의 메서드 사용

        실운영 참고:
            ALASKA 시그널 시스템은 SmKernelEvent + Double-Check 패턴을 사용합니다.
            SmLockFreeEvent는 극저지연(μs) 필요 시에만 사용하세요.
    """

    def __init__(self, name: str, create: bool = False, initial: bool = False):
        """SmLockFreeEvent 초기화.

        내부적으로 SmValue('B') (1바이트 unsigned char)를 사용합니다.
        """
        self._name = name
        self._val = SmValue(name=name + "_evt", typecode='B', create=create,
                            initial=1 if initial else 0)

    def set(self):
        """이벤트를 '설정' 상태(1)로 변경"""
        self._val.value = 1

    def clear(self):
        """이벤트를 '해제' 상태(0)로 변경"""
        self._val.value = 0

    def is_set(self) -> bool:
        """현재 이벤트가 설정되어 있는지 확인 (Lock-free)"""
        return self._val.value == 1

    def wait(self, timeout: float = None, spin: int = 1000) -> bool:
        """이벤트가 설정될 때까지 대기.

        Args:
            timeout: 최대 대기 시간 (초). None이면 무한 대기.
            spin: OS sleep을 수행하기 전 스핀락(Busy-wait) 횟수.
                  기본 1000회 (약 수십 μs).
                  -1이면 무한 스핀 (CPU 사용율 100%, 지연 시간 최소화).

        Returns:
            bool: 이벤트가 설정되면 True, 타임아웃이면 False.
        """
        start = time.perf_counter()
        count = 0

        while self._val.value == 0:
            if spin == -1:
                pass
            elif count < spin:
                count += 1
                continue
            else:
                time.sleep(0)

            if timeout is not None:
                if (time.perf_counter() - start) > timeout:
                    return False

        return True

    def wait_drain(self, timeout: float = None, spin: int = 1000) -> bool:
        """이벤트 대기 후 자동 clear (drain 패턴).

        Producer는 set()만 호출하고, Consumer는 이 메서드로 wait+clear를
        원자적으로 수행합니다. set→clear 사이 91% 손실(RC-09) 방지.

        Args:
            timeout: 최대 대기 시간 (초). None이면 무한 대기.
            spin: OS sleep 전 스핀 횟수.

        Returns:
            bool: 이벤트가 설정되면 True, 타임아웃이면 False.
        """
        result = self.wait(timeout=timeout, spin=spin)
        if result:
            self.clear()
        return result

    def poll(self) -> bool:
        """현재 값을 즉시 읽어 반환 (Wait 없이 체크)"""
        return self.is_set()

    def close(self):
        """공유 메모리 리소스 해제"""
        self._val.close()

    def __repr__(self):
        return f"SmLockFreeEvent({self._name}, set={self.is_set()})"


# ═══════════════════════════════════════════════════════════════
#  SmMutex — Win32 Named Mutex (Manager.Lock 대체)
# ═══════════════════════════════════════════════════════════════

class SmMutex:
    """Win32 Named Mutex — Manager.Lock 대체 (직접 커널 호출, IPC 0회)"""
    __slots__ = ('_name', '_handle', '_k32')
    _MUTEX_ALL_ACCESS = 0x1F0001
    _INFINITE = 0xFFFFFFFF
    _WAIT_OBJECT_0 = 0x00000000
    _WAIT_ABANDONED = 0x00000080
    _DEFAULT_TIMEOUT = 5000  # 5초

    def __init__(self, name, create=True):
        self._name = name
        self._k32 = ctypes.windll.kernel32
        win_name = name if name.startswith("Global\\") else rf"Global\{name}"
        if create:
            self._handle = self._k32.CreateMutexW(None, False, win_name)
        else:
            self._handle = self._k32.OpenMutexW(self._MUTEX_ALL_ACCESS, False, win_name)
        if not self._handle:
            raise OSError(f"Mutex '{win_name}' failed: {ctypes.get_last_error()}")

    def acquire(self, timeout_ms=None):
        if timeout_ms is None:
            timeout_ms = self._DEFAULT_TIMEOUT
        res = self._k32.WaitForSingleObject(self._handle, timeout_ms)
        if res == self._WAIT_OBJECT_0:
            return True
        if res == self._WAIT_ABANDONED:
            warnings.warn(f"[ALASKA] Mutex '{self._name}' abandoned (owner crashed)")
            return True
        raise TimeoutError(f"Mutex '{self._name}' acquire timeout ({timeout_ms}ms)")

    def release(self):
        try:
            self._k32.ReleaseMutex(self._handle)
        except (OSError, TypeError, AttributeError):
            pass

    def __enter__(self):
        self.acquire()
        return self

    def __exit__(self, *a):
        self.release()

    def close(self):
        if hasattr(self, '_handle') and self._handle:
            try:
                self._k32.CloseHandle(self._handle)
            except (OSError, TypeError, AttributeError):
                pass
            self._handle = None

    def __del__(self):
        try:
            self.close()
        except Exception:
            pass

    def __getstate__(self):
        return {'name': self._name}

    def __setstate__(self, state):
        self.__init__(state['name'], create=False)
