def enhanced_dir(arg, categorize=True, show_types=False, checks=False, interfaces_and_types=False, print_width=120,
                 p=False):
    from collections import defaultdict
    if not categorize:
        return_list = []
    passed = defaultdict(lambda: defaultdict(set))
    failed = defaultdict(set)
    passed_ = defaultdict(lambda: defaultdict(set))
    failed_ = defaultdict(lambda: defaultdict(set))
    x = arg

    for method in (set(dir(arg)) | (set(dir(type(x))) - set(dir(x)))):
        try:
            type_ = type(eval(f'x.{method}'))
        except:
            failed[f'{arg}'].add(method)
            continue
        try:
            qualname = eval(f'x.{method}.__qualname__')
            qualname = qualname.split('.')
            passed[f'{arg}'][qualname[0]].add(qualname[1])
            passed_[f'{arg}'][type_].add(qualname[1])
        except:
            failed[f'{arg}'].add(method)
            failed_[f'{arg}'][type_].add(method)
    if categorize:
        return_list = [{'passed': passed}, {'failed': failed}]
    if show_types:
        return_list.extend(({'passed_types': passed_}, {'failed_types': failed_}))
    if interfaces_and_types:
        import collections.abc
        import types
        import typing
        collections_abc = {*()}
        for i in dir(collections.abc):
            try:
                if isinstance(arg, eval(f'collections.abc.{i}')):
                    collections_abc.add(i)
            except:
                pass
        return_list.append({'collections_abc': collections_abc})
        types_ = {*()}
        for i in dir(types):
            try:
                if isinstance(arg, eval(f'types.{i}')):
                    types_.add(i)
            except:
                pass
        return_list.append({'types': types_})
        typing_ = {*()}
        for i in dir(typing):
            try:
                if isinstance(arg, eval(f'typing.{i}')):
                    typing_.add(i)
            except:
                pass
        return_list.append({'typing': typing_})

    if checks:
        checks_ = {}
        try:
            class A(x):
                pass

            checks_['inheritable'] = True
        except:
            checks_['inheritable'] = False

        try:
            a = defaultdict(arg)
            checks_['defaultdict_arg'] = True
        except:
            checks_['defaultdict_arg'] = False

        try:
            d = {arg: 1}
            checks_['dict_key'] = True
        except:
            checks_['dict_key'] = False

        try:
            for i in arg:
                pass
            checks_['iterable'] = True
        except:
            checks_['iterable'] = False
        return_list.append([checks_])

    if p:
        from pprint import pprint
        pprint(return_list, compact=True, width=print_width)
    else:
        return return_list


def two_way(operation, opposite=False, iterators=False, print_width=120, p=False):
    """
    two_way('+')
    two_way('+', opposite=True)
    """
    import warnings
    warnings.filterwarnings("ignore")
    import re, keyword
    from collections import defaultdict, Counter, OrderedDict, namedtuple
    from decimal import Decimal
    from fractions import Fraction
    failed = defaultdict(set)
    succeeded = defaultdict(set)
    invalid = 'StopIteration|StopAsyncIteration|Error|Warning|Exception|Exit|Interrupt|__|ipython|display|execfile' \
              '|dreload|help|license|open|get_ipython|credits|runfile|copyright|breakpoint|input|print'
    bytes_iterator = "(iter(b''))"
    bytearray_iterator = "(iter(bytearray()))"
    dict_keyiterator = "(iter({}.keys()))"
    dict_valueiterator = "(iter({}.values()))"
    dict_itemiterator = "(iter({}.items()))"
    list_iterator = "(iter([]))"
    list_reverseiterator = "(iter(reversed([])))"
    range_iterator = "(iter(range(0)))"
    set_iterator = "(iter(set()))"
    str_iterator = "(iter(''))"
    tuple_iterator = "(iter(()))"
    zip_iterator = "(iter(zip()))"
    line_iterator = "(lambda x: 1).__code__.co_lines"
    positions_iterator = "(lambda x: 1).__code__.co_positions"
    mappingproxy = '(type.__dict__)'
    generator = '((lambda: (yield))())'
    ## views ##
    dict_keys = 'dict().keys'
    dict_values = 'dict().values'
    dict_items = 'dict().items'
    counter = 'Counter'
    ordered_dict = 'OrderedDict'
    default_dict = 'defaultdict'
    named_tuple = 'namedtuple'
    decim = 'Decimal'
    fract = 'Fraction'
    y = [(dict_keys, 13), (dict_values, 14), (dict_items, 15), (mappingproxy, 16),
         (generator, 17), (counter, 18), (ordered_dict, 19), (default_dict, 20), (named_tuple, 21), (decim, 22),
         (fract, 23)]
    if iterators:
        y += [(bytes_iterator, 0), (bytearray_iterator, 1), (dict_keyiterator, 2),
              (dict_valueiterator, 3), (dict_itemiterator, 4), (list_iterator, 5),
              (list_reverseiterator, 6), (range_iterator, 7),
              (set_iterator, 9), (str_iterator, 10), (tuple_iterator, 11), (zip_iterator, 12)]

    for a, i in list(keyword.__builtins__.items()) + y:
        if not re.search(invalid, str(a)):
            for b, j in list(keyword.__builtins__.items()) + y:
                if not re.search(invalid, b):
                    try:
                        x = eval(f'{a}() {operation} {b}()')
                        if opposite:
                            succeeded[f'{b}()'].add(f'{a}()')
                        else:
                            succeeded[f'{a}()'].add(f'{b}()')
                    except:
                        failed[a].add(b)
                    try:
                        x = eval(f'{a}() {operation} {b}')
                        if opposite:
                            succeeded[b].add(f'{a}()')
                        else:
                            succeeded[f'{a}()'].add(b)
                    except:
                        failed[a].add(b)
                    try:
                        x = eval(f'{a} {operation} {b}()')
                        if opposite:
                            succeeded[f'{b}()'].add(a)
                        else:
                            succeeded[a].add(f'{b}()')
                    except:
                        failed[a].add(b)
                    try:
                        x = eval(f'{a} {operation} {b}')
                        if opposite:
                            succeeded[b].add(a)
                        else:
                            succeeded[a].add(b)
                    except:
                        failed[a].add(b)
    if p:
        from pprint import pprint
        pprint([{'succeeded': succeeded}], compact=True, width=print_width)
    else:
        return [{'succeeded': succeeded}]


def operator_check(left_argument, right_argument, show_failed=False, print_width=120, p=False):
    """
    operator_check(1, 2)
    """

    import warnings
    warnings.filterwarnings("ignore")
    failed = set()
    succeeded = set()
    operators = [':', ',', ';', '+', '-', '*', '/', '|', '&', '<', '>', '=',
                 '.', '%', '==', '!=', '<=', '>=', '~', '^', '<<',
                 '>>', '**', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=',
                 '<<=', '>>=', '**=', '//', '//=', '@', '@=', '->', '...',
                 ':=', 'and', 'or', 'in', 'is']

    for operator in operators:
        try:
            x = eval(f'left_argument {operator} right_argument')
            succeeded.add(operator)
        except:
            failed.add(operator)
    returned_dictionary = {'succeeded': succeeded}
    if show_failed:
        returned_dictionary['failed'] = failed
    if p:
        from pprint import pprint
        pprint(returned_dictionary, compact=True, width=print_width)
    else:
        return returned_dictionary


extended_builtins = ['Ellipsis', 'False', 'None', 'NotImplemented', 'True', 'abs', 'all', 'any',
                     'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr',
                     'classmethod', 'compile', 'complex', 'delattr', 'dict',
                     'dir', 'divmod', 'enumerate', 'eval', 'exec',
                     'filter', 'float', 'frozenset', 'getattr', 'globals', 'hasattr',
                     'hash', 'hex', 'id', 'int', 'isinstance', 'issubclass',
                     'iter', 'len', 'list', 'locals', 'map', 'max', 'memoryview', 'min',
                     'next', 'object', 'oct', 'ord', 'pow', 'property', 'range',
                     'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted',
                     'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip',
                     "(iter(b''))", '(iter(bytearray()))', '(iter({}.keys()))',
                     '(iter({}.values()))', '(iter({}.items()))', '(iter([]))',
                     '(iter(reversed([])))', '(iter(range(0)))', '(iter(set()))', "(iter(''))",
                     '(iter(()))', '(iter(zip()))', '(lambda x: 1).__code__.co_lines',
                     '(lambda x: 1).__code__.co_positions', '(type.__dict__)',
                     '((lambda: (yield))())', 'dict().keys', 'dict().values', 'dict().items']

external_checks = {'imports': 'from collections import Counter, namedtuple, defaultdict, OrderedDict;\
                               from types import SimpleNamespace;\
                               from fractions import Fraction;\
                               from decimal import Decimal;',
                   'modules': ['Counter', 'Fraction', 'Decimal', 'defaultdict', 'OrderedDict', 'namedtuple',
                               'SimpleNamespace']}


def argument_inspector(arg, lib=None, no_of_arguments=2, p=False, print_width=120):
    """
    argument_inspector('int')
    argument_inspector('Fraction', lib='fractions')
    """
    from collections import defaultdict
    import warnings
    warnings.filterwarnings("ignore")
    rtrnd_dct = defaultdict(set)
    if lib:
        try:
            exec(f'from {lib} import {arg}')
        except:
            pass
    if no_of_arguments >= 0:
        try:
            x = eval(f'{arg}()')
            rtrnd_dct[0].add(True)
        except:
            pass

    if no_of_arguments >= 1:
        import enhanced_dir
        for i in enhanced_dir.extended_builtins + enhanced_dir.inspection_arguments:
            try:
                x = eval(f'{arg}({i})')
                rtrnd_dct[1].add(i)
            except:
                pass

            try:
                x = eval(f'{arg}({i}())')
                rtrnd_dct[1].add(f'{i}()')
            except:
                pass

    if no_of_arguments >= 2:
        for i in enhanced_dir.extended_builtins + enhanced_dir.inspection_arguments:
            for j in enhanced_dir.extended_builtins + enhanced_dir.inspection_arguments:
                try:
                    x = eval(f'{arg}({i}, {j})')
                    rtrnd_dct[2].add((i, j))
                except:
                    pass

                try:
                    x = eval(f'{arg}({i}(), {j}())')
                    rtrnd_dct[2].add((f'{i}()', f'{j}()'))
                except:
                    pass

                try:
                    x = eval(f'{arg}({i}(), {j})')
                    rtrnd_dct[2].add((f'{i}()', j))
                except:
                    pass

                try:
                    x = eval(f'{arg}({i}, {j}())')
                    rtrnd_dct[2].add((i, f'{j}()'))
                except:
                    pass

                # try:
                #   x = eval(f'{arg}({i!r}, {j!r})')
                #   rtrnd_dct[2].add((i, j))
                # except:
                #   pass

                # try:
                #   x = eval(f'{arg}({i!r}(), {j!r}())')
                #   rtrnd_dct[2].add((f'{i}()', f'{j}()'))
                # except:
                #   pass

                # try:
                #   x = eval(f'{arg}({i!r}(), {j!r})')
                #   rtrnd_dct[2].add((f'{i}()', j))
                # except:
                #   pass

                # try:
                #   x = eval(f'{arg}({i!r}, {j!r}())')
                #   rtrnd_dct[2].add((i, f'{j}()'))
                # except:
                #   pass

    if no_of_arguments >= 3:
        for i in enhanced_dir.extended_builtins + enhanced_dir.inspection_arguments:
            for j in enhanced_dir.extended_builtins + enhanced_dir.inspection_arguments:
                for k in enhanced_dir.extended_builtins + enhanced_dir.inspection_arguments:
                    try:
                        x = eval(f'{arg}({i}, {j}, {k})')
                        rtrnd_dct[3].add((i, j, k))
                    except:
                        pass

                    try:
                        x = eval(f'{arg}({i}, {j}, {k}())')
                        rtrnd_dct[3].add((i, j, f'{k}()'))
                    except:
                        pass

                    try:
                        x = eval(f'{arg}({i}, {j}(), {k})')
                        rtrnd_dct[3].add((i, f'{j}()', k))
                    except:
                        pass

                    try:
                        x = eval(f'{arg}({i}, {j}(), {k}())')
                        rtrnd_dct[3].add((i, f'{j}()', f'{k}()'))
                    except:
                        pass

                    try:
                        x = eval(f'{arg}({i}(), {j}, {k})')
                        rtrnd_dct[3].add((f'{i}()', j, k))
                    except:
                        pass

                    try:
                        x = eval(f'{arg}({i}(), {j}, {k}())')
                        rtrnd_dct[3].add((f'{i}()', j, f'{k}()'))
                    except:
                        pass

                    try:
                        x = eval(f'{arg}({i}(), {j}(), {k})')
                        rtrnd_dct[3].add((f'{i}()', f'{j}()', k))
                    except:
                        pass

                    try:
                        x = eval(f'{arg}({i}(), {j}(), {k}())')
                        rtrnd_dct[3].add((f'{i}()', f'{j}()', f'{k}()'))
                    except:
                        pass

    if p:
        from pprint import pprint
        pprint(rtrnd_dct, compact=True, width=print_width)
    else:
        return rtrnd_dct


inspection_arguments = [1, 0, 1.2, 'abc', '1', '0', '1.2', '1/2', {1, 2, 3}, {'a': 1, 'b': 2}, (1, 2, 3),
                        [(2, 5), (3, 2)], [1, 2, 3], {'a', 'b', 'c'}, ('a', 'b', 'c'), ['a', 'b', 'c'], 1 + 2j,
                        b'2', b'a', bytearray(1), frozenset({1, 2, 3}), frozenset({'a', 'b', 'c'}),
                        (lambda x: x < 5)]
