# -*- coding: utf-8 -*-
#
# miditk/smf/converters.py
#
"""Functions for reading and writing the special data types in a MIDI file."""

__all__ = (
    'read_bew',
    'read_varlen',
    'sizeof_varlen',
    'tointseq',
    'tobytestr',
    'write_bew',
    'write_varlen'
)

try:
    from ._speedups import (read_bew, read_varlen, sizeof_varlen, tobytestr,  # noqa: F401
                            tointseq, write_bew, write_varlen)  # noqa: F401
except ImportError:
    # _speedups module not available
    from struct import pack, unpack

    from six import iterbytes, text_type

    if isinstance(b'', str):
        def _tobytes(*values):
            return "".join(chr(c) for c in values)
    else:
        def _tobytes(*values):
            return bytes(values)

    def _ord(x):
        return x if isinstance(x, int) else ord(x)

    def read_bew(value):
        """Read byte string as big endian word.

        ::

            >>> long(read_bew(b'aáâã'))
            1642193635L
            >>> read_bew(b'aá')
            25057

        """
        lval = len(value)
        if lval == 1:
            return _ord(value)
        elif lval == 2:
            return unpack('>H', value)[0]
        else:
            return unpack('>L', value)[0]

    # Variable Length Data (varlen) is a data format sprayed liberally throughout
    # a midi file. It can be anywhere from 1 to 4 bytes long.
    # If the 8'th bit is set in a byte another byte follows. The value is stored
    # in the lowest 7 bits of each byte. So max value is 4x7 bits = 28 bits.

    def read_varlen(value):
        """Convert variable length data format to integer.

        Just pass it 0 or more chars that might be a varlen and it will only use
        the relevant chars.

        Use sizeof_varlen(value) to see how many bytes the integer value takes.

        ::

            >>> read_varlen(b'@')
            64
            >>> read_varlen(b'áâãa')
            205042145

        """
        sum = 0
        for byte in iterbytes(value):
            sum = (sum << 7) + (byte & 0x7F)

            if not 0x80 & byte:
                # stop after last byte with high bit set
                break

        return sum

    def sizeof_varlen(value):
        """Return number of bytes an integer will need when converted to varlength."""
        if value <= 127:
            return 1
        elif value <= 16383:
            return 2
        elif value <= 2097151:
            return 3
        else:
            return 4

    def tobytestr(value, encoding='latin1'):
        if isinstance(value, text_type):
            value = value.encode(encoding)
        elif isinstance(value, (list, tuple)):
            value = _tobytes(*value)
        return value

    def tointseq(value, encoding='latin1'):
        """Convert a bytes/str/unicode instance into a tuple of int byte values."""
        if isinstance(value, text_type):
            value = value.encode(encoding)
        return tuple(_ord(c) for c in value)

    def write_bew(value, length):
        """Write int as big endian formatted bytes string.

        Difficult to print the result in doctest, so do a simple roundabout test:

        >>> read_bew(write_bew(25057, 2))
        25057
        >>> long(read_bew(write_bew(1642193635L, 4)))
        1642193635L

        """
        if length == 1:
            return _tobytes(value)
        elif length == 2:
            return pack('>H', value)
        else:
            return pack('>L', value)

    def write_varlen(value):
        """Convert an integer to varlength format."""
        nbytes = sizeof_varlen(value)
        if nbytes == 1:
            return _tobytes(value)
        else:
            return b"".join(_tobytes(((value >> ((i - 1) * 7)) | 0x80) & (0xFF if i > 1 else 0x7F))
                            for i in range(nbytes, 0, -1))
