from abc import abstractmethod
from typing import TypeVar, Type, Generic, Optional, Union
from saleae.data import SaleaeTimeDelta

MeasureType = TypeVar('MeasureType', float, int, SaleaeTimeDelta)


class MeasureError:
    def __init__(self, label: str, *, description: str = ''):
        self.label = label
        self.description = description


class Measure(Generic[MeasureType]):
    """
    Measure declaration that appears as a class property of an :py:class:`.analog_measurer.AnalogMeasurer`

    """

    def __init__(self, label: str, *, type: Type[MeasureType] = float, description: str = '', units: str = '') -> None:
        """
        :param label: Short label used to identify this measure.
        :param type: Type of value that this measure will be assigned.
        :param description: Description of the measure.
        :param units: Unit of this measure.
        """
        self.label = label
        self.type = type
        self.description = description
        self.units = units

    def enabled(self) -> bool:
        """True if this Measure has been enabled"""

    @property
    def value(self) -> Optional[MeasureType]:
        """
        The value of this measure.

        """
        pass

    @value.setter
    def value(self, value: MeasureType):
        pass

    @property
    def error(self) -> Optional[MeasureError]:
        """
        Mark this measure as having an error.

        This can be useful if the measure can not be calculated for the given data passed to :py:meth:`measure_range`.

        """
        return self.error

    @error.setter
    def error(self, value: Union[str, MeasureError]):
        pass


class MeasureInstance(Generic[MeasureType]):
    def __init__(self, enabled: bool):
        self._enabled = enabled
        self._value = None
        self._error = None

    @property
    def enabled(self) -> bool:
        """True if this Measure has been enabled"""
        return self._enabled

    @property
    def value(self) -> Optional[MeasureType]:
        return self._value

    @value.setter
    def value(self, value: MeasureType):
        if self._value is not None:
            raise RuntimeError('A measure value can only be set once')
        self._value = value

    @property
    def error(self) -> Optional[MeasureError]:
        return self._error

    @error.setter
    def error(self, value: Union[str, MeasureError]):
        if self._error is not None:
            raise RuntimeError('A measure error can only be set once')

        if isinstance(value, str):
            self._error = MeasureError(value)
        elif isinstance(value, MeasureError):
            self._error = value
        else:
            raise TypeError('Expected str or MeasureError')
