#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Provides an abstract base class for oscilloscope instruments
"""

# IMPORTS #####################################################################

from __future__ import absolute_import
from __future__ import division

import abc

from future.utils import with_metaclass

from instruments.abstract_instruments import Instrument

# CLASSES #####################################################################


class OscilloscopeDataSource(with_metaclass(abc.ABCMeta, object)):

    """
    Abstract base class for data sources (physical channels, math, ref) on
    an oscilloscope.

    All applicable concrete instruments should inherit from this ABC to
    provide a consistent interface to the user.
    """

    def __init__(self, parent, name):
        self._parent = parent
        self._name = name
        self._old_dsrc = None

    def __enter__(self):
        self._old_dsrc = self._parent.data_source
        if self._old_dsrc != self:
            # Set the new data source, and let __exit__ cleanup.
            self._parent.data_source = self
        else:
            # There's nothing to do or undo in this case.
            self._old_dsrc = None

    def __exit__(self, type, value, traceback):
        if self._old_dsrc is not None:
            self._parent.data_source = self._old_dsrc

    def __eq__(self, other):
        if not isinstance(other, type(self)):
            return NotImplemented

        return other.name == self.name

    __hash__ = None

    # PROPERTIES #

    @abc.abstractproperty
    def name(self):
        """
        Gets the name of the channel. This is an abstract property.

        :type: `str`
        """
        raise NotImplementedError

    # METHODS #

    @abc.abstractmethod
    def read_waveform(self, bin_format=True):
        """
        Gets the waveform of the specified data source channel. This is an
        abstract property.

        :param bool bin_format: If the waveform should be transfered in binary
            (``True``) or ASCII (``False``) formats.
        :return: The waveform with both x and y components.
        :rtype: `numpy.ndarray`
        """
        raise NotImplementedError


class OscilloscopeChannel(with_metaclass(abc.ABCMeta, object)):

    """
    Abstract base class for physical channels on an oscilloscope.

    All applicable concrete instruments should inherit from this ABC to
    provide a consistent interface to the user.
    """

    # PROPERTIES #

    @property
    @abc.abstractmethod
    def coupling(self):
        """
        Gets/sets the coupling setting for the oscilloscope. This is an
        abstract method.

        :type: `~enum.Enum`
        """
        raise NotImplementedError

    @coupling.setter
    @abc.abstractmethod
    def coupling(self, newval):
        raise NotImplementedError


class Oscilloscope(with_metaclass(abc.ABCMeta, Instrument)):

    """
    Abstract base class for oscilloscope instruments.

    All applicable concrete instruments should inherit from this ABC to
    provide a consistent interface to the user.
    """

    # PROPERTIES #

    @abc.abstractproperty
    def channel(self):
        """
        Gets an iterator or list for easy Pythonic access to the various
        channel objects on the oscilloscope instrument. Typically generated
        by the `~instruments.util_fns.ProxyList` helper.
        """
        raise NotImplementedError

    @abc.abstractproperty
    def ref(self):
        """
        Gets an iterator or list for easy Pythonic access to the various
        ref data sources objects on the oscilloscope instrument. Typically
        generated by the `~instruments.util_fns.ProxyList` helper.
        """
        raise NotImplementedError

    @abc.abstractproperty
    def math(self):
        """
        Gets an iterator or list for easy Pythonic access to the various
        math data sources objects on the oscilloscope instrument. Typically
        generated by the `~instruments.util_fns.ProxyList` helper.
        """
        raise NotImplementedError

    # METHODS #

    @abc.abstractmethod
    def force_trigger(self):
        """
        Forces a trigger event to occur on the attached oscilloscope.
        """
        raise NotImplementedError
