from cpython cimport PyBuffer_FillInfo


cdef extern from "Python.h":
    bytes PyBytes_FromString(char*)


cdef class SubtitleProxy:
    def __dealloc__(self):
        lib.avsubtitle_free(&self.struct)


cdef class SubtitleSet:
    """
    A :class:`SubtitleSet` can contain many :class:`Subtitle` objects.
    """
    def __cinit__(self, SubtitleProxy proxy):
        self.proxy = proxy
        self.rects = tuple(build_subtitle(self, i) for i in range(self.proxy.struct.num_rects))

    def __repr__(self):
        return f"<{self.__class__.__module__}.{self.__class__.__name__} at 0x{id(self):x}>"

    @property
    def format(self): return self.proxy.struct.format
    @property
    def start_display_time(self): return self.proxy.struct.start_display_time
    @property
    def end_display_time(self): return self.proxy.struct.end_display_time
    @property
    def pts(self): return self.proxy.struct.pts

    def __len__(self):
        return len(self.rects)

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

    def __getitem__(self, i):
        return self.rects[i]


cdef Subtitle build_subtitle(SubtitleSet subtitle, int index):
    """Build an av.Stream for an existing AVStream.

    The AVStream MUST be fully constructed and ready for use before this is
    called.

    """

    if index < 0 or <unsigned int>index >= subtitle.proxy.struct.num_rects:
        raise ValueError("subtitle rect index out of range")
    cdef lib.AVSubtitleRect *ptr = subtitle.proxy.struct.rects[index]

    if ptr.type == lib.SUBTITLE_BITMAP:
        return BitmapSubtitle(subtitle, index)
    elif ptr.type == lib.SUBTITLE_ASS or ptr.type == lib.SUBTITLE_TEXT:
        return AssSubtitle(subtitle, index)
    else:
        raise ValueError("unknown subtitle type %r" % ptr.type)


cdef class Subtitle:
    """
    An abstract base class for each concrete type of subtitle.
    Wraps :ffmpeg:`AVSubtitleRect`
    """
    def __cinit__(self, SubtitleSet subtitle, int index):
        if index < 0 or <unsigned int>index >= subtitle.proxy.struct.num_rects:
            raise ValueError("subtitle rect index out of range")
        self.proxy = subtitle.proxy
        self.ptr = self.proxy.struct.rects[index]

        if self.ptr.type == lib.SUBTITLE_NONE:
            self.type = b"none"
        elif self.ptr.type == lib.SUBTITLE_BITMAP:
            self.type = b"bitmap"
        elif self.ptr.type == lib.SUBTITLE_TEXT:
            self.type = b"text"
        elif self.ptr.type == lib.SUBTITLE_ASS:
            self.type = b"ass"
        else:
            raise ValueError(f"unknown subtitle type {self.ptr.type!r}")

    def __repr__(self):
        return f"<{self.__class__.__module__}.{self.__class__.__name__} at 0x{id(self):x}>"


cdef class BitmapSubtitle(Subtitle):
    def __cinit__(self, SubtitleSet subtitle, int index):
        self.planes = tuple(
            BitmapSubtitlePlane(self, i)
            for i in range(4)
            if self.ptr.linesize[i]
        )

    def __repr__(self):
        return (
            f"<{self.__class__.__module__}.{self.__class__.__name__} "
            f"{self.width}x{self.height} at {self.x},{self.y}; at 0x{id(self):x}>"
        )

    @property
    def x(self): return self.ptr.x
    @property
    def y(self): return self.ptr.y
    @property
    def width(self): return self.ptr.w
    @property
    def height(self): return self.ptr.h
    @property
    def nb_colors(self): return self.ptr.nb_colors

    def __len__(self):
        return len(self.planes)

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

    def __getitem__(self, i):
        return self.planes[i]


cdef class BitmapSubtitlePlane:
    def __cinit__(self, BitmapSubtitle subtitle, int index):
        if index >= 4:
            raise ValueError("BitmapSubtitles have only 4 planes")
        if not subtitle.ptr.linesize[index]:
            raise ValueError("plane does not exist")

        self.subtitle = subtitle
        self.index = index
        self.buffer_size = subtitle.ptr.w * subtitle.ptr.h
        self._buffer = <void*>subtitle.ptr.data[index]

    # New-style buffer support.
    def __getbuffer__(self, Py_buffer *view, int flags):
        PyBuffer_FillInfo(view, self, self._buffer, self.buffer_size, 0, flags)


cdef class AssSubtitle(Subtitle):
    """
    Represents an ASS/Text subtitle format, as opposed to a bitmap Subtitle format.
    """
    def __repr__(self):
        return (
            f"<{self.__class__.__module__}.{self.__class__.__name__} "
            f"{self.text!r} at 0x{id(self):x}>"
        )

    @property
    def ass(self):
        """
        Returns the subtitle in the ASS/SSA format. Used by the vast majority of subtitle formats.
        """
        if self.ptr.ass is not NULL:
            return PyBytes_FromString(self.ptr.ass)
        return b""

    @property
    def dialogue(self):
        """
        Extract the dialogue from the ass format. Strip comments.
        """
        comma_count = 0
        i = 0
        cdef bytes ass_text = self.ass
        cdef bytes result = b""

        while comma_count < 8 and i < len(ass_text):
            if bytes([ass_text[i]]) == b",":
                comma_count += 1
            i += 1

        state = False
        while i < len(ass_text):
            char = bytes([ass_text[i]])
            next_char = b"" if i + 1 >= len(ass_text) else bytes([ass_text[i + 1]])

            if char == b"\\" and next_char == b"N":
                result += b"\n"
                i += 2
                continue

            if not state:
                if char == b"{" and next_char != b"\\":
                    state = True
                else:
                    result += char
            elif char == b"}":
                state = False
            i += 1

        return result

    @property
    def text(self):
        """
        Rarely used attribute. You're probably looking for dialogue.
        """
        if self.ptr.text is not NULL:
            return PyBytes_FromString(self.ptr.text)
        return b""
