from __future__ import annotations

from os import PathLike
from typing import NamedTuple, Sequence

from .enum import Direction
from .util import to_rgb


class ASSStyleType(NamedTuple):
    name: str = 'Default'
    font_name: str = 'Helvetica'
    font_size: int = 60
    primary_color: str = '&Hffffff'
    secondary_color: str = '&Hffffff'
    outline_color: str = '&H0'
    back_color: str = '&H0'
    bold: bool = False
    italic: bool = False
    underline: bool = False
    strike_out: bool = False
    scale_x: int = 100
    scale_y: int = 100
    spacing: int = 0
    angle: int = 0
    border_style: int = 1
    outline: int = 5
    shadow: int = 0
    alignment: Direction = Direction.BOTTOM_CENTER
    margin_l: int = 10
    margin_r: int = 10
    margin_v: int = 30


def rgb_to_ass_color(color: str | tuple[int, int, int] | Sequence[int]) -> str:
    """Transform the rgb array into the string used in ASS file.

    Args:
        color:
            A union type that can be either a string or a tuple containing RGB integers or a sequence of integers.
            If ``str``: The string can be either a CSS color name (e.g., 'red') or a hexadecimal RGB string
            (e.g., ``'#FF0000'``). If ``tuple[int, int, int]`` or ``Sequence[int]``, it represents RGB values
            as integers between 0 and 255 (`e.g.`, ``(255, 0, 0)``).

    Returns:
        the color string used in ASS file.

    Example:
        >>> import movis as mv
        >>> mv.rgb_to_ass_color((255, 0, 0))
        '&H0000ff'
        >>> mv.rgb_to_ass_color('blue')
        '&Hff0000'
    """
    rgb_array = to_rgb(color)
    return "&H{:02x}{:02x}{:02x}".format(rgb_array[2], rgb_array[1], rgb_array[0])


def _make_ass_style_header():
    format_str = "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, " \
        "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, " \
        "ScaleX, ScaleY, Spacing, Angle, BorderStyle, " \
        "Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"
    return '\n'.join(["[V4+ Styles]", format_str])


def _make_ass_style(s: ASSStyleType):
    style_str = f'Style: {s.name},{s.font_name},{s.font_size},{s.primary_color},{s.secondary_color},' \
        f'{s.outline_color},{s.back_color},' \
        f'{int(s.bold)},{int(s.italic)},{int(s.underline)},{int(s.strike_out)},' \
        f'{s.scale_x},{s.scale_y},{s.spacing},{s.angle},{s.border_style},' \
        f'{s.outline},{s.shadow},2,{s.margin_l},{s.margin_r},{s.margin_v},1'
    return style_str


def write_ass_file(
    start_times: Sequence[float],
    end_times: Sequence[float],
    texts: Sequence[str],
    dst_ass_file: str | PathLike,
    size: tuple[int, int] = (1920, 1080),
    characters: Sequence[str] | None = None,
    styles: Sequence[ASSStyleType] | None = None,
) -> None:
    """Writes an ASS (Advanced SubStation Alpha) subtitle file.

    Args:
        start_times:
            A list of start times in seconds for each subtitle entry.
        end_times:
            A list of end times in seconds for each subtitle entry.
        texts:
            A list of text strings corresponding to each subtitle entry.
        dst_ass_file:
            The destination path for the generated ASS file.
        size:
            The resolution of the video, defaults to (1920, 1080).
        characters:
            A list of character names for each subtitle entry. Defaults to None.
        styles:
            A list of styles for each subtitle entry. Defaults to None.

    Examples:
        >>> import movis as mv
        >>> mv.write_ass_file(
        ...     start_times=[0.0, 1.0, 3.0],
        ...     end_times=[1.0, 3.0, 4.0],
        ...     texts=['Subtitle1', 'Subtitle2', 'Subtitle3'],
        ...     styles=[mv.ASSStyleType(font_size=96)],  # Set default font size
        ...     dst_ass_file='subtitle1.ass')
        >>> mv.write_ass_file(
        ...    start_times=[0.0, 1.0, 3.0],
        ...    end_times=[1.0, 3.0, 4.0],
        ...    texts=['text by Alice', 'text by Bob', 'text by Alice'],
        ...    characters=['Alice', 'Bob', 'Alice'],
        ...    styles=[
        ...        mv.ASSStyleType(name='Alice', primary_color=mv.rgb_to_ass_color('red')),
        ...        mv.ASSStyleType(name='Bob', primary_color=mv.rgb_to_ass_color('blue'))],
        ...    dst_ass_file='subtitle2.ass')
    """
    assert len(start_times) == len(end_times) == len(texts)

    if characters is None:
        characters = ['Default'] * len(texts)

    if styles is None:
        styles = [ASSStyleType()]

    ass_style_header = _make_ass_style_header()
    ass_style_body = '\n'.join([_make_ass_style(style) for style in styles])

    header = f"""[Script Info]
; Script generated by FFmpeg/Lavc60.14.101
ScriptType: v4.00+
PlayResX: {size[0]}
PlayResY: {size[1]}
ScaledBorderAndShadow: yes
YCbCr Matrix: None

{ass_style_header}
{ass_style_body}

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
"""
    line_template = "Dialogue: 0,{start_time},{end_time},{character},,0,0,0,,{text}"

    def get_time(t):
        return "{:02d}:{:02d}:{:02d}.{:02d}".format(
            int(t / 3600), int((t / 60) % 60), int(t % 60), int((t % 1) * 100)
        )

    lines = []
    for t0, t1, character, text in zip(start_times, end_times, characters, texts):
        text0, text1 = get_time(t0), get_time(t1)
        x = line_template.format(
            start_time=text0, end_time=text1, character=character, text=text)
        lines.append(x)
    body = "\n".join(lines)
    with open(dst_ass_file, "w") as fp:
        fp.write(header + body)


def write_srt_file(
        start_times: Sequence[float], end_times: Sequence[float], texts: Sequence[str],
        dst_srt_file: str | PathLike) -> None:
    """
    Writes an SRT (SubRip Text) subtitle file.

    Args:
        start_times:
            A list of start times in seconds for each subtitle entry.
        end_times:
            A list of end times in seconds for each subtitle entry.
        texts:
            A list of text strings corresponding to each subtitle entry.
        dst_srt_file:
            The destination path for the generated SRT file.
    """
    assert len(start_times) == len(end_times) == len(texts)
    with open(dst_srt_file, 'w') as srt:
        for i, (start_time, end_time, text) in enumerate(zip(start_times, end_times, texts)):
            srt.write('{}\n'.format(i + 1))
            srt.write('{:02d}:{:02d}:{:02d},{:03d} --> {:02d}:{:02d}:{:02d},{:03d}\n'.format(
                int(start_time / 3600), int((start_time / 60) % 60),
                int(start_time % 60), int((start_time % 1) * 1000),
                int(end_time / 3600), int((end_time / 60) % 60),
                int(end_time % 60), int((end_time % 1) * 1000),
            ))
            cleaned_text = text.replace(r"\n", "").replace("\n", "")
            srt.write(cleaned_text + '\n\n')
