"""Defines classes that can write markers to LabStreamingLayer StreamOutlet."""
import logging
from typing import Any
import pylsl

log = logging.getLogger(__name__)


class MarkerWriter():
    """Abstract base class for an object that can be used to handle stimulus
    markers.
    """

    def push_marker(self, marker: Any, lsl_time: float = None):
        """Push the given stimulus marker for processing.

        Parameters:
            marker : any object that can be converted to a str
            lsl_time : optional float indicating the time to associate with
                the marker.
        """
        raise NotImplementedError()

    def now(self) -> float:
        """Returns current time from the clock internal to the marker
        writer."""
        raise NotImplementedError()

    def cleanup(self):
        """Performs any necessary cleanup"""
        raise NotImplementedError()


class LslMarkerWriter(MarkerWriter):
    """Writes stimulus markers to a LabStreamingLayer StreamOutlet
    using pylsl. To consume this data, the client code would need to create a
    pylsl.StreamInlet. See https://github.com/sccn/labstreaminglayer/wiki.
    """

    def __init__(self):
        super(LslMarkerWriter, self).__init__()
        markers_info = pylsl.StreamInfo("BCI Stimulus Markers",
                                        "Markers", 1, 0, 'string',
                                        "bci_stim_markers")
        self.markers_outlet = pylsl.StreamOutlet(markers_info)
        self._stamp = None

    def now(self) -> float:
        """Create the timestamp for the next marker using the pylsl
        local_clock."""
        return pylsl.local_clock()

    def push_marker(self, marker: Any, lsl_time: float = None):
        """Push the given stimulus marker for processing.
        Parameters:
            marker : any object that can be converted to a str
            lsl_time : optional float indicating the time to associate with
                the marker. If omitted, the pylsl.local_clock() will be used.
        """
        stamp = lsl_time if lsl_time is not None else self.now()
        self.markers_outlet.push_sample([str(marker)], stamp)
        log.debug("Pushing marker: %s; timestamp: %s", marker, stamp)

    def cleanup(self):
        """Cleans up the StreamOutlet."""
        del self.markers_outlet


class NullMarkerWriter(MarkerWriter):
    """MarkerWriter which doesn't write anything.

    A NullMarkerWriter can be passed in to the calling object in scenarios
    where marker handling occurs indirectly (ex. through a trigger box). By
    using a NullMarkerWriter rather than a None value, the calling
    object does not have to do additional null checks and a separation
    of concerns is maintained regarding how triggers are written for different
    devices.

    See the Null Object Design Pattern:
    https://en.wikipedia.org/wiki/Null_object_pattern
    """

    def push_marker(self, marker: Any, lsl_time: float = None):
        pass

    def now(self) -> float:
        return 0.0

    def cleanup(self):
        pass

"""Defines classes that can write markers to LabStreamingLayer StreamOutlet."""
import logging
from typing import Any
import pylsl

log = logging.getLogger(__name__)


class MarkerWriter():
    """Abstract base class for an object that can be used to handle stimulus
    markers.
    """

    def push_marker(self, marker: Any, lsl_time: float = None):
        """Push the given stimulus marker for processing.

        Parameters:
            marker : any object that can be converted to a str
            lsl_time : optional float indicating the time to associate with
                the marker.
        """
        raise NotImplementedError()

    def now(self) -> float:
        """Returns current time from the clock internal to the marker
        writer."""
        raise NotImplementedError()

    def cleanup(self):
        """Performs any necessary cleanup"""
        raise NotImplementedError()


class LslMarkerWriter(MarkerWriter):
    """Writes stimulus markers to a LabStreamingLayer StreamOutlet
    using pylsl. To consume this data, the client code would need to create a
    pylsl.StreamInlet. See https://github.com/sccn/labstreaminglayer/wiki.
    """

    def __init__(self):
        super(LslMarkerWriter, self).__init__()
        markers_info = pylsl.StreamInfo("BCI Stimulus Markers",
                                        "Markers", 1, 0, 'string',
                                        "bci_stim_markers")
        self.markers_outlet = pylsl.StreamOutlet(markers_info)
        self._stamp = None

    def now(self) -> float:
        """Create the timestamp for the next marker using the pylsl
        local_clock."""
        return pylsl.local_clock()

    def push_marker(self, marker: Any, lsl_time: float = None):
        """Push the given stimulus marker for processing.
        Parameters:
            marker : any object that can be converted to a str
            lsl_time : optional float indicating the time to associate with
                the marker. If omitted, the pylsl.local_clock() will be used.
        """
        stamp = lsl_time if lsl_time is not None else self.now()
        self.markers_outlet.push_sample([str(marker)], stamp)
        log.debug("Pushing marker: %s; timestamp: %s", marker, stamp)

    def cleanup(self):
        """Cleans up the StreamOutlet."""
        del self.markers_outlet


class NullMarkerWriter(MarkerWriter):
    """MarkerWriter which doesn't write anything.

    A NullMarkerWriter can be passed in to the calling object in scenarios
    where marker handling occurs indirectly (ex. through a trigger box). By
    using a NullMarkerWriter rather than a None value, the calling
    object does not have to do additional null checks and a separation
    of concerns is maintained regarding how triggers are written for different
    devices.

    See the Null Object Design Pattern:
    https://en.wikipedia.org/wiki/Null_object_pattern
    """

    def push_marker(self, marker: Any, lsl_time: float = None):
        pass

    def now(self) -> float:
        return 0.0

    def cleanup(self):
        pass

Defines classes that can write markers to LabStreamingLayer StreamOutlet. Abstract base class for an object that can be used to handle stimulus     markers. Push the given stimulus marker for processing.          Parameters:             marker : any object that can be converted to a str             lsl_time : optional float indicating the time to associate with                 the marker. Returns current time from the clock internal to the marker         writer. Performs any necessary cleanup Writes stimulus markers to a LabStreamingLayer StreamOutlet     using pylsl. To consume this data, the client code would need to create a     pylsl.StreamInlet. See https://github.com/sccn/labstreaminglayer/wiki. Create the timestamp for the next marker using the pylsl         local_clock. Push the given stimulus marker for processing.         Parameters:             marker : any object that can be converted to a str             lsl_time : optional float indicating the time to associate with                 the marker. If omitted, the pylsl.local_clock() will be used. Cleans up the StreamOutlet. MarkerWriter which doesn't write anything.      A NullMarkerWriter can be passed in to the calling object in scenarios     where marker handling occurs indirectly (ex. through a trigger box). By     using a NullMarkerWriter rather than a None value, the calling     object does not have to do additional null checks and a separation     of concerns is maintained regarding how triggers are written for different     devices.      See the Null Object Design Pattern:     https://en.wikipedia.org/wiki/Null_object_pattern