"""
This module should be run to recreate the files that we generate automatically
(i.e.: modules that shouldn't be traced and cython .pyx)
"""

from __future__ import print_function

import os
import struct
import re


def is_python_64bit():
    return struct.calcsize("P") == 8


root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))


def get_cython_contents(filename):
    if filename.endswith(".pyc"):
        filename = filename[:-1]

    state = "regular"

    replacements = []

    new_contents = []
    with open(filename, "r") as stream:
        for line in stream:
            strip = line.strip()
            if state == "regular":
                if strip == "# IFDEF CYTHON":
                    state = "cython"

                    new_contents.append(
                        "%s -- DONT EDIT THIS FILE (it is automatically generated)\n" % line.replace("\n", "").replace("\r", "")
                    )
                    continue

                new_contents.append(line)

            elif state == "cython":
                if strip == "# ELSE":
                    state = "nocython"
                    new_contents.append(line)
                    continue

                elif strip == "# ENDIF":
                    state = "regular"
                    new_contents.append(line)
                    continue

                if strip == "#":
                    continue

                assert strip.startswith("# "), 'Line inside # IFDEF CYTHON must start with "# ". Found: %s' % (strip,)
                strip = strip.replace("# ", "", 1).strip()

                if strip.startswith("cython_inline_constant:"):
                    strip = strip.replace("cython_inline_constant:", "")
                    word_to_replace, replacement = strip.split("=")
                    replacements.append((word_to_replace.strip(), replacement.strip()))
                    continue

                line = line.replace("# ", "", 1)
                new_contents.append(line)

            elif state == "nocython":
                if strip == "# ENDIF":
                    state = "regular"
                    new_contents.append(line)
                    continue
                new_contents.append("# %s" % line)

    assert state == "regular", "Error: # IFDEF CYTHON found without # ENDIF"

    ret = "".join(new_contents)

    for word_to_replace, replacement in replacements:
        ret = re.sub(r"\b%s\b" % (word_to_replace,), replacement, ret)

    return ret


def _generate_cython_from_files(target, modules):
    contents = [
        """from __future__ import print_function

# Important: Autogenerated file.

# DO NOT edit manually!
# DO NOT edit manually!
"""
    ]

    found = []
    for mod in modules:
        found.append(mod.__file__)
        contents.append(get_cython_contents(mod.__file__))

    print("Generating cython from: %s" % (found,))

    with open(target, "w") as stream:
        stream.write("".join(contents))


def generate_dont_trace_files():
    template = """# Important: Autogenerated file.

# fmt: off
# DO NOT edit manually!
# DO NOT edit manually!

LIB_FILE = 1
PYDEV_FILE = 2

DONT_TRACE_DIRS = {
%(pydev_dirs)s
}

LIB_FILES_IN_DONT_TRACE_DIRS = {
%(pydev_lib_files_in_dont_trace_dirs)s
}

DONT_TRACE = {
    # commonly used things from the stdlib that we don't want to trace
    'Queue.py':LIB_FILE,
    'queue.py':LIB_FILE,
    'socket.py':LIB_FILE,
    'weakref.py':LIB_FILE,
    '_weakrefset.py':LIB_FILE,
    'linecache.py':LIB_FILE,
    'threading.py':LIB_FILE,
    'dis.py':LIB_FILE,

    # things from pydev that we don't want to trace
%(pydev_files)s
}

# if we try to trace io.py it seems it can get halted (see http://bugs.python.org/issue4716)
DONT_TRACE['io.py'] = LIB_FILE

# Don't trace common encodings too
DONT_TRACE['cp1252.py'] = LIB_FILE
DONT_TRACE['utf_8.py'] = LIB_FILE
DONT_TRACE['codecs.py'] = LIB_FILE

# fmt: on
"""

    pydev_files = []
    pydev_dirs = []
    pydev_lib_files_in_dont_trace_dirs = []

    exclude_dirs = [
        ".git",
        ".settings",
        "build",
        "build_tools",
        "dist",
        "pydevd.egg-info",
        "pydevd_attach_to_process",
        "pydev_sitecustomize",
        "stubs",
        "tests",
        "tests_mainloop",
        "tests_python",
        "tests_runfiles",
        "test_pydevd_reload",
        "third_party",
        "__pycache__",
        "vendored",
        ".mypy_cache",
        "pydevd.egg-info",
    ]

    for root, dirs, files in os.walk(root_dir):
        for d in dirs:
            if d == "pydev_ipython":
                pydev_dirs.append("    '%s': LIB_FILE," % (d,))
                continue

            if "pydev" in d and d != "pydevd.egg-info":
                # print(os.path.join(root, d))
                pydev_dirs.append("    '%s': PYDEV_FILE," % (d,))

        for d in exclude_dirs:
            try:
                dirs.remove(d)
            except:
                pass

        if os.path.basename(root) == "pydev_ipython":
            for f in files:
                if f.endswith(".py"):
                    pydev_lib_files_in_dont_trace_dirs.append("    '%s'," % (f,))
            continue

        for f in files:
            if f.endswith(".py"):
                if f not in (
                    "__init__.py",
                    "runfiles.py",
                    "pydev_coverage.py",
                    "pydev_pysrc.py",
                    "setup.py",
                    "setup_pydevd_cython.py",
                    "interpreterInfo.py",
                    "conftest.py",
                ):
                    pydev_files.append("    '%s': PYDEV_FILE," % (f,))

    contents = template % (
        dict(
            pydev_files="\n".join(sorted(set(pydev_files))),
            pydev_dirs="\n".join(sorted(set(pydev_dirs))),
            pydev_lib_files_in_dont_trace_dirs="\n".join(sorted(set(pydev_lib_files_in_dont_trace_dirs))),
        )
    )
    assert "pydevd.py" in contents
    assert "pydevd_dont_trace.py" in contents
    with open(os.path.join(root_dir, "_pydevd_bundle", "pydevd_dont_trace_files.py"), "w") as stream:
        stream.write(contents)


def remove_if_exists(f):
    try:
        if os.path.exists(f):
            os.remove(f)
    except:
        import traceback

        traceback.print_exc()


def generate_cython_module():
    _generate_cython_module()
    _generate_sys_monitoring_cython_module()


def _generate_cython_module():
    print("Removing pydevd_cython.pyx")
    remove_if_exists(os.path.join(root_dir, "_pydevd_bundle", "pydevd_cython.pyx"))

    target = os.path.join(root_dir, "_pydevd_bundle", "pydevd_cython.pyx")
    curr = os.environ.get("PYDEVD_USE_CYTHON")
    try:
        os.environ["PYDEVD_USE_CYTHON"] = "NO"

        from _pydevd_bundle import pydevd_additional_thread_info_regular
        from _pydevd_bundle import pydevd_frame, pydevd_trace_dispatch_regular

        _generate_cython_from_files(target, [pydevd_additional_thread_info_regular, pydevd_frame, pydevd_trace_dispatch_regular])
    finally:
        if curr is None:
            del os.environ["PYDEVD_USE_CYTHON"]
        else:
            os.environ["PYDEVD_USE_CYTHON"] = curr


def _generate_sys_monitoring_cython_module():
    print("Removing _pydevd_sys_monitoring_cython.pyx")
    remove_if_exists(os.path.join(root_dir, "_pydevd_sys_monitoring", "_pydevd_sys_monitoring_cython.pyx"))

    target = os.path.join(root_dir, "_pydevd_sys_monitoring", "_pydevd_sys_monitoring_cython.pyx")
    curr = os.environ.get("PYDEVD_USE_CYTHON")
    try:
        os.environ["PYDEVD_USE_CYTHON"] = "NO"

        from _pydevd_sys_monitoring import _pydevd_sys_monitoring

        _generate_cython_from_files(target, [_pydevd_sys_monitoring])
    finally:
        if curr is None:
            del os.environ["PYDEVD_USE_CYTHON"]
        else:
            os.environ["PYDEVD_USE_CYTHON"] = curr


if __name__ == "__main__":
    generate_dont_trace_files()
    generate_cython_module()
