# -*- coding: utf-8 -*-
import sys, os, cv2
from os import makedirs
from os.path import join, exists
from loguru import logger
from json import loads
from ruamel.yaml import safe_load, YAML
import random, sys, math, inspect, psutil
from pathlib import Path
from PySide6.QtGui import QIcon, QColor
from PySide6.QtCore import QObject, QRectF, QEventLoop, QIODevice, QTextStream, QFile
from PySide6.QtWidgets import QApplication

import DrGraph.utils.vclEnums as enums
#region Property
class Property:
    def __init__(self, read_func=None, write_func=None, default=None, hasMember=True):
        self.read_func = read_func  
        self.write_func = write_func
        self.default = default
        self.owner_class = None  
        self.private_name = None
        self.hasMember = hasMember
    def __set_name__(self, owner, name):
        if self.hasMember:
            self.private_name = f"_{name}"
            self.owner_class = owner  
    def callDirectGet(self, instance, owner):
        if instance is None or not self.hasMember:
            return self.default
        if not hasattr(instance, self.private_name):
            return self.default
        return getattr(instance, self.private_name)
    def callCustomGet(self, instance, owner):
        if instance is None:
            return self
        
        if self.read_func is None:
            if hasattr(instance, self.private_name):
                return getattr(instance, self.private_name)
            return self.default
        
        try:
            if isinstance(self.read_func, str):
                if hasattr(instance, self.read_func):
                    method = getattr(instance, self.read_func)
                    return method()
            elif hasattr(self.read_func, '__name__'):
                method_name = self.read_func.__name__
                if hasattr(instance, method_name):
                    method = getattr(instance, method_name)
                    return method()
            elif callable(self.read_func):
                try:
                    return self.read_func(instance)
                except (TypeError, AttributeError):
                    return self.read_func()
        except Exception as e:
            logger.error(e)
        
        if hasattr(instance, self.private_name):
            return getattr(instance, self.private_name)
        return self.default

    def callDirectSet(self, instance, value):
        if instance is None or not self.hasMember:
            return
        setattr(instance, self.private_name, value)

    def callCustomSet(self, instance, value):
        try:
            if isinstance(self.write_func, str):
                if hasattr(instance, self.write_func):
                    method = getattr(instance, self.write_func)
                    method(value)
            elif hasattr(self.write_func, '__name__'):
                method_name = self.write_func.__name__
                if hasattr(instance, method_name):
                    method = getattr(instance, method_name)
                    method(value)
            elif callable(self.write_func):
                try:
                    self.write_func(instance, value)
                except (TypeError, AttributeError):
                    self.write_func(value)
        except Exception as e:
            pass

class Property_rw(Property):
    def __init__(self, default=None, hasMember=True):
        super().__init__(None, None, default, hasMember)
    def __get__(self, instance, owner):
        return self.callDirectGet(instance, owner)
    def __set__(self, instance, value):
        self.callDirectSet(instance, value)

class Property_Rw(Property):
    def __init__(self, read_func=None, default=None, hasMember=True):
        super().__init__(read_func, None, default, hasMember)
    
    def __get__(self, instance, owner):
        return self.callCustomGet(instance, owner)
    
    def __set__(self, instance, value):
        setattr(instance, self.private_name, value)

class Property_rW(Property):
    def __init__(self, write_func=None, default=None, hasMember=True):
        super().__init__(None, write_func, default, hasMember)

    def __get__(self, instance, owner):
        return self.callDirectGet(instance, owner)
    
    def __set__(self, instance, value):    
        self.callCustomSet(instance, value) 

class Property_RW(Property):
    def __init__(self, read_func=None, write_func=None, default=None, hasMember=True):
        super().__init__(read_func, write_func, default, hasMember)

    def __get__(self, instance, owner):
        return self.callCustomGet(instance, owner)
    
    def __set__(self, instance, value):    
        self.callCustomSet(instance, value) 
#endregion Property

class AppHelper(QObject):
    app = Property_rw(None)
    def setBriefStatusText(self, text):
        if self.briefStatusControl:
            self.briefStatusControl.setText(text)
        else:
            print(text)
    briefStatusText = Property_rW(setBriefStatusText, '')

    def setProgress(self, value):
        if self.progressBarControl:
            self.progressBarControl.setValue(value)
    progress = Property_rW(setProgress, 0)

    def setProgressMax(self, value):
        if self.progressBarControl:
            self.progressBarControl.setMaximum(value)
    progressMax = Property_rW(setProgressMax, 100)

    def setProgressMin(self, value):
        if self.progressBarControl:
            self.progressBarControl.setMinimum(value)
    progressMin = Property_rW(setProgressMin, 0)

    def __init__(self):
        self.briefStatusControl = None
        self.progressBarControl = None
        self._briefStatusText = ''
        pass

class Helper:
    OnLogMsg = None
    AppFlag_SaveAnalysisResult = True
    AppFlag_SaveLog = False
    App = None

    @staticmethod
    def castRange(value, minValue, maxValue):
        return max(minValue, min(maxValue, value))
    # 取得程序目录
    @staticmethod
    def getPath_App():
        if getattr(sys, 'frozen', False):
            # 如果程序是打包的exe文件
            return os.path.dirname(sys.executable)
        else:
            # 如果是Python脚本 - 获取上两级目录
            current_file = os.path.abspath(__file__)           # f:\PySide6\AiBase\DrGraph\utils\Helper.py
            current_dir = os.path.dirname(current_file)        # f:\PySide6\AiBase\DrGraph\utils
            parent_dir = os.path.dirname(current_dir)          # f:\PySide6\AiBase\DrGraph
            root_dir = os.path.dirname(parent_dir)             # f:\PySide6\AiBase
            return root_dir
    
    @staticmethod
    def fitOS(file_name):
        if sys.platform.startswith('win'):
            file_name = file_name.replace('/','\\')
        else:
            file_name = file_name.replace('\\', '/')
        return file_name

    def generateDistinctColors(n, s=0.8, v=0.7):
        import colorsys
        colors = []
        for i in range(n):
            hue = i * 1.0 / n  # 均匀分布在 [0, 1)
            r, g, b = colorsys.hsv_to_rgb(hue, s, v)
            colors.append(QColor(r * 255, g * 255, b * 255))
        return colors
            
    def setBriefStatusText(self, text):
        Helper.App.setBriefStatusText(text)
    briefStatusText = Property_rW(setBriefStatusText, '')
    @staticmethod
    def Sleep(msec):
        QApplication.processEvents(QEventLoop.AllEvents, msec)


    @staticmethod
    def getAbsoluteFileName(file_name):
        if os.path.isabs(file_name):
            return Helper.fitOS(file_name)
        else:
            return Helper.fitOS(os.path.join(Helper.getPath_App(), file_name))
    @staticmethod
    def getConfigs(path, read_type='yml'):
        """
        读取配置文件并返回解析后的配置信息
        
        :param path: 配置文件路径
        :param read_type: 配置文件类型，默认为'yml'，可选'json'或'yml'
        :return: 解析后的配置信息，JSON格式返回字典，YML格式返回对应的数据结构
        :raises Exception: 当无法获取配置信息时抛出异常
        """
        yaml = YAML(typ='safe', pure=True)
        with open(path, 'r', encoding='utf-8') as f:
            return yaml.load(f)
        # with open(path, 'r', encoding="utf-8") as f:
        #     # 根据文件类型选择相应的解析方式
        #     if read_type == 'json':
        #         return loads(f.read())
        #     if read_type == 'yml':
        #         return safe_load(f)
        # 如果未成功读取配置信息，则抛出异常
        raise Exception('路径: %s未获取配置信息' % path)

    @staticmethod
    def getTooltipText(content):
        # 增加一个小喇叭图标
        # content = f'<img src="appIOs/res/images/icons/info.png" width="16" height="16"> {content}'
        return f"""
<html>
<head/><body>
    <p><span style=" font-weight:600; color:#ffffff;">DrGraph <img src="appIOs/res/images/icons/Notice.png" width="16" height="16"></span></p>
    <p>{content}</p>
</body>
</html>
"""
    @staticmethod
    def log_init(app, base_dir, env):
        """
        初始化日志配置
        
        :param base_dir: 基础目录路径，用于定位配置文件和日志文件存储位置
        :param env: 环境标识，用于加载对应环境的日志配置文件
        :return: 无返回值
        """
        Helper.App = AppHelper()
        Helper.App.app = app
        # QToolTip样式 - 自定义样式 - 增加Header
        app.setStyleSheet("""
            QToolTip {
                background-color: #dd2222;
                color: #f0f0f0;
                border: 1px solid #555;
                border-radius: 4px;
                padding: 6px;
                font: 10pt "Segoe UI";
                opacity: 220; 
            }
        """)

        log_config = Helper.getConfigs(join(base_dir, 'appIOs/configs/logger/drgraph_%s_logger.yml' % env))
        # 判断日志文件是否存在，不存在创建
        base_path = join(base_dir, log_config.get("base_path"))
        if not exists(base_path):
            makedirs(base_path)
        # 移除日志设置
        logger.remove(handler_id=None)
        # 打印日志到文件
        if bool(log_config.get("enable_file_log")):
            logger.add(join(base_path, log_config.get("log_name")),
                        rotation=log_config.get("rotation"),
                        retention=log_config.get("retention"),
                        format=log_config.get("log_fmt"),
                        level=log_config.get("level"),
                        enqueue=True,
                        encoding=log_config.get("encoding"))
        # 控制台输出
        if bool(log_config.get("enable_stderr")):
            logger.add(sys.stderr,
                        format=log_config.get("log_fmt"),
                        level=log_config.get("level"),
                        enqueue=True)
        logger.info("\n\n\n----=========== 日志配置初始化完成, 开始新的日志记录 ==========----")

    @staticmethod
    def log_info(msg, toWss = False):
        if Helper.OnLogMsg: 
            Helper.OnLogMsg(f'INFO: {msg}', 'black')
        caller = inspect.stack()[1]
        logger.info(f"[{Path(caller.filename).name}:{caller.lineno}.{caller.function}()] > {msg}")    
        if toWss:
            Helper.log_wss({"type": "log", "kind": "INFO", "msg" : msg} )
    @staticmethod
    def log_error(msg, toWss = False):
        if Helper.OnLogMsg: 
            Helper.OnLogMsg(f'ERROR: {msg}', 'red')
        caller = inspect.stack()[1]
        logger.error(f"[{Path(caller.filename).name}:{caller.lineno}.{caller.function}()] > {msg}")
        if toWss:
            Helper.log_wss({"type": "log", "kind": "ERROR", "msg" : msg} )
    @staticmethod
    def log_warning(msg, toWss = False):
        if Helper.OnLogMsg: 
            Helper.OnLogMsg(f'WARNING: {msg}', (255, 128, 0))
        caller = inspect.stack()[1]
        logger.warning(f"[{Path(caller.filename).name}:{caller.lineno}.{caller.function}()] > {msg}")  
        if toWss:
            Helper.log_wss({"type": "log", "kind": "WARNING", "msg" : msg} )
    @staticmethod
    def log_debug(msg, toWss = False):
        if Helper.OnLogMsg: 
            Helper.OnLogMsg(f'DEBUG: {msg}', (0, 128, 128))
        caller = inspect.stack()[1]
        logger.debug(f"[{Path(caller.filename).name}:{caller.lineno}.{caller.function}()] > {msg}")  
        if toWss:
            Helper.log_wss({"type": "log", "kind": "DEBUG", "msg" : msg} )
    @staticmethod
    def log_critical(msg, toWss = False):
        if Helper.OnLogMsg: 
            Helper.OnLogMsg(f'CRITICAL: {msg}', (128, 0, 128))
        caller = inspect.stack()[1]
        logger.critical(f"[{Path(caller.filename).name}:{caller.lineno}.{caller.function}()] > {msg}")  
        if toWss:
            Helper.log_wss({"type": "log", "kind": "CRITICAL", "msg" : msg} )
    @staticmethod
    def log_exception(msg, toWss = False):
        if Helper.OnLogMsg: 
            Helper.OnLogMsg(f'EXCEPTION: {msg}', (255, 140, 0))
        caller = inspect.stack()[1]
        logger.exception(f"[{Path(caller.filename).name}:{caller.lineno}.{caller.function}()] > {msg}")  
        if toWss:
            Helper.log_wss({"type": "log", "kind": "EXCEPTION", "msg" : msg} )
    @staticmethod
    def log(msg, toWss = False):
        caller = inspect.stack()[1]
        logger.log(f"[{Path(caller.filename).name}:{caller.lineno}.{caller.function}()] > {msg}")  
        if toWss:
            Helper.log_wss({"type": "log", "kind": "LOG", "msg" : msg} )
    @staticmethod
    def log_wss(msg):
        if Helper.wss: 
            Helper.wss.send(msg)
            
    @staticmethod
    def getTextSize(font, text):
        import pygame as pg
        surface = font.render(text, True, (0, 0, 0))
        return (surface.get_width(), surface.get_height(), surface)
    
    @staticmethod
    def buildSurfaces(font, text, width, color, wordWrap):
        text = text.strip()
        w = Helper.getTextSize(font, text)[0]
        result = []
        if w > width and wordWrap:
            segLen = math.floor(width / w * len(text))
            while len(text):
                if len(text) < segLen:
                    t = text
                    text = ''
                else:
                    t = text[:segLen]
                    text = text[segLen:]
                result.append(font.render(t, True, color))
        else:
            result.append(font.render(text, True, color))
        return result
    
    @staticmethod
    def randomColor():
        '''随机颜色'''
        return (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
    
    @staticmethod
    def reverseColor(color: QColor):
        '''反转颜色'''
        return (255 - color.red(), 255 - color.green(), 255 - color.blue())    
    @staticmethod
    def getRGB(color_value):
        # 如果是元组或列表形式的RGB值
        if isinstance(color_value, (tuple, list)):
            if len(color_value) >= 3:
                # 取前三个值作为RGB
                r, g, b = color_value[0], color_value[1], color_value[2]
                # 确保值在0-255范围内
                return (max(0, min(255, int(r))), 
                        max(0, min(255, int(g))), 
                        max(0, min(255, int(b))))
        
        # 如果是整数形式的颜色值
        elif isinstance(color_value, int):
            # 将整数转换为RGB分量
            # 假设格式为0xRRGGBB
            r = (color_value >> 16) & 0xFF
            g = (color_value >> 8) & 0xFF
            b = color_value & 0xFF
            return (r, g, b)
        
        # 如果是字符串形式
        elif isinstance(color_value, str):
            # 处理十六进制颜色值
            if color_value.startswith('#'):
                hex_value = color_value[1:]
                if len(hex_value) == 3:  # 简写形式 #RGB
                    hex_value = ''.join([c*2 for c in hex_value])
                if len(hex_value) in (6, 8):  # #RRGGBB 或 #RRGGBBAA
                    r = int(hex_value[0:2], 16)
                    g = int(hex_value[2:4], 16)
                    b = int(hex_value[4:6], 16)
                    return (r, g, b)
            # 处理颜色名称（需要额外的颜色名称映射表）
            # 这里只列举几种常见颜色
            color_names = {
                'black': (0, 0, 0),
                'white': (255, 255, 255),
                'red': (255, 0, 0),
                'green': (0, 255, 0),
                'blue': (0, 0, 255),
                'yellow': (255, 255, 0),
                'magenta': (255, 0, 255),
                'cyan': (0, 255, 255),
                'orange': (255, 128, 0),  # 根据项目规范
                'teal': (0, 128, 128)     # 根据项目规范
            }
            if color_value.lower() in color_names:
                return color_names[color_value.lower()]
        
        # 如果是Color对象（如pygame.Color）
        elif hasattr(color_value, 'r') and hasattr(color_value, 'g') and hasattr(color_value, 'b'):
            return (color_value.r, color_value.g, color_value.b)
        
        # 默认返回黑色
        return (0, 0, 0)
        
    @staticmethod
    def check_system_resources():
        """检查系统资源使用情况"""
        logger.info("检查系统资源使用情况...")
        cpu_percent = psutil.cpu_percent(interval=1)
        memory = psutil.virtual_memory()
        network = psutil.net_io_counters()
        
        logger.info("检查系统资源使用情况完毕")
        
        return {
            'cpu_percent': cpu_percent,
            'memory_percent': memory.percent,
            'memory_available': memory.available / (1024**3),  # GB
            'network_bytes_sent': int(network.bytes_sent / 1024),
            'network_bytes_recv': int(network.bytes_recv / 1024)
        }
            
    @staticmethod
    def build_response(type, status : enums.Response, msg):
        status_code, status_msg = status.value
        result = {
            "type": "response",
            "request_type": type,
            "status_code": status_code,
            "status_msg": status_msg,
            "detail_msg": msg
        }
        if status_code != 0:
            Helper.error(result);
        return result
    
    @staticmethod
    def get_surrounding_rect(points):
        if len(points) == 0:
            return Constant.invalid_rect
        min_x = min(p.x() for p in points)
        min_y = min(p.y() for p in points)
        max_x = max(p.x() for p in points)
        max_y = max(p.y() for p in points)
        return QRectF(min_x, min_y, max_x - min_x, max_y - min_y)
    
    @staticmethod
    def getYoloLabellingInfo(dir_path, file_names, desc):
        if len(dir_path) > 0:
            imageNumber, labelNumber = 0, 0
            imagePath = dir_path + 'images/'
            labelPath = dir_path + 'labels/'
            for file_name in file_names:
                if file_name.startswith(imagePath):
                    imageNumber += 1
                elif file_name.startswith(labelPath):
                    labelNumber += 1
            return f'{desc} {imageNumber - 1} 张图片,{labelNumber - 1} 张标签;', imageNumber - 1
        return f'无{desc};', 0

    @staticmethod
    def getMarkdownRenderText(mdContent):
        # 使用Python库直接将Markdown转换为HTML，避免JavaScript依赖
        try:
            # 尝试导入markdown库
            import markdown
            html_content = markdown.markdown(mdContent)
            # 添加基本样式使其美观
            styled_html = f"""
            <!DOCTYPE html>
            <html>
            <head>
            <meta charset="utf-8">
            <style>
                body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; 
                    max-width: 900px; margin: 20px auto; padding: 0 20px; line-height: 1.6; }}
                code {{ background: #f5f5f5; padding: 2px 4px; border-radius: 3px; }}
                pre {{ background: #f5f5f5; padding: 10px; border-radius: 5px; overflow: auto; }}
                pre code {{ background: none; padding: 0; }}
                h1, h2, h3 {{ color: #333; border-bottom: 1px solid #eee; padding-bottom: 5px; }}
            </style>
            </head>
            <body>
                {html_content}
            </body>
            </html>
            """
            return styled_html
        except ImportError:
            # 回退到JavaScript的marked.js方法
            logger.warning("未找到markdown库，使用JavaScript渲染方式")
            # 从appIOs/configs加载marked.js
            file_js = QFile('appIOs/configs/marked.min.js')
            markedJs = ''
            if file_js.open(QIODevice.ReadOnly | QIODevice.Text):
                markedJs = file_js.readAll().data().decode('utf-8')
                file_js.close()
            
            # 转义markdown内容
            escapedMd = mdContent.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#x27;')
            
            # 创建HTML模板
            htmlTemplate = '''
                <!DOCTYPE html>
                <html>
                <head>
                <meta charset="utf-8">
                <style>
                    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; 
                        max-width: 900px; margin: 20px auto; padding: 0 20px; line-height: 1.6; }
                    code { background: #f5f5f5; padding: 2px 4px; border-radius: 3px; }
                    pre { background: #f5f5f5; padding: 10px; border-radius: 5px; overflow: auto; }
                    pre code { background: none; padding: 0; }
                    h1, h2, h3 { color: #333; border-bottom: 1px solid #eee; padding-bottom: 5px; }
                </style>
                <script>
                    // 加载marked库
                    %1
                </script>
                </head>
                <body>
                <div id="content"></div>
                <script>
                    const md = `%2`;
                    document.getElementById('content').innerHTML = marked.parse(md);
                </script>
                </body>
                </html>
                '''
            
            # 生成HTML内容
            htmlContent = htmlTemplate.replace('%1', markedJs).replace('%2', escapedMd)
            return htmlContent        
    @staticmethod
    def getMarkdownRender(mdFileName):
        file = QFile(mdFileName)
        mdContent = ''
        if file.open(QIODevice.ReadOnly | QIODevice.Text):
            stream = QTextStream(file)
            stream.setAutoDetectUnicode(True) 
            mdContent = stream.readAll()
            file.close()
            return Helper.getMarkdownRenderText(mdContent)
        else:
            logger.error(f'打开文件 {mdFileName} 失败')
            return f"<p style='color: red;'>无法打开文件: {mdFileName}</p>"

class RTTI:
    @staticmethod
    def _do_set_attr(obj, property_name, property_value):
        if obj is None:
            logger.error(f"RTTI.set: obj is None")
            return
        
        class_name = type(obj).__name__
        object_name = obj.objectName()
        if property_name not in dir(obj):
            logger.error(f"RTTI.set: {class_name} {object_name}.{property_name} not in dir(obj)")
            return
        if property_name.endswith('icon') and isinstance(property_value, str):
            original_property_value = property_value
            if not os.path.exists(property_value):
                property_value = os.path.join('appIOs/res/images/icons',property_value)
                # logger.info(f"RTTI.set: {class_name} {object_name}.{property_name} = {property_value}(自动匹配)")
            if not os.path.exists(property_value):
                logger.error(f"{original_property_value}文件不存在 > RTTI.set: {class_name} {object_name}.{property_name} = '{original_property_value}'")
                return
            property_value = QIcon(property_value)
        setter_method = getattr(obj, f'set{property_name[0].upper() + property_name[1:]}')
        setter_method(property_value)
    @staticmethod
    def set(obj, property_name, property_value):
        property_list = property_name.split('.')
        if len(property_list) == 1:
            RTTI._do_set_attr(obj, property_name, property_value)
        else:
            dest_obj = obj
            for i in range(len(property_list) - 1):
                if not dest_obj:
                    logger.error(f"RTTI.set: {property_list.join('.')} not found")
                    return
                dest_obj = getattr(dest_obj, property_list[i])
            RTTI._do_set_attr(dest_obj, property_list[-1], property_value)
    
    @staticmethod
    def _do_get_attr(obj, property_name):
        if obj is None:
            logger.error(f"RTTI.get: obj is None")
            return None, None
        if property_name not in dir(obj):
            logger.error(f"RTTI.get: {type(obj).__name__} {obj.objectName()}.{property_name} not in dir(obj)")
            return None, None
        # 返回属性类型与属性值
        type_name = type(getattr(obj, property_name)).__name__
        value = getattr(obj, property_name)
        return type_name, value
    # 取得属性类型与属性值 type, value = RTTI.get(obj, property_name)
    @staticmethod
    def get(obj, property_name):
        property_list = property_name.split('.')
        if len(property_list) == 1:
            return RTTI._do_get_attr(obj, property_name)
        else:
            dest_obj = obj
            for i in range(len(property_list) - 1):
                if not dest_obj:
                    logger.error(f"RTTI.get: {property_list.join('.')} not found")
                    return None
                dest_obj = getattr(dest_obj, property_list[i])
            return RTTI._do_get_attr(dest_obj, property_list[-1])

class DrawHelper:
    @staticmethod
    def draw_dashed_line(mat, pt1, pt2, color, thickness=1, dash_length=10):
        dist = ((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2) ** 0.5
        dashes = int(dist / dash_length)
        for i in range(dashes):
            start = (int(pt1[0] + (pt2[0] - pt1[0]) * i / dashes), int(pt1[1] + (pt2[1] - pt1[1]) * i / dashes))
            end = (int(pt1[0] + (pt2[0] - pt1[0]) * (i + 0.5) / dashes), int(pt1[1] + (pt2[1] - pt1[1]) * (i + 0.5) / dashes))
            cv2.line(mat, start, end, color, thickness)

    @staticmethod
    def draw_dashed_rect(painter, rect, color, thickness=1, dash_length=10):
        x1, y1 = rect.left(), rect.top()
        x2, y2 = rect.right(), rect.bottom()
        DrawHelper.draw_dashed_line(painter, (x1, y1), (x2, y1), color, thickness, dash_length)
        DrawHelper.draw_dashed_line(painter, (x1, y2), (x2, y2), color, thickness, dash_length)
        DrawHelper.draw_dashed_line(painter, (x1, y1), (x1, y2), color, thickness, dash_length)
        DrawHelper.draw_dashed_line(painter, (x2, y1), (x2, y2), color, thickness, dash_length)