import ctypes

from . import vorbis
from .audio_file import AudioFile
from .pyogg_error import PyOggError

# TODO: Issue #70: Vorbis files with multiple logical bitstreams could
# be supported by chaining VorbisFile instances (with say a 'next'
# attribute that points to the next VorbisFile that would contain the
# PCM for the next logical bitstream).  A considerable constraint to
# implementing this was that examples files that demonstrated multiple
# logical bitstreams couldn't be found or created.  Note that even
# Audacity doesn't handle multiple logical bitstreams (see
# https://wiki.audacityteam.org/wiki/OGG#Importing_multiple_stream_files).

# TODO: Issue #53: Unicode file names are not well supported.
# They may work in macOS and Linux, they don't work under Windows.

class VorbisFile(AudioFile):
    def __init__(self,
                 path: str,
                 bytes_per_sample: int = 2,
                 signed:bool = True) -> None:
        """Load an OggVorbis File.
        
        path specifies the location of the Vorbis file.  Unicode
        filenames may not work correctly under Windows.

        bytes_per_sample specifies the word size of the PCM.  It may
        be either 1 or 2.  Specifying one byte per sample will save
        memory but will likely decrease the quality of the decoded
        audio.

        Only Vorbis files with a single logical bitstream are
        supported.

        """
        # Sanity check the number of bytes per sample
        assert bytes_per_sample==1 or bytes_per_sample==2

        # Sanity check that the vorbis library is available (for mypy)
        assert vorbis.libvorbisfile is not None
        
        #: Bytes per sample
        self.bytes_per_sample = bytes_per_sample

        #: Samples are signed (rather than unsigned)
        self.signed = signed

        # Create a Vorbis File structure
        vf = vorbis.OggVorbis_File()

        # Attempt to open the Vorbis file
        error = vorbis.libvorbisfile.ov_fopen(
            vorbis.to_char_p(path),
            ctypes.byref(vf)
        )

        # Check for errors during opening
        if error != 0:
            raise PyOggError(
                ("File '{}' couldn't be opened or doesn't exist. "+
                 "Error code : {}").format(path, error)
            )

        # Extract info from the Vorbis file
        info = vorbis.libvorbisfile.ov_info(
            ctypes.byref(vf),
            -1 # the current logical bitstream
        )

        #: Number of channels in audio file.
        self.channels = info.contents.channels

        #: Number of samples per second (per channel), 44100 for
        #  example.
        self.frequency = info.contents.rate

        # Extract the total number of PCM samples for the first
        # logical bitstream
        pcm_length_samples = vorbis.libvorbisfile.ov_pcm_total(
            ctypes.byref(vf),
            0 # to extract the length of the first logical bitstream
        )

        # Create a memory block to store the entire PCM
        Buffer = (
            ctypes.c_char
            * (
                pcm_length_samples
                * self.bytes_per_sample
                * self.channels
            )
        )
        self.buffer = Buffer()

        # Create a pointer to the newly allocated memory.  It
        # seems we can only do pointer arithmetic on void
        # pointers.  See
        # https://mattgwwalker.wordpress.com/2020/05/30/pointer-manipulation-in-python/
        buf_ptr = ctypes.cast(
            ctypes.pointer(self.buffer),
            ctypes.c_void_p
        )

        # Storage for the index of the logical bitstream
        bitstream_previous = None 
        bitstream = ctypes.c_int()

        # Set bytes remaining to read into PCM
        read_size = len(self.buffer)

        while True:
            # Convert buffer pointer to the desired type
            ptr = ctypes.cast(
                buf_ptr,
                ctypes.POINTER(ctypes.c_char)
            )

            # Attempt to decode PCM from the Vorbis file
            result = vorbis.libvorbisfile.ov_read(
                ctypes.byref(vf),
                ptr,
                read_size,
                0, # Little endian
                self.bytes_per_sample,
                int(self.signed),
                ctypes.byref(bitstream)
            )

            # Check for errors
            if result < 0:
                raise PyOggError(
                    "An error occurred decoding the Vorbis file: "+
                    f"Error code: {result}"
                )

            # Check that the bitstream hasn't changed as we only
            # support Vorbis files with a single logical bitstream.
            if bitstream_previous is None:
                bitstream_previous = bitstream
            else:
                if bitstream_previous != bitstream:
                    raise PyOggError(
                        "PyOgg currently supports Vorbis files "+
                        "with only one logical stream" 
                    )
                    
            # Check for end of file
            if result == 0:
                break

            # Calculate the number of bytes remaining to read into PCM
            read_size -= result

            # Update the pointer into the buffer
            buf_ptr.value += result
            

        # Close the file and clean up memory
        vorbis.libvorbisfile.ov_clear(ctypes.byref(vf))