
"""
SylkServer-specific conference-info+xml extensions.

These XML elements are emitted by SylkServer's conference focus inside
the standard RFC 4575 `conference-info` payload and consumed by:

  * the conference admin HTTP API on the server side, and
  * external clients (the sylk-janus-audio-bridge, the webrtcgateway and
    its connected WebRTC clients) that read the conference NOTIFY.

All elements live in the AG Projects custom namespace already declared
by sipsimple (`urn:ag-projects:xml:ns:conference-info`, prefix
`agp-conf`), so a NOTIFY using them validates against the standard
schema with these extensions added.

The generic *EndpointExtension* mechanism (and its sibling marker
classes `UserExtension`, `ConferenceDescriptionExtension`) stays in
sipsimple — it's a reusable capability, not sylkserver-specific.

Importing this module is a side effect: every class below registers
itself on the appropriate sipsimple element at import time.

This module lives in `sylk.payloads` (NOT under any single application)
on purpose. The conference and webrtcgateway applications both need
these extensions registered with sipsimple before they parse or build
a conference-info NOTIFY, but neither application should have to drag
the other's package into the import graph to get them. In particular,
importing `sylk.applications.conference.payloads` (the previous home of
these extensions) implicitly executed `sylk.applications.conference`'s
package `__init__.py`, which defines `ConferenceApplication`. That
class is auto-registered with `ApplicationRegistry` by the
`SylkApplicationMeta` metaclass at class-definition time, bypassing
the `[Server] disabled_applications` list and causing the conference
focus to come up (REGISTERs, UDP server, observers, …) on any process
that imported this module — including a webrtcgateway-only deployment.
Keeping the registrations in a neutral package eliminates that import
side effect.
"""

from sipsimple.payloads import (XMLAttribute, XMLBooleanElement, XMLElement,
                                XMLStringElement, XMLUnsignedIntElement)
from sipsimple.payloads.conference import (ConferenceDescription,
                                           ConferenceDescriptionExtension,
                                           ConferenceDocument,
                                           Endpoint, EndpointExtension,
                                           User, UserExtension,
                                           agp_conf_namespace)


__all__ = ('AdminEndpointURL', 'AdminEndpointToken',
           'AudioLevelsUdpEndpoint',
           'MutedFlag', 'ParticipantId', 'UserAgent', 'RadioPTT', 'Duration')


# --- User extensions -----------------------------------------------------

class AdminEndpointURL(XMLStringElement, UserExtension):
    """Base URL of the conference admin HTTP API on this focus.

    Published only on the sylk-janus-audio-bridge participant's User
    element so the bridge can drive the admin API (mute, audio levels,
    kick). Other participants do not receive it.
    """
    _xml_tag = 'admin_endpoint_url'
    _xml_namespace = agp_conf_namespace
    _xml_document = ConferenceDocument


User.register_extension('admin_endpoint_url', AdminEndpointURL)


class AdminEndpointToken(XMLStringElement, UserExtension):
    """Per-room bearer token the focus accepts on its admin HTTP API.

    Paired with `admin_endpoint_url`. Generated by the focus at room
    creation and stable for the lifetime of that room. Published only
    on the audio bridge participant; non-bridge participants cannot
    impersonate the bridge.
    """
    _xml_tag = 'admin_endpoint_token'
    _xml_namespace = agp_conf_namespace
    _xml_document = ConferenceDocument


User.register_extension('admin_endpoint_token', AdminEndpointToken)


class AudioLevelsUdpEndpoint(XMLStringElement, UserExtension):
    """host:port of the conference focus's audio-level UDP server.

    Published on the audio bridge participant so a webrtcgateway running
    on a different host can discover where to subscribe for real-time
    level updates. The webrtcgateway opens a UDP socket, sends a
    `subscribe` datagram (using `admin_endpoint_token` as the shared
    secret), and the focus then streams `audio-levels` datagrams back
    every audio_level_notify_period milliseconds. See
    sylk.applications.conference.audio_level_udp for the wire protocol.
    """
    _xml_tag = 'audio_levels_udp_endpoint'
    _xml_namespace = agp_conf_namespace
    _xml_document = ConferenceDocument


User.register_extension('audio_levels_udp_endpoint', AudioLevelsUdpEndpoint)


# --- Endpoint extensions -------------------------------------------------

class MutedFlag(XMLBooleanElement, EndpointExtension):
    """Server-side input-mute state of a participant's audio stream.

    Set on every endpoint EXCEPT the audio bridge so subscribers see a
    definitive True/False instead of inferring "absence == not muted".
    The bridge endpoint deliberately omits the flag — the bridge's own
    audio path is not interesting to publish back to itself.
    """
    _xml_tag = 'muted'
    _xml_namespace = agp_conf_namespace
    _xml_document = ConferenceDocument


Endpoint.register_extension('muted', MutedFlag)


class ParticipantId(XMLStringElement, EndpointExtension):
    """Short opaque per-session identifier for a participant endpoint.

    The standard `entity` attribute on Endpoint (the device's Contact
    URI) is already unique per device but unwieldy as an HTTP URL path
    component. `participant_id` is a `secrets.token_urlsafe(8)` value
    generated by the focus at session join. Stable for the lifetime of
    that one SIP session; the canonical identifier accepted by the
    admin HTTP API to disambiguate multiple devices sharing one AoR.
    """
    _xml_tag = 'participant_id'
    _xml_namespace = agp_conf_namespace
    _xml_document = ConferenceDocument


Endpoint.register_extension('participant_id', ParticipantId)


class UserAgent(XMLStringElement, EndpointExtension):
    """SIP User-Agent of a participant's endpoint.

    Copied verbatim from the `User-Agent` header of the participant's
    INVITE (the focus stashes the INVITE headers on the session, so this
    is available without an extra lookup). Published on every non-bridge
    endpoint so subscribers — in particular the webrtcgateway's admin web
    UI — can show which SIP client each caller is using. The bridge
    endpoint omits it: the bridge is plumbing, not a user-facing client.
    Absent when the INVITE carried no User-Agent header.
    """
    _xml_tag = 'user_agent'
    _xml_namespace = agp_conf_namespace
    _xml_document = ConferenceDocument


Endpoint.register_extension('user_agent', UserAgent)


class RadioPTT(XMLElement, EndpointExtension):
    """On-air state of the radio driven by this endpoint (docs/radio-ptt.md).

    Present only on endpoints that can key a two-way radio; its presence
    is the capability flag. `elapsed` is whole seconds in the current
    state, computed by the focus on every build so clients need no clock
    sync. All attributes are strings on the wire.

    sipclients3 (sipclient/radio_ptt.py) carries an identical definition
    so the CLI can parse it; registration tolerates either being loaded
    first in the same process.
    """
    _xml_tag = 'radio_ptt'
    _xml_namespace = agp_conf_namespace
    _xml_document = ConferenceDocument

    state = XMLAttribute('state', type=str, required=True, test_equal=False)
    by = XMLAttribute('by', type=str, required=False, test_equal=False)
    elapsed = XMLAttribute('elapsed', type=str, required=False, test_equal=False)
    timeout = XMLAttribute('timeout', type=str, required=False, test_equal=False)
    reason = XMLAttribute('reason', type=str, required=False, test_equal=False)

    def __init__(self, state, by=None, elapsed=None, timeout=None, reason=None):
        XMLElement.__init__(self)
        self.state = state
        self.by = by
        self.elapsed = elapsed
        self.timeout = timeout
        self.reason = reason


if not hasattr(Endpoint, 'radio_ptt'):
    Endpoint.register_extension('radio_ptt', RadioPTT)


# --- ConferenceDescription extensions ------------------------------------

class Duration(XMLUnsignedIntElement, ConferenceDescriptionExtension):
    """Seconds elapsed since the conference room was created on the focus.

    Computed server-side at the moment the conference-info payload is
    rendered. Late joiners read it once on receipt and add their own
    local elapsed time to maintain a running counter, sidestepping
    timezone, daylight-saving and clock-skew problems that come with
    publishing an absolute timestamp.
    """
    _xml_tag = 'duration'
    _xml_namespace = agp_conf_namespace
    _xml_document = ConferenceDocument


ConferenceDescription.register_extension('duration', Duration)
