# It's recommended to run this with `python3 -I not_impl_gen.py`, to make sure
# that nothing in your global Python environment interferes with what's being
# extracted here.

import pkgutil
import os
import sys
import warnings

sys.path = list(
    filter(
        lambda path: "site-packages" not in path and "dist-packages" not in path,
        sys.path,
    )
)


def attr_is_not_inherited(type_, attr):
    """
    returns True if type_'s attr is not inherited from any of its base classes
    """

    bases = type_.__mro__[1:]

    return getattr(type_, attr) not in (getattr(base, attr, None) for base in bases)


def gen_methods(header, footer, output):
    types = [
        bool,
        bytearray,
        bytes,
        complex,
        dict,
        enumerate,
        filter,
        float,
        frozenset,
        int,
        list,
        map,
        memoryview,
        range,
        set,
        slice,
        str,
        super,
        tuple,
        object,
        zip,

        classmethod,
        staticmethod,
        property,

        Exception,
        BaseException,
    ]
    objects = [(t.__name__, t.__name__) for t in types]
    objects.extend([
        ('NoneType', 'type(None)'),
    ])

    iters = [
        ('bytearray_iterator', 'type(bytearray().__iter__())'),
        ('bytes_iterator', 'type(bytes().__iter__())'),
        ('dict_keyiterator', 'type(dict().__iter__())'),
        ('dict_valueiterator', 'type(dict().values().__iter__())'),
        ('dict_itemiterator', 'type(dict().items().__iter__())'),
        ('dict_values', 'type(dict().values())'),
        ('dict_items', 'type(dict().items())'),
        ('set_iterator', 'type(set().__iter__())'),
        ('list_iterator', 'type(list().__iter__())'),
        ('range_iterator', 'type(range(0).__iter__())'),
        ('str_iterator', 'type(str().__iter__())'),
        ('tuple_iterator', 'type(tuple().__iter__())'),
    ]

    output.write(header.read())
    output.write("expected_methods = {\n")

    for name, typ_code in objects + iters:
        typ = eval(typ_code)
        output.write(f" '{name}': ({typ_code}, [\n")
        output.write(
            "\n".join(
                f"    {attr!r},"
                for attr in dir(typ)
                if attr_is_not_inherited(typ, attr)
            )
        )
        output.write("\n ])," + ("\n" if objects[-1] == typ else "\n\n"))

    output.write("}\n\n")
    output.write(footer.read())

def get_module_methods(name):
    with warnings.catch_warnings():
        # ignore warnings caused by importing deprecated modules
        warnings.filterwarnings("ignore", category=DeprecationWarning)
        try:
            return set(dir(__import__(name))) if name not in ("this", "antigravity") else None
        except ModuleNotFoundError:
            return None
        except Exception as e:
            print("!!! {} skipped because {}: {}".format(name, type(e).__name__, str(e)))

def gen_modules(header, footer, output):
    output.write(header.read())

    modules = dict(
        map(
            lambda mod: (
                mod.name,
                # check name b/c modules listed have side effects on import,
                # e.g. printing something or opening a webpage
                get_module_methods(mod.name)
            ),
            pkgutil.iter_modules(),
        )
    )

    print(
        f"""
cpymods = {modules!r}
libdir = {os.path.abspath("../Lib/").encode('utf8')!r}
""",
        file=output,
    )

    output.write(footer.read())


gen_funcs = {"methods": gen_methods, "modules": gen_modules}


for name, gen_func in gen_funcs.items():
    gen_func(
        header=open(f"generator/not_impl_{name}_header.txt"),
        footer=open(f"generator/not_impl_{name}_footer.txt"),
        output=open(f"snippets/whats_left_{name}.py", "w"),
    )
