"""
File: AdroitConfig.py
Description: Python COM wrapper for Adroit OLE server configuration
Author: Diego Acuña
Revision: 0.1.1
Created: 2026-01-22
Last Modified: 2026-01-23
"""
import atexit
from typing import cast, Optional, TYPE_CHECKING, Any
import win32com.client
from win32com.client import gencache
import pythoncom
import pywintypes

if TYPE_CHECKING:
    # static type checkers / editors will read AdroitConfig.pyi for this name
    from Adroit import Adroit as _AdroitBase  # type: ignore
else:
    _AdroitBase = object  # runtime base

AdroitType = Any  # runtime fallback for the _com attribute

# Module-level instance exposed to client code
adroitconfig = None

class AdroitConfigOLE(_AdroitBase):
    """Python COM wrapper for Adroit OLE server configuration"""
    # names on the proxied COM object we want to hide/deny at runtime
    BLOCKED_COM_NAMES = frozenset({
        "Poke",
        "Fetch",
        "FetchValues",
        "FetchChanges",
        "GetSlotInfo"
    })

    def __init__(self, prog_id="adroitserver", auto_connect=True):
        self._prog_id = prog_id
        self._com: Optional["AdroitType"] = None
        self._connected = False
        if auto_connect:
            self.connect()

    def connect(self):
        if self._connected:
            return self
        pythoncom.CoInitialize()
        try:
            # prefer generated wrapper for IntelliSense
            try:
                self._com = cast("AdroitType", gencache.EnsureDispatch(self._prog_id))
            except Exception as e:
                # Some COM servers cannot automate makepy — fall back to dynamic (late) dispatch
                try:
                    self._com = cast("AdroitType", win32com.client.Dispatch(self._prog_id))
                except Exception:
                    raise
            self._connected = True
            return self
        except Exception:
            pythoncom.CoUninitialize()
            raise

    def close(self):
        if not self._connected:
            return
        try:
            # release reference to COM object
            self._com = None
        finally:
            try:
                pythoncom.CoUninitialize()
            finally:
                self._connected = False

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

    def __getattr__(self, name):
        # deny access to blocked COM names
        if name in self.BLOCKED_COM_NAMES:
            raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
        if self._com is None:
            raise AttributeError("Adroit COM object is not connected")
        return getattr(self._com, name)

    
# create the module-level object but avoid connecting automatically at import time
adroitconfig = AdroitConfigOLE(auto_connect=False)
atexit.register(lambda: adroitconfig.close())

def Main():
    try:
        # adroitconfig is available as module variable `adroitconfig`.
        # Connect lazily for interactive use:
        adroitconfig.connect()
        print("AdroitConfig is available as module variable `adroitconfig`.")
        print("Ping ->", adroitconfig.ping())
    except Exception as e:
        print("COM error:", e)
    finally:
        input("\nPress Enter to exit...")
        adroitconfig.close()

if __name__ == "__main__":
    Main()