# -*- encoding:utf-8 -*-
# ==============================================
# Author: Jeza Chen
# Time: 2023/8/18 14:24
# Description: 
# ==============================================
'''
This module holds the constants used for specifying the states of the debugger.
'''
from __future__ import nested_scopes

import logging
import platform
import sys  # Note: the sys import must be here anyways (others depend on it)

STATE_RUN = 1
STATE_SUSPEND = 2

PYTHON_SUSPEND = 1
DJANGO_SUSPEND = 2
JINJA2_SUSPEND = 3
JUPYTER_SUSPEND = 4

GET_FRAME_NORMAL_GROUP = 0
GET_FRAME_SPECIAL_GROUP = 1
GET_FRAME_RETURN_GROUP = 2


class DebugInfoHolder:
    # we have to put it here because it can be set through the command line (so, the
    # already imported references would not have it).
    DEBUG_RECORD_SOCKET_READS = False
    LOG_TO_FILE_LEVEL = logging.INFO
    LOG_TO_CONSOLE_LEVEL = logging.WARNING


IS_CPYTHON = platform.python_implementation() == 'CPython'

# Used to determine the maximum size of each variable passed to eclipse -- having a big value here may make
# the communication slower -- as the variables are being gathered lazily in the latest version of eclipse,
# this value was raised from 200 to 1000.
MAXIMUM_VARIABLE_REPRESENTATION_SIZE = 1000
# Prefix for saving functions return values in locals
RETURN_VALUES_DICT = '__pydevd_ret_val_dict'

original_excepthook = sys.__excepthook__


def dummy_excepthook(exctype, value, traceback):
    return None


import os

# Constant detects when running on Jython/windows properly later on.
IS_WINDOWS = sys.platform == 'win32'

IS_64BIT_PROCESS = sys.maxsize > (2 ** 32)

IS_LINUX = sys.platform.startswith('linux')
IS_MACOS = sys.platform == 'darwin'

IS_PYTHON_STACKLESS = "stackless" in sys.version.lower()
CYTHON_SUPPORTED = False

NUMPY_NUMERIC_TYPES = "biufc"
NUMPY_FLOATING_POINT_TYPES = "fc"
# b boolean
# i signed integer
# u unsigned integer
# f floating-point
# c complex floating-point

try:
    import platform
    python_implementation = platform.python_implementation()
except:
    pass
else:
    if python_implementation == 'CPython' and not IS_PYTHON_STACKLESS:
        # Only available for CPython!
        if (
            (sys.version_info[0] == 2 and sys.version_info[1] >= 7)
            or (sys.version_info[0] == 3 and sys.version_info[1] >= 5)
            or (sys.version_info[0] > 3)
        ):
            # Supported in 2.7 or 3.5 onwards (32 or 64)
            CYTHON_SUPPORTED = True

IS_PYCHARM_ATTACH = os.getenv('PYCHARM_ATTACH') == 'True'

#=======================================================================================================================
# Python 3?
#=======================================================================================================================
IS_PY3K = False
IS_PY34_OR_GREATER = False
IS_PY36_OR_GREATER = False
IS_PY37_OR_GREATER = False
IS_PY36_OR_LESSER = False
IS_PY38_OR_GREATER = False
IS_PY38 = False
IS_PY39_OR_GREATER = False
IS_PY311 = False
IS_PY311_OR_GREATER = False
IS_PY2 = True
IS_PY27 = False
IS_PY24 = False
try:
    if sys.version_info[0] >= 3:
        IS_PY3K = True
        IS_PY2 = False
        IS_PY34_OR_GREATER = sys.version_info >= (3, 4)
        IS_PY36_OR_GREATER = sys.version_info >= (3, 6)
        IS_PY37_OR_GREATER = sys.version_info >= (3, 7)
        IS_PY36_OR_LESSER = sys.version_info[:2] <= (3, 6)
        IS_PY38 = sys.version_info[0] == 3 and sys.version_info[1] == 8
        IS_PY38_OR_GREATER = sys.version_info >= (3, 8)
        IS_PY39_OR_GREATER = sys.version_info >= (3, 9)
        IS_PY311 = sys.version_info[0] == 3 and sys.version_info[1] == 11
        IS_PY311_OR_GREATER = sys.version_info >= (3, 11)
    elif sys.version_info[0] == 2 and sys.version_info[1] == 7:
        IS_PY27 = True
    elif sys.version_info[0] == 2 and sys.version_info[1] == 4:
        IS_PY24 = True
except AttributeError:
    pass  # Not all versions have sys.version_info

try:
    SUPPORT_GEVENT = os.getenv('GEVENT_SUPPORT', 'False') == 'True'
except:
    # Jython 2.1 doesn't accept that construct
    SUPPORT_GEVENT = False

try:
    DROP_INTO_DEBUGGER_ON_FAILED_TESTS = os.environ.get('DROP_INTO_DEBUGGER_ON_FAILED_TESTS', 'False') == 'True'
except:
    DROP_INTO_DEBUGGER_ON_FAILED_TESTS = False

# At the moment gevent supports Python >= 2.6 and Python >= 3.3
USE_LIB_COPY = SUPPORT_GEVENT and \
               ((not IS_PY3K and sys.version_info[1] >= 6) or
                (IS_PY3K and sys.version_info[1] >= 3))


class ValuesPolicy:
    SYNC = 0
    ASYNC = 1
    ON_DEMAND = 2


LOAD_VALUES_POLICY = ValuesPolicy.SYNC
if os.getenv('PYDEVD_LOAD_VALUES_ASYNC', 'False') == 'True':
    LOAD_VALUES_POLICY = ValuesPolicy.ASYNC
if os.getenv('PYDEVD_LOAD_VALUES_ON_DEMAND', 'False') == 'True':
    LOAD_VALUES_POLICY = ValuesPolicy.ON_DEMAND
DEFAULT_VALUES_DICT = {ValuesPolicy.ASYNC: "__pydevd_value_async", ValuesPolicy.ON_DEMAND: "__pydevd_value_on_demand"}

INTERACTIVE_MODE_AVAILABLE = sys.platform in ('darwin', 'win32') or os.getenv('DISPLAY') is not None
IS_PYCHARM = True
ASYNC_EVAL_TIMEOUT_SEC = 60
NEXT_VALUE_SEPARATOR = "__pydev_val__"
BUILTINS_MODULE_NAME = '__builtin__' if IS_PY2 else 'builtins'
SHOW_DEBUG_INFO_ENV = os.getenv('PYCHARM_DEBUG') == 'True' or os.getenv('PQI_DEBUG') == 'True'
IS_ASYNCIO_REPL = os.getenv('ASYNCIO_REPL') == 'True' and IS_PY38_OR_GREATER
IS_ASYNCIO_DEBUGGER_ENV = os.getenv('ASYNCIO_DEBUGGER_ENV') == 'True' and IS_PY38_OR_GREATER

# If True, CMD_SET_NEXT_STATEMENT and CMD_RUN_TO_LINE commands have responses indicating success or failure.
GOTO_HAS_RESPONSE = IS_PYCHARM

if SHOW_DEBUG_INFO_ENV:
    # show debug info before the debugger start
    DebugInfoHolder.DEBUG_RECORD_SOCKET_READS = True
    DebugInfoHolder.LOG_TO_FILE_LEVEL = logging.DEBUG
    DebugInfoHolder.LOG_TO_CONSOLE_LEVEL = logging.DEBUG


def protect_libraries_from_patching():
    """
    In this function we delete some modules from `sys.modules` dictionary and import them again inside
      `_pydev_saved_modules` in order to save their original copies there. After that we can use these
      saved modules within the debugger to protect them from patching by external libraries (e.g. gevent).
    """
    patched = ['threading', 'thread', '_thread', 'time', 'socket', 'Queue', 'queue', 'select',
               'xmlrpclib', 'SimpleXMLRPCServer', 'BaseHTTPServer', 'SocketServer',
               'xmlrpc.client', 'xmlrpc.server', 'http.server', 'socketserver']

    for name in patched:
        try:
            __import__(name)
        except:
            pass

    patched_modules = dict([(k, v) for k, v in sys.modules.items()
                            if k in patched])

    for name in patched_modules:
        del sys.modules[name]

    # import for side effects
    import PyQtInspect._pqi_imps._pqi_saved_modules

    for name in patched_modules:
        sys.modules[name] = patched_modules[name]


if USE_LIB_COPY:
    protect_libraries_from_patching()

from PyQtInspect._pqi_imps._pqi_saved_modules import thread
_thread_id_lock = thread.allocate_lock()
thread_get_ident = thread.get_ident

if IS_PY3K:

    def dict_keys(d):
        return list(d.keys())

    def dict_values(d):
        return list(d.values())

    dict_iter_values = dict.values

    def dict_iter_items(d):
        return d.items()

    def dict_items(d):
        return list(d.items())

else:
    def dict_keys(d):
        return d.keys()

    try:
        dict_iter_values = dict.itervalues
    except:
        try:
            dict_iter_values = dict.values  # Older versions don't have the itervalues
        except:

            def dict_iter_values(d):
                return d.values()

    try:
        dict_values = dict.values
    except:

        def dict_values(d):
            return d.values()

    def dict_iter_items(d):
        try:
            return d.iteritems()
        except:
            return d.items()

    def dict_items(d):
        return d.items()

try:
    xrange = xrange
except:
    # Python 3k does not have it
    xrange = range

try:
    import itertools
    izip = itertools.izip
except:
    izip = zip


try:
    enumerate
except:
    def enumerate(lst):
        ret = []
        i = 0
        for element in lst:
            ret.append((i, element))
            i += 1
        return ret

try:
    from StringIO import StringIO
except:
    from io import StringIO

NO_FTRACE = None

if sys.version_info[:2] in ((3, 3), (3, 4), (3, 5)) or sys.version_info < (2, 7, 12):

    def NO_FTRACE(frame, event, arg):
        # In Python < 2.7.12 and <= 3.5, if we're tracing a method, frame.f_trace may not be set
        # to None, it must always be set to a tracing function.
        # See: pydev_tests_python.test_tracing_gotchas.test_tracing_gotchas
        return None


#=======================================================================================================================
# get_pid
#=======================================================================================================================
def get_pid():
    try:
        return os.getpid()
    except AttributeError:
        try:
            # Jython does not have it!
            import java.lang.management.ManagementFactory  # @UnresolvedImport -- just for jython
            pid = java.lang.management.ManagementFactory.getRuntimeMXBean().getName()
            return pid.replace('@', '_')
        except:
            # ok, no pid available (will be unable to debug multiple processes)
            return '000001'


def clear_cached_thread_id(thread):
    with _thread_id_lock:
        try:
            if thread.__pydevd_id__ != 'console_main':
                # The console_main is a special thread id used in the console and its id should never be reset
                # (otherwise we may no longer be able to get its variables -- see: https://www.brainwy.com/tracker/PyDev/776).
                del thread.__pydevd_id__
        except AttributeError:
            pass


# Don't let threads be collected (so that id(thread) is guaranteed to be unique).
_thread_id_to_thread_found = {}


def _get_or_compute_thread_id_with_lock(thread, is_current_thread):
    with _thread_id_lock:
        # We do a new check with the lock in place just to be sure that nothing changed
        tid = getattr(thread, '__pydevd_id__', None)
        if tid is not None:
            return tid

        _thread_id_to_thread_found[id(thread)] = thread

        # Note: don't use thread.ident because a new thread may have the
        # same id from an old thread.
        pid = get_pid()
        tid = 'pid_%s_id_%s' % (pid, id(thread))

        thread.__pydevd_id__ = tid

    return tid


def get_current_thread_id(thread):
    '''
    Note: the difference from get_current_thread_id to get_thread_id is that
    for the current thread we can get the thread id while the thread.ident
    is still not set in the Thread instance.
    '''
    try:
        # Fast path without getting lock.
        tid = thread.__pydevd_id__
        if tid is None:
            # Fix for https://www.brainwy.com/tracker/PyDev/645
            # if __pydevd_id__ is None, recalculate it... also, use an heuristic
            # that gives us always the same id for the thread (using thread.ident or id(thread)).
            raise AttributeError()
    except AttributeError:
        tid = _get_or_compute_thread_id_with_lock(thread, is_current_thread=True)

    return tid


#=======================================================================================================================
# Null
#=======================================================================================================================
class Null:
    """
    Gotten from: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68205
    """

    def __init__(self, *args, **kwargs):
        return None

    def __call__(self, *args, **kwargs):
        return self

    def __enter__(self, *args, **kwargs):
        return self

    def __exit__(self, *args, **kwargs):
        return self

    def __getattr__(self, mname):
        if len(mname) > 4 and mname[:2] == '__' and mname[-2:] == '__':
            # Don't pretend to implement special method names.
            raise AttributeError(mname)
        return self

    def __setattr__(self, name, value):
        return self

    def __delattr__(self, name):
        return self

    def __repr__(self):
        return "<Null>"

    def __str__(self):
        return "Null"

    def __len__(self):
        return 0

    def __getitem__(self):
        return self

    def __setitem__(self, *args, **kwargs):
        pass

    def write(self, *args, **kwargs):
        pass

    def __nonzero__(self):
        return 0

    def __iter__(self):
        return iter(())


# Default instance
NULL = Null()


def call_only_once(func):
    '''
    To be used as a decorator

    @call_only_once
    def func():
        print 'Calling func only this time'

    Actually, in PyDev it must be called as:

    func = call_only_once(func) to support older versions of Python.
    '''

    def new_func(*args, **kwargs):
        if not new_func._called:
            new_func._called = True
            return func(*args, **kwargs)

    new_func._called = False
    return new_func


#=======================================================================================================================
# GlobalDebuggerHolder
#=======================================================================================================================
class GlobalDebuggerHolder:
    '''
        Holder for the global debugger.
    '''
    global_dbg = None  # Note: don't rename (the name is used in our attach to process)


#=======================================================================================================================
# get_global_debugger
#=======================================================================================================================
def get_global_debugger():
    return GlobalDebuggerHolder.global_dbg


GetGlobalDebugger = get_global_debugger  # Backward-compatibility


#=======================================================================================================================
# set_global_debugger
#=======================================================================================================================
def set_global_debugger(dbg):
    GlobalDebuggerHolder.global_dbg = dbg


# =======================================================================================================================
# Qt Patch
# =======================================================================================================================

QtWidgetClasses = [
    'QWidget',
    'QAbstractButton',
    'QFrame',
    'QAbstractSlider',
    'QAbstractSpinBox',
    'QCalendarWidget',
    'QDialog',
    'QComboBox',
    'QDesktopWidget',
    'QDialogButtonBox',
    'QDockWidget',
    'QFocusFrame',
    'QGroupBox',
    'QKeySequenceEdit',
    'QLineEdit',
    'QMainWindow',
    'QMdiSubWindow',
    'QMenu',
    'QMenuBar',
    'QOpenGLWidget',
    'QProgressBar',
    'QRubberBand',
    'QSizeGrip',
    'QSplashScreen',
    'QSplitterHandle',
    'QStatusBar',
    'QTabBar',
    'QTabWidget',
    'QToolBar',
    'QWizardPage',
    'QCheckBox',
    'QPushButton',
    'QRadioButton',
    'QToolButton',
    'QCommandLinkButton',
    'QAbstractScrollArea',
    'QLCDNumber',
    'QLabel',
    # 'QSplitter',  # ignored
    'QStackedWidget',
    'QToolBox',
    'QAbstractItemView',
    'QGraphicsView',
    'QMdiArea',
    'QPlainTextEdit',
    'QScrollArea',
    'QTextEdit',
    'QColumnView',
    'QHeaderView',
    'QListView',
    'QTableView',
    'QTreeView',
    'QListWidget',
    'QUndoView',
    'QTableWidget',
    'QTreeWidget',
    'QTextBrowser',
    'QDial',
    'QScrollBar',
    'QSlider',
    'QDateTimeEdit',
    'QDoubleSpinBox',
    'QSpinBox',
    'QDateEdit',
    'QTimeEdit',
    'QColorDialog',
    'QErrorMessage',
    'QFileDialog',
    'QFontDialog',
    'QInputDialog',
    'QMessageBox',
    'QProgressDialog',
    'QWizard',
    'QFontComboBox',
]

SUPPORT_CC = True

if __name__ == '__main__':
    if Null():
        sys.stdout.write('here\n')
