# Copyright: 2020, Ableton AG, Berlin. All rights reserved.

import weakref

from AbletonIPC import create_ipc_channel
from Queue import Empty, Queue


class IPCChannel(object):
    """
    A thread-safe wrapper for AbletonIPC
    """

    def __init__(self, channel_id, events=[]):
        self._channel = create_ipc_channel(channel_id)

        for event in events:
            self.register_event(event)

        # ipc messages must be sent and received on the main thread
        # so we use message queues for thread-safe ipc
        self._outgoing_messages = Queue()
        self._incoming_messages = Queue()

    def receive_message(self):
        try:
            message = self._incoming_messages.get_nowait()
            self._incoming_messages.task_done()
            return message
        except Empty:
            return None

    def process(self):
        """
        Attempt to send all messages on the outgoing
        message queue
        """
        while not self._outgoing_messages.empty():
            try:
                name, payload = self._outgoing_messages.get_nowait()
                self._channel.send(name, payload)
                self._outgoing_messages.task_done()
            except Empty:
                break
        self._channel.poll()

    def send(self, event, payload, immediate=False):
        """
        Put a message consisting of `event` and `payload` in
        the outgoing message queue.

        If `immediate` is `True`, then send the message right away.
        This option is only safe from the same thread that created this
        object
        """
        self._outgoing_messages.put((event, payload))

        if immediate:
            self.process()

    def register_event(self, event):
        """
        Register `event` so that received messages of this
        type will be put on the incoming messages queue
        when `process` is called
        """
        channel = weakref.ref(self)

        def queue_message(message, payload):
            # Use weakref in the callback so as not to create a circular
            # reference between the ipc channel object and the connector object
            channel()._incoming_messages.put(message)

        self._channel.register_event(event, queue_message)
