from PySide6.QtWidgets import QApplication
from PySide6.QtGui import QFontMetrics

from ....common.config import config
from ....format.time import Time

from typing import List, Tuple, Optional

ass_base = """[Script Info]
; Script generated by Bili23 Downloader
; http://bili23.scott-sloan.cn
Title: {title}
ScriptType: v4.00+
WrapStyle: 2
Collisions: Normal
ScaledBorderAndShadow: yes
Aspect Ratio: {width}:{height}
YCbCr Matrix: TV.601
PlayResX: {width}
PlayResY: {height}

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
{style}

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
{dialogues}
"""

class ScrollTrack:
    def __init__(self, screen_width: int, min_gap: int):
        self.screen_width = screen_width
        self.min_gap = min_gap
        self.last_stime = -1
        self.last_duration = 0
        self.last_width = 0
        self.last_speed = 0.0

    def can_fit(self, stime: int, speed: float) -> bool:
        if self.last_stime == -1:
            return True
            
        # 条件1：前一条弹幕的尾部必须离开右边缘，并保持最小间距
        cond1 = stime >= self.last_stime + ((self.last_width + self.min_gap) / self.last_speed)
        
        # 条件2：前一条弹幕尾巴到达屏幕左边缘时，当前弹幕的头还没到达屏幕左边缘，并保持最小间距
        cond2 = stime >= self.last_stime + self.last_duration - ((self.screen_width - self.min_gap) / speed)
        
        return cond1 and cond2

    def push(self, stime: int, duration_ms: int, text_width: int, speed: float):
        self.last_stime = stime
        self.last_duration = duration_ms
        self.last_width = text_width
        self.last_speed = speed


class StaticTrack:
    def __init__(self):
        self.end_time = -1
        
    def can_fit(self, stime: int) -> bool:
        return stime >= self.end_time
        
    def push(self, end_time: int):
        self.end_time = end_time


class DanmakuLayoutEngine:
    def __init__(self, screen_width: int, screen_height: int):
        self.screen_width = screen_width
        self.screen_height = screen_height
        
        self._load_config()

        # 初始化不同类型弹幕的轨道
        self.scroll_tracks = [ScrollTrack(screen_width, self.min_gap) for _ in range(self.max_scroll_rows)]
        self.top_tracks = [StaticTrack() for _ in range(self.max_static_rows)]
        self.bottom_tracks = [StaticTrack() for _ in range(self.max_static_rows)]

    def _load_config(self):
        style = config.get(config.danmaku_style)
        
        font = QApplication.font()

        font.setFamily(style["font"]["name"])
        font.setPixelSize(style["font"]["size"])
        font.setBold(style["font"]["bold"])
        font.setItalic(style["font"]["italic"])
        font.setUnderline(style["font"]["underline"])
        font.setStrikeOut(style["font"]["strike"])
        
        self.font_metrics = QFontMetrics(font)
        self.line_height = self.font_metrics.height() + 4

        self.display_area = style["advanced"]["display_area"] / 100.0
        self.opacity = style["advanced"]["opacity"] / 100.0
        self.min_gap = style["advanced"]["minimum_gap"]
        
        # 滚动区域行数计算
        total_scroll_rows = int((self.screen_height * self.display_area) / self.line_height)
        self.max_scroll_rows = max(1, total_scroll_rows)
        
        # 顶部/底部固定弹幕区域
        self.max_static_rows = max(1, int(self.screen_height * self.display_area / self.line_height))
        
    def alloc_scroll(self, stime: int, text_width: int, duration_ms: int) -> Optional[int]:
        speed = (self.screen_width + text_width) / duration_ms

        for row, track in enumerate(self.scroll_tracks):
            if track.can_fit(stime, speed):
                track.push(stime, duration_ms, text_width, speed)
                return row
        
        return None
        
    def alloc_top(self, stime: int, duration_ms: int) -> Optional[int]:
        for row, track in enumerate(self.top_tracks):
            if track.can_fit(stime):
                track.push(stime + duration_ms)
                return row
        
        return None
        
    def alloc_bottom(self, stime: int, duration_ms: int) -> Optional[int]:
        for row, track in enumerate(self.bottom_tracks):
            if track.can_fit(stime):
                track.push(stime + duration_ms)
                return row
        
        return None


class DanmakuASS:
    def __init__(self, dict_list: List[dict], title: str):
        # 弹幕按出现时间排序
        self.dict_list = sorted(dict_list, key=lambda x: x.get("stime", 0))
        self.title = title
        
        # 各模式的显示时长 (毫秒)
        self.duration_map = {
            1: 10000, 2: 10000, 3: 10000, # 滚动弹幕
            4: 5000,  # 底部弹幕
            5: 5000   # 顶部弹幕
        }

    def generate(self) -> str:
        style_str, screen_width, screen_height = self._get_style_info()
        
        engine = DanmakuLayoutEngine(screen_width, screen_height)
        dialogues = self._convert_dialogues(engine)
        
        return ass_base.format(
            title = self.title,
            style = style_str,
            dialogues = "\n".join(dialogues),
            width = screen_width,
            height = screen_height
        )
        
    def _get_style_info(self) -> Tuple[str, int, int]:
        style = config.get(config.danmaku_style)
        
        screen_width = style["resolution"]["width"]
        screen_height = style["resolution"]["height"]
        
        opacity = style["advanced"]["opacity"] / 100.0
        alpha = int((1.0 - opacity) * 255)
        alpha_hex = f"{alpha:02X}"
        
        style_str = "Style: Default,{font},{size},&H{alpha}FFFFFF,&H{alpha}0000FF,&H{alpha}000000,&H{alpha}000000," \
                    "{bold},{italic},{underline},{strikeout},100,100,0,0,1,{border},{shadow},7,0,0,0,1".format(
            font = style["font"]["name"],
            size = style["font"]["size"],
            alpha = alpha_hex,
            bold = int(style["font"]["bold"]),
            italic = int(style["font"]["italic"]),
            underline = int(style["font"]["underline"]),
            strikeout = int(style["font"]["strike"]),
            border = style["border"]["border"],
            shadow = style["border"]["shadow"]
        )
        return style_str, screen_width, screen_height
        
    def _convert_dialogues(self, engine: DanmakuLayoutEngine) -> List[str]:
        dialogues = []
        
        for entry in self.dict_list:
            mode = entry.get("mode")
            stime = entry.get("stime", 0)
            text = entry.get("text", "")
            
            if not text or mode not in self.duration_map:
                continue
                
            duration_ms = self.duration_map[mode]
            text_width = engine.font_metrics.horizontalAdvance(text)
            
            start_ass = Time.format_ass_time_by_ms(stime)
            end_ass = Time.format_ass_time_by_ms(stime + duration_ms)
            
            style_label = ""
            allocated_row = None
            
            if mode in (1, 2, 3):
                allocated_row = engine.alloc_scroll(stime, text_width, duration_ms)

                if allocated_row is not None:
                    y_pos = allocated_row * engine.line_height
                    style_label = f"\\move({engine.screen_width},{y_pos},-{text_width},{y_pos})"
                    
            elif mode == 5:
                allocated_row = engine.alloc_top(stime, duration_ms)

                if allocated_row is not None:
                    y_pos = allocated_row * engine.line_height
                    style_label = f"\\an8\\pos({engine.screen_width // 2},{y_pos})"
                    
            elif mode == 4:
                allocated_row = engine.alloc_bottom(stime, duration_ms)

                if allocated_row is not None:
                    y_pos = engine.screen_height - allocated_row * engine.line_height
                    style_label = f"\\an2\\pos({engine.screen_width // 2},{y_pos})"
            
            # 如果没有分配到轨道（满屏），则抛弃该弹幕以防重叠
            if allocated_row is not None:
                # 解析可能附加的颜色
                color_tag = ""
                if "color" in entry and entry["color"] != 16777215:
                    try:
                        c = int(entry["color"])
                        bgr = ((c & 0xFF) << 16) | (c & 0xFF00) | ((c >> 16) & 0xFF)
                        color_tag = f"\\c&H{bgr:06X}&"
                    except (ValueError, TypeError):
                        pass

                dialogue = f"Dialogue: 0,{start_ass},{end_ass},Default,,0,0,0,,{{{style_label}{color_tag}}}{text}"
                dialogues.append(dialogue)
                
        return dialogues

