# See `ExecuteExtraPythonCode` in `pydrake_pybind.h` for usage details and
# rationale.

from pydrake.common.value import AbstractValue as _AbstractValue


class PySerializer(SerializerInterface):
    """Provides a Python implementation of `SerializerInterface` for use
    with `LcmPublisherSystem` and `LcmSubscriberSystem` when the given
    `lcm_type` is a Python object (not a C++ object).
    """
    def __init__(self, lcm_type):
        SerializerInterface.__init__(self)
        self._lcm_type = lcm_type

    def __repr__(self):
        return f"PySerializer({self._lcm_type.__name__})"

    def CreateDefaultValue(self):
        return _AbstractValue.Make(self._lcm_type())

    def Deserialize(self, buffer, abstract_value):
        message = self._lcm_type.decode(buffer)
        abstract_value.set_value(message)

    def Serialize(self, abstract_value):
        assert isinstance(abstract_value, _AbstractValue)
        message = abstract_value.get_value()
        assert isinstance(message, self._lcm_type)
        return message.encode()


@staticmethod
def _make_lcm_subscriber(channel,
                         lcm_type,
                         lcm,
                         use_cpp_serializer=False,
                         *,
                         wait_for_message_on_initialization_timeout=0.0):
    """Convenience to create an LCM subscriber system with a concrete type.

    Args:
        channel: LCM channel name.
        lcm_type: Python class generated by lcmgen.
        lcm: LCM service instance.
        use_cpp_serializer: Use C++ serializer to interface with LCM converter
            systems that are implemented in C++. LCM types must be registered
            in C++ via ``BindCppSerializer``.
        wait_for_message_on_initialization_timeout: Configures the behavior of
            initialization events (see ``System.ExecuteInitializationEvents``
            and ``Simulator.Initialize``) by specifying the number of seconds
            (wall-clock elapsed time) to wait for a new message. If this
            timeout is <= 0, initialization will copy any already-received
            messages into the Context but will not process any new messages.
            If this timeout is > 0, initialization will call
            ``lcm.HandleSubscriptions()`` until at least one message is
            received or until the timeout. Pass ∞ to wait indefinitely.
    """
    # TODO(eric.cousineau): Make `use_cpp_serializer` be kwarg-only.
    # N.B. This documentation is actually public, as it is assigned to classes
    # below as a static class method.
    if not use_cpp_serializer:
        serializer = PySerializer(lcm_type)
    else:
        serializer = _Serializer_[lcm_type]()
    return LcmSubscriberSystem(channel, serializer, lcm,
                               wait_for_message_on_initialization_timeout)


@staticmethod
def _make_lcm_publisher(
        channel, lcm_type, lcm, publish_period=0.0, use_cpp_serializer=False,
        *, publish_triggers=None, publish_offset=0.0):
    """Convenience to create an LCM publisher system with a concrete type.

    Args:
        channel: LCM channel name.
        lcm_type: Python class generated by lcmgen.
        lcm: LCM service instance.
        publish_period: System's publish period (in seconds). Default is 0.
        publish_offset: System's publish offset (in seconds). Default is 0.
        use_cpp_serializer: Use C++ serializer to interface with LCM converter
            systems that are implemented in C++. LCM types must be registered
            in C++ via `BindCppSerializer`.
    """
    # TODO(eric.cousineau): Make `use_cpp_serializer` be kwarg-only.
    if not use_cpp_serializer:
        serializer = PySerializer(lcm_type)
    else:
        serializer = _Serializer_[lcm_type]()
    if publish_triggers is not None:
        return LcmPublisherSystem(
            channel=channel, serializer=serializer, lcm=lcm,
            publish_triggers=publish_triggers, publish_period=publish_period,
            publish_offset=publish_offset)
    else:
        return LcmPublisherSystem(
            channel=channel, serializer=serializer, lcm=lcm,
            publish_period=publish_period, publish_offset=publish_offset)


LcmSubscriberSystem.Make = _make_lcm_subscriber
LcmPublisherSystem.Make = _make_lcm_publisher
