# -*- coding: utf-8 -*-
#
# Licensed under the terms of the Qwt License
# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
# (see LICENSE file for more details)

"""
QwtInterval
-----------

.. autoclass:: QwtInterval
   :members:
"""


class QwtInterval(object):
    """
    A class representing an interval

    The interval is represented by 2 doubles, the lower and the upper limit.

    .. py:class:: QwtInterval(minValue=0., maxValue=-1., borderFlags=None)

        Build an interval with from min/max values

        :param float minValue: Minimum value
        :param float maxValue: Maximum value
        :param int borderFlags: Include/Exclude borders
    """

    # enum BorderFlag
    IncludeBorders = 0x00
    ExcludeMinimum = 0x01
    ExcludeMaximum = 0x02
    ExcludeBorders = ExcludeMinimum | ExcludeMaximum

    def __init__(self, minValue=0.0, maxValue=-1.0, borderFlags=None):
        assert not isinstance(minValue, QwtInterval)
        assert not isinstance(maxValue, QwtInterval)
        self.__minValue = None
        self.__maxValue = None
        self.__borderFlags = None
        self.setInterval(minValue, maxValue, borderFlags)

    def setInterval(self, minValue, maxValue, borderFlags=None):
        """
        Assign the limits of the interval

        :param float minValue: Minimum value
        :param float maxValue: Maximum value
        :param int borderFlags: Include/Exclude borders
        """
        self.__minValue = float(minValue)  # avoid overflows with NumPy scalars
        self.__maxValue = float(maxValue)  # avoid overflows with NumPy scalars
        if borderFlags is None:
            self.__borderFlags = self.IncludeBorders
        else:
            self.__borderFlags = borderFlags

    def setBorderFlags(self, borderFlags):
        """
        Change the border flags

        :param int borderFlags: Include/Exclude borders

        .. seealso::

            :py:meth:`borderFlags()`
        """
        self.__borderFlags = borderFlags

    def borderFlags(self):
        """
        :return: Border flags

        .. seealso::

            :py:meth:`setBorderFlags()`
        """
        return self.__borderFlags

    def setMinValue(self, minValue):
        """
        Assign the lower limit of the interval

        :param float minValue: Minimum value
        """
        self.__minValue = float(minValue)  # avoid overflows with NumPy scalars

    def setMaxValue(self, maxValue):
        """
        Assign the upper limit of the interval

        :param float maxValue: Maximum value
        """
        self.__maxValue = float(maxValue)  # avoid overflows with NumPy scalars

    def minValue(self):
        """
        :return: Lower limit of the interval
        """
        return self.__minValue

    def maxValue(self):
        """
        :return: Upper limit of the interval
        """
        return self.__maxValue

    def isValid(self):
        """
        A interval is valid when minValue() <= maxValue().
        In case of `QwtInterval.ExcludeBorders` it is true
        when minValue() < maxValue()

        :return: True, when the interval is valid
        """
        if (self.__borderFlags & self.ExcludeBorders) == 0:
            return self.__minValue <= self.__maxValue
        else:
            return self.__minValue < self.__maxValue

    def width(self):
        """
        The width of invalid intervals is 0.0, otherwise the result is
        maxValue() - minValue().

        :return: the width of an interval
        """
        if self.isValid():
            return self.__maxValue - self.__minValue
        else:
            return 0.0

    def __and__(self, other):
        return self.intersect(other)

    def __iand__(self, other):
        self = self & other
        return self

    def __or__(self, other):
        if isinstance(other, QwtInterval):
            return self.unite(other)
        else:
            return self.extend(other)

    def __ior__(self, other):
        self = self | other
        return self

    def __eq__(self, other):
        return (
            self.__minValue == other.__minValue
            and self.__maxValue == other.__maxValue
            and self.__borderFlags == other.__borderFlags
        )

    def __ne__(self, other):
        return not self.__eq__(other)

    def isNull(self):
        """
        :return: true, if isValid() && (minValue() >= maxValue())
        """
        return self.isValid() and self.__minValue >= self.__maxValue

    def invalidate(self):
        """
        The limits are set to interval [0.0, -1.0]

        .. seealso::

            :py:meth:`isValid()`
        """
        self.__minValue = 0.0
        self.__maxValue = -1.0

    def normalized(self):
        """
        Normalize the limits of the interval

        If maxValue() < minValue() the limits will be inverted.

        :return: Normalized interval

        .. seealso::

            :py:meth:`isValid()`, :py:meth:`inverted()`
        """
        if self.__minValue > self.__maxValue:
            return self.inverted()
        elif (
            self.__minValue == self.__maxValue
            and self.__borderFlags == self.ExcludeMinimum
        ):
            return self.inverted()
        else:
            return self

    def inverted(self):
        """
        Invert the limits of the interval

        :return: Inverted interval

        .. seealso::

            :py:meth:`normalized()`
        """
        borderFlags = self.IncludeBorders
        if self.__borderFlags & self.ExcludeMinimum:
            borderFlags |= self.ExcludeMaximum
        if self.__borderFlags & self.ExcludeMaximum:
            borderFlags |= self.ExcludeMinimum
        return QwtInterval(self.__maxValue, self.__minValue, borderFlags)

    def contains(self, value):
        """
        Test if a value is inside an interval

        :param float value: Value
        :return: true, if value >= minValue() && value <= maxValue()
        """
        if not self.isValid():
            return False
        elif value < self.__minValue or value > self.__maxValue:
            return False
        elif value == self.__minValue and self.__borderFlags & self.ExcludeMinimum:
            return False
        elif value == self.__maxValue and self.__borderFlags & self.ExcludeMaximum:
            return False
        else:
            return True

    def unite(self, other):
        """
        Unite two intervals

        :param qwt.interval.QwtInterval other: other interval to united with
        :return: united interval
        """
        if not self.isValid():
            if not other.isValid():
                return QwtInterval()
            else:
                return other
        elif not other.isValid():
            return self

        united = QwtInterval()
        flags = self.IncludeBorders

        # minimum
        if self.__minValue < other.minValue():
            united.setMinValue(self.__minValue)
            flags &= self.__borderFlags & self.ExcludeMinimum
        elif other.minValue() < self.__minValue:
            united.setMinValue(other.minValue())
            flags &= other.borderFlags() & self.ExcludeMinimum
        else:
            united.setMinValue(self.__minValue)
            flags &= (self.__borderFlags & other.borderFlags()) & self.ExcludeMinimum

        # maximum
        if self.__maxValue > other.maxValue():
            united.setMaxValue(self.__maxValue)
            flags &= self.__borderFlags & self.ExcludeMaximum
        elif other.maxValue() > self.__maxValue:
            united.setMaxValue(other.maxValue())
            flags &= other.borderFlags() & self.ExcludeMaximum
        else:
            united.setMaxValue(self.__maxValue)
            flags &= self.__borderFlags & other.borderFlags() & self.ExcludeMaximum

        united.setBorderFlags(flags)
        return united

    def intersect(self, other):
        """
        Intersect two intervals

        :param qwt.interval.QwtInterval other: other interval to intersect with
        :return: intersected interval
        """
        if not other.isValid() or not self.isValid():
            return QwtInterval()

        i1 = self
        i2 = other

        if i1.minValue() > i2.minValue():
            i1, i2 = i2, i1
        elif i1.minValue() == i2.minValue():
            if i1.borderFlags() & self.ExcludeMinimum:
                i1, i2 = i2, i1

        if i1.maxValue() < i2.maxValue():
            return QwtInterval()

        if i1.maxValue() == i2.minValue():
            if (
                i1.borderFlags() & self.ExcludeMaximum
                or i2.borderFlags() & self.ExcludeMinimum
            ):
                return QwtInterval()

        intersected = QwtInterval()
        flags = self.IncludeBorders

        intersected.setMinValue(i2.minValue())
        flags |= i2.borderFlags() & self.ExcludeMinimum

        if i1.maxValue() < i2.maxValue():
            intersected.setMaxValue(i1.maxValue())
            flags |= i1.borderFlags() & self.ExcludeMaximum
        elif i2.maxValue() < i1.maxValue():
            intersected.setMaxValue(i2.maxValue())
            flags |= i2.borderFlags() & self.ExcludeMaximum
        else:  # i1.maxValue() == i2.maxValue()
            intersected.setMaxValue(i1.maxValue())
            flags |= i1.borderFlags() & i2.borderFlags() & self.ExcludeMaximum

        intersected.setBorderFlags(flags)
        return intersected

    def intersects(self, other):
        """
        Test if two intervals overlap

        :param qwt.interval.QwtInterval other: other interval
        :return: True, when the intervals are intersecting
        """
        if not other.isValid() or not self.isValid():
            return False

        i1 = self
        i2 = other

        if i1.minValue() > i2.minValue():
            i1, i2 = i2, i1
        elif i1.minValue() == i2.minValue() and i1.borderFlags() & self.ExcludeMinimum:
            i1, i2 = i2, i1

        if i1.maxValue() > i2.minValue():
            return True
        elif i1.maxValue() == i2.minValue():
            return (
                i1.borderFlags() & self.ExcludeMaximum
                and i2.borderFlags() & self.ExcludeMinimum
            )
        return False

    def symmetrize(self, value):
        """
        Adjust the limit that is closer to value, so that value becomes
        the center of the interval.

        :param float value: Center
        :return: Interval with value as center
        """
        if not self.isValid():
            return self
        delta = max([abs(value - self.__maxValue), abs(value - self.__minValue)])
        return QwtInterval(value - delta, value + delta)

    def limited(self, lowerBound, upperBound):
        """
        Limit the interval, keeping the border modes

        :param float lowerBound: Lower limit
        :param float upperBound: Upper limit
        :return: Limited interval
        """
        if not self.isValid() or lowerBound > upperBound:
            return QwtInterval()
        minValue = max([self.__minValue, lowerBound])
        minValue = min([minValue, upperBound])
        maxValue = max([self.__maxValue, lowerBound])
        maxValue = min([maxValue, upperBound])
        return QwtInterval(minValue, maxValue, self.__borderFlags)

    def extend(self, value):
        """
        Extend the interval

        If value is below minValue(), value becomes the lower limit.
        If value is above maxValue(), value becomes the upper limit.

        extend() has no effect for invalid intervals

        :param float value: Value
        :return: extended interval
        """
        if not self.isValid():
            return self
        return QwtInterval(min([value, self.__minValue]), max([value, self.__maxValue]))

    def extend_fraction(self, value):
        """
        Extend the interval by a fraction of its width

        :param float value: Fraction
        :return: extended interval
        """
        if not self.isValid():
            return self
        return QwtInterval(
            self.__minValue - value * self.width(),
            self.__maxValue + value * self.width(),
            self.__borderFlags,
        )
