# Copyright (c) 2021, Riverbank Computing Limited
# All rights reserved.
#
# This copy of SIP is licensed for use under the terms of the SIP License
# Agreement.  See the file LICENSE for more details.
#
# This copy of SIP may also used under the terms of the GNU General Public
# License v2 or v3 as published by the Free Software Foundation which can be
# found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ('AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.


from .specification import PySlot


# A 4-tuple of the name, the type, True if the slot requires %MethodCode and
# the number of arguments required (-1 if any number is valid).
_SLOT_DEFINITIONS = (
    ('__str__', PySlot.STR, True, 0),
    ('__int__', PySlot.INT, False, 0),
    ('__float__', PySlot.FLOAT, False, 0),
    ('__len__', PySlot.LEN, True, 0),
    ('__contains__', PySlot.CONTAINS, True, 1),
    ('__add__', PySlot.ADD, False, 1),
    ('__sub__', PySlot.SUB, False, 1),
    ('__mul__', PySlot.MUL, False, 1),
    ('__mod__', PySlot.MOD, False, 1),
    ('__floordiv__', PySlot.FLOORDIV, True, 1),
    ('__truediv__', PySlot.TRUEDIV, False, 1),
    ('__and__', PySlot.AND, False, 1),
    ('__or__', PySlot.OR, False, 1),
    ('__xor__', PySlot.XOR, False, 1),
    ('__lshift__', PySlot.LSHIFT, False, 1),
    ('__rshift__', PySlot.RSHIFT, False, 1),
    ('__iadd__', PySlot.IADD, False, 1),
    ('__isub__', PySlot.ISUB, False, 1),
    ('__imul__', PySlot.IMUL, False, 1),
    ('__imod__', PySlot.IMOD, False, 1),
    ('__ifloordiv__', PySlot.IFLOORDIV, True, 1),
    ('__itruediv__', PySlot.ITRUEDIV, False, 1),
    ('__iand__', PySlot.IAND, False, 1),
    ('__ior__', PySlot.IOR, False, 1),
    ('__ixor__', PySlot.IXOR, False, 1),
    ('__ilshift__', PySlot.ILSHIFT, False, 1),
    ('__irshift__', PySlot.IRSHIFT, False, 1),
    ('__invert__', PySlot.INVERT, False, 0),
    ('__call__', PySlot.CALL, False, -1),
    ('__getitem__', PySlot.GETITEM, False, 1),
    ('__setitem__', PySlot.SETITEM, True, 2),
    ('__delitem__', PySlot.DELITEM, True, 1),
    ('__lt__', PySlot.LT, False, 1),
    ('__le__', PySlot.LE, False, 1),
    ('__eq__', PySlot.EQ, False, 1),
    ('__ne__', PySlot.NE, False, 1),
    ('__gt__', PySlot.GT, False, 1),
    ('__ge__', PySlot.GE, False, 1),
    ('__bool__', PySlot.BOOL, True, 0),
    ('__neg__', PySlot.NEG, False, 0),
    ('__pos__', PySlot.POS, False, 0),
    ('__abs__', PySlot.ABS, True, 0),
    ('__repr__', PySlot.REPR, True, 0),
    ('__hash__', PySlot.HASH, True, 0),
    ('__index__', PySlot.INDEX, True, 0),
    ('__iter__', PySlot.ITER, True, 0),
    ('__next__', PySlot.NEXT, True, 0),
    ('__setattr__', PySlot.SETATTR, True, 2),
    ('__delattr__', PySlot.DELATTR, True, 1),
    ('__matmul__', PySlot.MATMUL, False, 1),
    ('__imatmul__', PySlot.IMATMUL, False, 1),
    ('__await__', PySlot.AWAIT, True, 0),
    ('__aiter__', PySlot.AITER, True, 0),
    ('__anext__', PySlot.ANEXT, True, 0),
)


# The mapping of slot type to slot name.
slot_type_name_map = {t: n for n, t, f, a in _SLOT_DEFINITIONS}


# The mapping of slot name to a 3-tuple of type, %MethodCode flag and argument
# count.
slot_name_detail_map = {n: (t, f, a) for n, t, f, a in _SLOT_DEFINITIONS}


def invalid_global_slot(slot):
    """ Return True if the slot cannot be specified as a global (ie. module
    level) slot.
    """

    if slot in (PySlot.NEG, PySlot.POS):
        return False

    if is_number_slot(slot):
        return False

    if is_inplace_number_slot(slot):
        return False

    if is_rich_compare_slot(slot):
        return False

    return True


def is_inplace_number_slot(slot):
    """ Return True if the slot is an inplace binary numeric slot. """

    return slot in (PySlot.IADD, PySlot.ISUB, PySlot.IMUL, PySlot.IMOD,
            PySlot.IFLOORDIV, PySlot.ITRUEDIV, PySlot.IAND, PySlot.IOR,
            PySlot.IXOR, PySlot.ILSHIFT, PySlot.IRSHIFT, PySlot.IMATMUL)


def is_number_slot(slot):
    """ Return True if the slot is a binary numeric slot. """

    return slot in (PySlot.ADD, PySlot.SUB, PySlot.MUL, PySlot.MOD,
            PySlot.FLOORDIV, PySlot.TRUEDIV, PySlot.AND, PySlot.OR, PySlot.XOR,
            PySlot.LSHIFT, PySlot.RSHIFT, PySlot.MATMUL)


def is_rich_compare_slot(slot):
    """ Return True if the slot is a rich comparision slot. """

    return slot in (PySlot.LT, PySlot.LE, PySlot.EQ, PySlot.NE, PySlot.GT,
            PySlot.GE)
