#
# Copyright (C) 2010-2012 Vinay Sajip. All rights reserved. Licensed under the new BSD license.
# (Note: 2018 modifications by @stamparm)
#

import logging
import re
import sys

from lib.core.settings import IS_WIN    # 导入一个设置，用于判断是否在Windows系统上运行

if IS_WIN:
    import ctypes
    import ctypes.wintypes

    # Reference: https://gist.github.com/vsajip/758430
    #            https://github.com/ipython/ipython/issues/4252
    #            https://msdn.microsoft.com/en-us/library/windows/desktop/ms686047%28v=vs.85%29.aspx
    # 设置Windows API函数SetConsoleTextAttribute的参数和返回值类型
    ctypes.windll.kernel32.SetConsoleTextAttribute.argtypes = [ctypes.wintypes.HANDLE, ctypes.wintypes.WORD]
    ctypes.windll.kernel32.SetConsoleTextAttribute.restype = ctypes.wintypes.BOOL

def stdoutEncode(data):  # 用于编码标准输出数据的函数
    return data

class ColorizingStreamHandler(logging.StreamHandler):
    # 定义颜色名称到索引的映射
    color_map = {
        'black': 0,
        'red': 1,
        'green': 2,
        'yellow': 3,
        'blue': 4,
        'magenta': 5,
        'cyan': 6,
        'white': 7,
    }

    # 定义日志级别到颜色和样式的映射
    level_map = {
        logging.DEBUG: (None, 'blue', False),
        logging.INFO: (None, 'green', False),
        logging.WARNING: (None, 'yellow', False),
        logging.ERROR: (None, 'red', False),
        logging.CRITICAL: ('red', 'white', False)
    }
    csi = '\x1b['   # ANSI转义序列的前缀
    reset = '\x1b[0m'   # ANSI重置颜色的转义序列
    bold = "\x1b[1m"    # ANSI加粗的转义序列
    disable_coloring = False    # 是否禁用颜色

    @property
    def is_tty(self):
        # 检查流是否是终端
        isatty = getattr(self.stream, 'isatty', None)
        return isatty and isatty() and not self.disable_coloring

    def emit(self, record):
        # 发送日志记录
        try:
            message = stdoutEncode(self.format(record))
            stream = self.stream

            #如果当前流不是TTY，直接写入消息

            if not self.is_tty:
                if message and message[0] == "\r":
                    message = message[1:]
                stream.write(message)
            #如果是TTY，调用output_colorized方法来输出带颜色的消息
            else:
                self.output_colorized(message)
            stream.write(getattr(self, 'terminator', '\n'))

            self.flush()
        except (KeyboardInterrupt, SystemExit):
            raise
        except IOError:
            #IO错误时，什么也不做（pass）
            pass
        except:
            #其他异常时，调用handleError方法
            self.handleError(record)

    if not IS_WIN:
        def output_colorized(self, message):
            # 如果不是Windows系统，直接写入消息
            self.stream.write(message)
    else:
        ansi_esc = re.compile(r'\x1b\[((?:\d+)(?:;(?:\d+))*)m')
        # 正则表达式，用于匹配ANSI转义序列

        nt_color_map = {
            0: 0x00,    # black
            1: 0x04,    # red
            2: 0x02,    # green
            3: 0x06,    # yellow
            4: 0x01,    # blue
            5: 0x05,    # magenta
            6: 0x03,    # cyan
            7: 0x07,    # white
        }

        def output_colorized(self, message):
            # 如果是Windows系统，解析ANSI转义序列并设置控制台颜色
            parts = self.ansi_esc.split(message)
            h = None
            fd = getattr(self.stream, 'fileno', None)

            #文件描述符有效，并且是标准输出或标准错误，获取对应的Windows句柄
            if fd is not None:
                fd = fd()

                if fd in (1, 2): # stdout or stderr
                    h = ctypes.windll.kernel32.GetStdHandle(-10 - fd)

            #循环处理分割后的消息部分
            while parts:
                text = parts.pop(0)

                #如果部分是文本，写入并刷新流
                if text:
                    self.stream.write(text)
                    self.stream.flush()

                #如果还有部分，取出下一个部分作为参数
                if parts:
                    params = parts.pop(0)

                    #如果句柄有效，将参数分割并转换为整数，初始化颜色代码
                    if h is not None:
                        params = [int(p) for p in params.split(';')]
                        color = 0

                        for p in params:
                            if 40 <= p <= 47:
                                color |= self.nt_color_map[p - 40] << 4
                            elif 30 <= p <= 37:
                                color |= self.nt_color_map[p - 30]
                            elif p == 1:
                                color |= 0x08 # foreground intensity on
                            elif p == 0: # reset to default color
                                color = 0x07
                            else:
                                pass # error condition ignored

                        ctypes.windll.kernel32.SetConsoleTextAttribute(h, color)

    def _reset(self, message):
    #重置消息的颜色
        if not message.endswith(self.reset):
        # 如果消息不以重置序列结尾，则添加重置序列
            reset = self.reset
        elif self.bold in message:
        # 如果消息包含加粗，则在重置后加粗
            reset = self.reset + self.bold
        else:
            reset = self.reset

        return reset

    def colorize(self, message, levelno):
    # 根据日志级别给消息上色
        if levelno in self.level_map and self.is_tty:
            bg, fg, bold = self.level_map[levelno]
            params = []

            #如果背景色有效，添加背景色参数
            if bg in self.color_map:
                params.append(str(self.color_map[bg] + 40))

            #如果前景色有效，添加前景色参数
            if fg in self.color_map:
                params.append(str(self.color_map[fg] + 30))
            #如果需要加粗，添加加粗参数
            if bold:
                params.append('1')

            #如果参数和消息都有效，检查消息是否有前缀（空格），并提取出来
            if params and message:
                if message.lstrip() != message:
                    prefix = re.search(r"\s+", message).group(0)
                    message = message[len(prefix):]
                else:
                    prefix = ""

                message = "%s%s" % (prefix, ''.join((self.csi, ';'.join(params),
                                   'm', message, self.reset)))

        return message

    def format(self, record):
        # 格式化日志记录
        message = logging.StreamHandler.format(self, record)
        return self.colorize(message, record.levelno)