#!/usr/bin/env python3
# Copyright 2026 (c) o6 Automation GmbH
"""
Client Basics: Read, Write, and Subscriptions
=============================================

Walks through the high-level ``o6.Client`` API end to end: connecting to
a server, reading and writing single and multiple values, calling a
method, subscribing to a variable for event-driven updates, and finally
introspecting the address space via ``client[NodeId]`` and ``client.browse()``.

The example is wired against `S03_firststeps.py` and talks to the
address space defined there (``Plant / Temperature``, ``Plant / Pressure``,
``Plant / Counter``, ``Plant / Running``, ``Plant / Add``, …).
"""


import os

import socket
import time
from o6 import Client, StatusCodeError, AttributeId

EXAMPLE_PORT = int(os.environ.get("O6_EXAMPLE_PORT", "4840"))
EXAMPLE_ENDPOINT = f"opc.tcp://localhost:{EXAMPLE_PORT}"


print("--- 1. Connection Setup ---")
localhost = "localhost"
endpoint_url = EXAMPLE_ENDPOINT
print(f"Connecting to {endpoint_url} ...")


try:
    with Client(endpoint_url) as client:
        print("Connected.")

        print()
        print("--- 2. Basic Read / Write ---")
        try:
            value = client.read("ns=1;i=1004")  # Plant / Counter (Int32)
            print(f"Counter = {value}")
        except StatusCodeError as e:
            print(f"Error: read failed: {e}")

        try:
            client.write("ns=1;i=1004", 42)
            print("Wrote Counter -> 42")

            new_value = client.read("ns=1;i=1004")
            print(f"Counter = {new_value}")
        except StatusCodeError as e:
            print(f"Error: write failed: {e}")


        print()
        print("--- 3. Multiple Read / Write ---")
        try:
            values = client.read(
                [
                    "ns=1;i=1001",  # Temperature (Double)
                    "ns=1;i=1002",  # Pressure    (Double)
                    "ns=1;i=1003",  # Status      (String)
                    "ns=1;i=1004",  # Counter     (Int32)
                ]
            )
            print(f"Read = {values}")
        except StatusCodeError as e:
            print(f"Error: multiple read failed: {e}")

        try:
            client.write(
                {
                    "ns=1;i=1001": 25.0,  # Temperature
                    "ns=1;i=1003": "online",  # Status
                    "ns=1;i=1004": 100,  # Counter
                }
            )
            print("Wrote Temperature, Status, Counter.")
        except StatusCodeError as e:
            print(f"Error: multiple write failed: {e}")


        print()
        print("--- 4. Method Call ---")
        refs = client.browse(
            "i=85",
            resultMask=63,  # ask for every reference field
        )
        plant_id = next(r.nodeId for r in refs if r.browseName.name == "Plant")
        print(f"Plant NodeId = {plant_id}")

        try:
            result = client.call(
                plant_id,  # object_id
                "ns=1;i=2001",  # method_id (Plant / Add)
                [3.0, 4.0],  # input_args: two Doubles
            )
            status, *outputs = result
            print(f"Add(3.0, 4.0) -> status {status.name}, result {outputs}")
        except Exception as e:
            print(f"Error: method call failed: {e}")


        def on_data_change(monitored_item, value) -> None:
            """Server-pushed callback: runs on the client's event-loop thread."""
            print(f"Counter changed -> {value}")

        print()
        print("--- 5. Subscriptions ---")
        subscription = client.createSubscription(publishingInterval=1000)

        print("Monitoring Plant / Counter (ns=1;i=1004) ...")
        monitored_item = client.monitor(
            target="ns=1;i=1004",
            callback=on_data_change,
            subscription=subscription,
            samplingInterval=500,
        )

        for i in range(3):
            client.write("ns=1;i=1004", 200 + i)
            time.sleep(1)

        print("Cleaning up subscription.")
        monitored_item.delete()
        subscription.delete()


    # New `with` block — the previous one's subscription was deleted
    # and its `client` is no longer connected.
    with Client(endpoint_url) as client:
        print()
        print("--- 6. Browsing the Address Space ---")
        node = client["i=85"]  # The standard `Objects` folder
        print(f"Node       = {node}")
        print(f"BrowseName = {node(attr=AttributeId.BROWSE_NAME)}")
        print(f"NodeClass  = {node(attr=AttributeId.NODE_CLASS)}")
        # (Browsing children of an ObjectNode is covered in
        # `C01_lowlevel.py` — use `result_mask=63` to get names.)

except StatusCodeError as e:
    print(f"Error: connection failed: {e}")
    print(f"Make sure an OPC UA server is running on localhost:{EXAMPLE_PORT}")
    print("(try: python examples/server/S03_firststeps.py)")

print()
print("Connection closed.")
print("=== Example completed ===")