# Liblouis Python ctypes bindings
#
# Copyright (C) 2009-2010 James Teh <jamie@jantrid.net>
# Copyright (C) 2010-2012 Michael Whapples <mwhapples@gmail.com>
# Copyright (C) 2012-2024 Christian Egli <christian.egli@sbs.ch>
# Copyright (C) 2015-2016 Davy Kager <mail@davykager.nl>
# Copyright (C) 2016 matt venn <matt@mattvenn.net>
# Copyright (C) 2018-2025 Leonard de Ruijter <alderuijter@gmail.com>
# Copyright (C) 2018 Bue Vester-Andersen <bue@vester-andersen.dk>
# Copyright (C) 2019 Dave Mielke <dave@mielke.cc>
# Copyright (C) 2019-2020 André-Abush Clause <dev@andreabc.net>
# Copyright (C) 2019 Bert Frees <bertfrees@gmail.com>
# Copyright (C) 2023 Rob Beezer <beezer@ups.edu>
# Copyright (C) 2024 Daniel Garcia Moreno <daniel.garcia@suse.com>
#
# This file is part of liblouis.
#
# liblouis is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 2.1 of the License, or
# (at your option) any later version.
#
# liblouis is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with liblouis. If not, see <http://www.gnu.org/licenses/>.
#

"""Liblouis Python ctypes bindings
These bindings allow you to use the liblouis braille translator and back-translator library
from within Python.
This documentation is only related to the Python helper.
Please see the liblouis documentation for more information.

Most of these functions take a C{tableList}      argument which specifies
a list of translation tables to use. Please see the liblouis documentation
concerning the C{tableList} parameter to the C{lou_translateString}
function for information about how liblouis searches for these tables.

:author: Michael Curran <mick@kulgan.net>
:author: James Teh <jamie@jantrid.net>
:author: Eitan Isaacson <eitan@ascender.com>
:author: Michael Whapples <mwhapples@aim.com>
:author: Davy Kager <mail@davykager.nl>
:author: Leonard de Ruijter <alderuijter@gmail.com>
:author: Babbage B.V. <info@babbage.com>
:author: Andre-Abush Clause <dev@andreabc.net>
"""

from atexit import register
from collections.abc import Generator, Iterable, Sequence
from ctypes import (
    Array,
    CFUNCTYPE,
    POINTER,
    c_char,
    c_char_p,
    c_int,
    c_ushort,
    cdll,
    create_string_buffer,
    string_at,
)
from sys import (
    byteorder,
    getdefaultencoding,
    getfilesystemencoding,
    platform,
)

try:  # Native win32
    from ctypes import WINFUNCTYPE, windll

    _loader, _functype = windll, WINFUNCTYPE
except ImportError:  # Unix/Cygwin
    _loader, _functype = cdll, CFUNCTYPE
liblouis = _loader["liblouis.so.20"]
_is_windows = platform == "win32"
_endianness = "be" if byteorder == "big" else "le"

# { Module Configuration
wideCharBytes: int = liblouis.lou_charSize()
"""
Specifies the charSize (in bytes) used by liblouis.
This is fetched once using `liblouis.lou_charSize`.
Call it directly, since `charSize` is not yet defined.
"""
outlenMultiplier: int = 4 + wideCharBytes * 2
"""
Specifies the number by which the input length should be multiplied
to calculate the maximum output length.
The default will handle the case where every input character is
undefined in the translation table.
"""
fileSystemEncoding: str = "mbcs" if _is_windows else getfilesystemencoding()
"""Specifies the encoding to use when encode/decode file/dir name."""
conversionEncoding: str = "utf_%d_%s" % (wideCharBytes * 8, _endianness)
"""Specifies the encoding to use when converting from byte strings to unicode strings."""


# Some general utility functions
TableListT = list[str | bytes]


def _createTableBuf(tablesList: TableListT) -> Array[c_char]:
    """Creates a tables string for liblouis calls"""
    return create_string_buffer(
        b",".join(
            [
                x.encode(fileSystemEncoding) if isinstance(x, str) else bytes(x)
                for x in tablesList
            ]
        )
    )


def _createTypeformbuf(
    length: int,
    typeform: Iterable[int] | None = None,
) -> Array[c_ushort]:
    """Creates a typeform buffer for liblouis calls"""
    return (c_ushort * length)(*typeform) if typeform else (c_ushort * length)()


ENCODING_ERROR_HANDLER = "surrogatepass"


def _createEncodedBuf(
    x: str,
    encoding: str = conversionEncoding,
    errors: str = ENCODING_ERROR_HANDLER,
) -> Array[c_char]:
    return create_string_buffer(x.encode(encoding, errors))


def _processTableFiles(stringArray: Sequence[bytes] | None) -> Generator[str]:
    if not stringArray:
        return
    i = 0
    while stringArray[i] is not None:
        yield stringArray[i].decode(fileSystemEncoding, errors=ENCODING_ERROR_HANDLER)
        i += 1
    liblouis.lou_freeTableFiles(stringArray)


def _createQueryStringBuf(dct: dict[str, str]) -> Array[c_char]:
    """Create a query string from a dictionary"""
    if not isinstance(dct, dict):
        raise TypeError("Query should be a dictionary")
    return create_string_buffer(
        " ".join(
            f"{k}:{v}"
            for k, v in dct.items()
            if isinstance(k, str) and isinstance(v, str)
        ).encode(getdefaultencoding(), errors=ENCODING_ERROR_HANDLER)
    )


register(liblouis.lou_free)

liblouis.lou_version.restype = c_char_p
liblouis.lou_version.argtypes = ()

liblouis.lou_charSize.restype = c_int
liblouis.lou_charSize.argtypes = ()

liblouis.lou_translateString.restype = c_int
liblouis.lou_translateString.argtypes = (
    c_char_p,
    POINTER(c_char),
    POINTER(c_int),
    POINTER(c_char),
    POINTER(c_int),
    POINTER(c_ushort),
    POINTER(c_char),
    c_int,
)

liblouis.lou_translate.restype = c_int
liblouis.lou_translate.argtypes = (
    c_char_p,
    POINTER(c_char),
    POINTER(c_int),
    POINTER(c_char),
    POINTER(c_int),
    POINTER(c_ushort),
    POINTER(c_char),
    POINTER(c_int),
    POINTER(c_int),
    POINTER(c_int),
    c_int,
)

liblouis.lou_backTranslateString.restype = c_int
liblouis.lou_backTranslateString.argtypes = (
    c_char_p,
    POINTER(c_char),
    POINTER(c_int),
    POINTER(c_char),
    POINTER(c_int),
    POINTER(c_ushort),
    POINTER(c_char),
    c_int,
)

liblouis.lou_backTranslate.restype = c_int
liblouis.lou_backTranslate.argtypes = (
    c_char_p,
    POINTER(c_char),
    POINTER(c_int),
    POINTER(c_char),
    POINTER(c_int),
    POINTER(c_ushort),
    POINTER(c_char),
    POINTER(c_int),
    POINTER(c_int),
    POINTER(c_int),
    c_int,
)

liblouis.lou_hyphenate.restype = c_int
liblouis.lou_hyphenate.argtypes = (
    c_char_p,
    POINTER(c_char),
    c_int,
    POINTER(c_char),
    c_int,
)

liblouis.lou_checkTable.restype = c_int
liblouis.lou_checkTable.argtypes = (c_char_p,)

liblouis.lou_compileString.restype = c_int
liblouis.lou_compileString.argtypes = (c_char_p, c_char_p)

liblouis.lou_getTypeformForEmphClass.restype = c_ushort
liblouis.lou_getTypeformForEmphClass.argtypes = (c_char_p, c_char_p)

liblouis.lou_dotsToChar.restype = c_int
liblouis.lou_dotsToChar.argtypes = (
    c_char_p,
    POINTER(c_char),
    POINTER(c_char),
    c_int,
    c_int,
)

liblouis.lou_charToDots.restype = c_int
liblouis.lou_charToDots.argtypes = (
    c_char_p,
    POINTER(c_char),
    POINTER(c_char),
    c_int,
    c_int,
)

LogCallback = _functype(None, c_int, c_char_p)

liblouis.lou_registerLogCallback.restype = None
liblouis.lou_registerLogCallback.argtypes = (LogCallback,)

liblouis.lou_setLogLevel.restype = None
liblouis.lou_setLogLevel.argtypes = (c_int,)

liblouis.lou_findTable.restype = POINTER(c_char)
liblouis.lou_findTable.argtypes = (c_char_p,)

liblouis.lou_freeTableFile.restype = None
liblouis.lou_freeTableFile.argtypes = (POINTER(c_char),)

liblouis.lou_findTables.restype = POINTER(c_char_p)
liblouis.lou_findTables.argtypes = (c_char_p,)

liblouis.lou_freeTableFiles.restype = None
liblouis.lou_freeTableFiles.argtypes = (POINTER(c_char_p),)

liblouis.lou_getTableInfo.restype = POINTER(c_char)
liblouis.lou_getTableInfo.argtypes = (c_char_p, c_char_p)

liblouis.lou_freeTableInfo.restype = None
liblouis.lou_freeTableInfo.argtypes = (liblouis.lou_getTableInfo.restype,)

liblouis.lou_listTables.restype = POINTER(c_char_p)
liblouis.lou_listTables.argtypes = ()

liblouis.lou_free.restype = None
liblouis.lou_free.argtypes = ()


def version() -> str:
    """Obtain version information for liblouis.
    :return: The version of liblouis, plus other information, such as
        the release date and perhaps notable changes.
    """
    return liblouis.lou_version().decode("ASCII")


def charSize() -> int:
    """Obtain charSize information for liblouis.
    :return: The size of the widechar with which liblouis was compiled.
    """
    return liblouis.lou_charSize()


def translate(
    tableList: TableListT,
    inbuf: str,
    typeform: list[int] | None = None,
    cursorPos: int = 0,
    mode: int = 0,
) -> tuple[str, list[int], list[int], int]:
    """Translate a string of characters, providing position information.
    :param tableList: A list of translation tables.
    :param inbuf: The string to translate.
    :param typeform: A list of typeform constants indicating the typeform for each position in inbuf,
        `None` for no typeform information.
    :param cursorPos: The position of the cursor in inbuf.
    :param mode: The translation mode; add multiple values for a combined mode.
    :return: A tuple of: the translated string,
        a list of input positions for each position in the output,
        a list of output positions for each position in the input, and
        the position of the cursor in the output.
    :raise RuntimeError: If a complete translation could not be done.
    :see: lou_translate in the liblouis documentation
    """
    tableBuf: Array[c_char] = _createTableBuf(tableList)
    _inbuf = _createEncodedBuf(inbuf)
    inlen = c_int(len(_inbuf) // wideCharBytes)
    outlen = c_int(inlen.value * outlenMultiplier)
    outbuf = create_string_buffer(outlen.value * wideCharBytes)
    typeformbuf = None
    if typeform:
        typeformbuf = _createTypeformbuf(outlen.value, typeform)
    inPos = (c_int * outlen.value)()
    outPos = (c_int * inlen.value)()
    _cursorPos = c_int(cursorPos)
    if not liblouis.lou_translate(
        tableBuf,
        _inbuf,
        inlen,
        outbuf,
        outlen,
        typeformbuf,
        None,
        outPos,
        inPos,
        _cursorPos,
        mode,
    ):
        raise RuntimeError(
            f"Can't translate: tables {tableList!r}, inbuf {inbuf!r}, typeform {typeform!r}, cursorPos {cursorPos!r}, mode {mode!r}"
        )
    if isinstance(typeform, list) and typeformbuf is not None:
        typeform[:] = list(typeformbuf)
    return (
        outbuf.raw[: outlen.value * wideCharBytes].decode(
            conversionEncoding,
            errors=ENCODING_ERROR_HANDLER,
        ),
        inPos[: outlen.value],
        outPos[: inlen.value],
        _cursorPos.value,
    )


def translateString(
    tableList: TableListT,
    inbuf: str,
    typeform: list[int] | None = None,
    mode: int = 0,
) -> str:
    """Translate a string of characters.
    :param tableList: A list of translation tables.
    :param inbuf: The string to translate.
    :param typeform: A list of typeform constants indicating the typeform for each position in inbuf,
        `None` for no typeform information.
    :param mode: The translation mode; add multiple values for a combined mode.
    :return: The translated string.
    :raise RuntimeError: If a complete translation could not be done.
    :see: lou_translateString in the liblouis documentation
    """
    tableBuf = _createTableBuf(tableList)
    _inbuf = _createEncodedBuf(inbuf)
    inlen = c_int(len(_inbuf) // wideCharBytes)
    outlen = c_int(inlen.value * outlenMultiplier)
    outbuf = create_string_buffer(outlen.value * wideCharBytes)
    typeformbuf = None
    if typeform:
        typeformbuf = _createTypeformbuf(outlen.value, typeform)
    if not liblouis.lou_translateString(
        tableBuf,
        _inbuf,
        inlen,
        outbuf,
        outlen,
        typeformbuf,
        None,
        mode,
    ):
        raise RuntimeError(
            f"Can't translate: tables {tableList}, inbuf {inbuf}, typeform {typeform}, mode {mode}"
        )
    if isinstance(typeform, list) and typeformbuf is not None:
        typeform[:] = list(typeformbuf)
    return outbuf.raw[: outlen.value * wideCharBytes].decode(
        conversionEncoding,
        errors=ENCODING_ERROR_HANDLER,
    )


def backTranslate(
    tableList: TableListT,
    inbuf: str,
    typeform: list[int] | None = None,
    cursorPos: int = 0,
    mode: int = 0,
) -> tuple[str, list[int], list[int], int]:
    """Back translates a string of characters, providing position information.
    :param tableList: A list of translation tables.
    :param inbuf: Braille to back translate.
    :param typeform: List where typeform constants will be placed.
    :param cursorPos: Position of cursor.
    :param mode: Translation mode.
    :return: A tuple: A string of the back translation,
        a list of input positions for each position in the output,
        a list of the output positions for each position in the input and
        the position of the cursor in the output.
    :raise RuntimeError: If a complete back translation could not be done.
    :see: lou_backTranslate in the liblouis documentation.
    """
    tableBuf = _createTableBuf(tableList)
    _inbuf = _createEncodedBuf(inbuf)
    inlen = c_int(len(_inbuf) // wideCharBytes)
    outlen = c_int(inlen.value * outlenMultiplier)
    outbuf = create_string_buffer(outlen.value * wideCharBytes)
    typeformbuf = None
    if isinstance(typeform, list):
        typeformbuf = _createTypeformbuf(outlen.value)
    inPos = (c_int * outlen.value)()
    outPos = (c_int * inlen.value)()
    _cursorPos = c_int(cursorPos)
    if not liblouis.lou_backTranslate(
        tableBuf,
        _inbuf,
        inlen,
        outbuf,
        outlen,
        typeformbuf,
        None,
        outPos,
        inPos,
        _cursorPos,
        mode,
    ):
        raise RuntimeError(
            f"Can't back translate: tables {tableList!r}, inbuf {inbuf!r}, typeform {typeform!r}, cursorPos {cursorPos!r}, mode {mode!r}"
        )
    if isinstance(typeform, list) and typeformbuf is not None:
        typeform[:] = list(typeformbuf)
    return (
        outbuf.raw[: outlen.value * wideCharBytes].decode(
            conversionEncoding,
            errors=ENCODING_ERROR_HANDLER,
        ),
        inPos[: outlen.value],
        outPos[: inlen.value],
        _cursorPos.value,
    )


def backTranslateString(
    tableList: TableListT,
    inbuf: str,
    typeform: list[int] | None = None,
    mode: int = 0,
) -> str:
    """Back translate from Braille.
    :param tableList: A list of translation tables.
    :param inbuf: The Braille to back translate.
    :param typeform: List for typeform constants to be put in.
        If you don't want typeform data then give `None`
    :param mode: The translation mode
    :return: The back translation of inbuf.
    :raise RuntimeError: If a complete back translation could not be done.
    :see: lou_backTranslateString in the liblouis documentation.
    """
    tableBuf = _createTableBuf(tableList)
    _inbuf = _createEncodedBuf(inbuf)
    inlen = c_int(len(_inbuf) // wideCharBytes)
    outlen = c_int(inlen.value * outlenMultiplier)
    outbuf = create_string_buffer(outlen.value * wideCharBytes)
    typeformbuf = None
    if isinstance(typeform, list):
        typeformbuf = _createTypeformbuf(outlen.value)
    if not liblouis.lou_backTranslateString(
        tableBuf,
        _inbuf,
        inlen,
        outbuf,
        outlen,
        typeformbuf,
        None,
        mode,
    ):
        raise RuntimeError(
            f"Can't back translate: tables {tableList!r}, inbuf {inbuf!r}, mode {mode!r}"
        )
    if isinstance(typeform, list) and typeformbuf is not None:
        typeform[:] = list(typeformbuf)
    return outbuf.raw[: outlen.value * wideCharBytes].decode(
        conversionEncoding,
        errors=ENCODING_ERROR_HANDLER,
    )


def hyphenate(tableList: TableListT, inbuf: str, mode: int = 0) -> str:
    """Get information for hyphenation.
    :param tableList: A list of translation tables and hyphenation
        dictionaries.
    :param inbuf: The text to get hyphenation information about.
        This should be a single word and leading/trailing whitespace
        and punctuation is ignored.
    :param mode: Lets liblouis know if inbuf is plain text or Braille.
        Set to 0 for text and anyother value for Braille.
    :return: A string with '1' at the beginning of every syllable
        and '0' elsewhere.
    :raise RuntimeError: If hyphenation data could not be produced.
    :see: lou_hyphenate in the liblouis documentation.
    """
    tableBuf = _createTableBuf(tableList)
    _inbuf = _createEncodedBuf(inbuf)
    inlen = c_int(len(_inbuf) // wideCharBytes)
    hyphen_string = create_string_buffer(inlen.value + 1)
    if not liblouis.lou_hyphenate(
        tableBuf,
        _inbuf,
        inlen,
        hyphen_string,
        mode,
    ):
        raise RuntimeError(
            f"Can't hyphenate: tables {tableList!r}, inbuf {inbuf!r}, mode {mode!r}"
        )
    return hyphen_string.value.decode("ASCII")


def checkTable(tableList: TableListT) -> None:
    """Check if the specified tables can be found and compiled.
        This can be used to check if a list of tables contains errors
        before sending it to other liblouis functions
        that accept a list of tables.
    :param tableList: A list of translation tables.
    :raise RuntimeError: If compilation failed.
    :see: lou_checkTable in the liblouis documentation
    """
    tableBuf = _createTableBuf(tableList)
    if not liblouis.lou_checkTable(tableBuf):
        raise RuntimeError(f"Can't compile: tables {tableList}")


def compileString(tableList: TableListT, inString: str) -> None:
    """Compile a table entry on the fly at run-time.
    :param tableList: A list of translation tables.
    :param inString: The table entry to be added.
    :raise RuntimeError: If compilation of the entry failed.
    :see: lou_compileString in the liblouis documentation
    """
    tableBuf = _createTableBuf(tableList)
    inbuf = create_string_buffer(
        inString.encode("ASCII") if isinstance(inString, str) else bytes(inString)
    )
    if not liblouis.lou_compileString(tableBuf, inbuf):
        raise RuntimeError(
            f"Can't compile entry: tables {tableList}, inString {inString}"
        )


def getTypeformForEmphClass(tableList: TableListT, emphClass: str) -> int:
    """Get the typeform bit for the named emphasis class.
    :param tableList: A list of translation tables.
    :param emphClass: An emphasis class name.
    :see: lou_getTypeformForEmphClass in the liblouis documentation
    """
    tableBuf = _createTableBuf(tableList)
    bEmphClass = emphClass.encode("ASCII")
    return liblouis.lou_getTypeformForEmphClass(tableBuf, bEmphClass)


def dotsToChar(tableList: TableListT, inbuf: str) -> str:
    """Convert a string of dot patterns to a string of characters according to the specifications in tableList.
    :param tableList: A list of translation tables.
    :param inbuf: a string of dot patterns, either in liblouis format or Unicode braille.
    :raise RuntimeError: If a complete conversion could not be done.
    :see: lou_dotsToChar in the liblouis documentation
    """
    tableBuf = _createTableBuf(tableList)
    _inbuf = _createEncodedBuf(inbuf)
    length = c_int(len(inbuf) // wideCharBytes)
    outbuf = create_string_buffer(length.value * wideCharBytes)
    if not liblouis.lou_dotsToChar(tableBuf, _inbuf, outbuf, length, 0):
        raise RuntimeError(
            f"Can't convert dots to char: tables {tableList}, inbuf {inbuf!r}"
        )
    return outbuf.raw[: length.value * wideCharBytes].decode(
        conversionEncoding,
        errors=ENCODING_ERROR_HANDLER,
    )


def charToDots(tableList: TableListT, inbuf: str, mode: int = 0) -> str:
    """Convert a string of characterss to a string of dot patterns according to the specifications in tableList.
    :param tableList: A list of translation tables.
    :param inbuf: a string of characters.
    :param mode: The translation mode; add multiple values for a combined mode.
    :raise RuntimeError: If a complete conversion could not be done.
    :see: lou_charToDots in the liblouis documentation
    """
    tableBuf = _createTableBuf(tableList)
    _inbuf = _createEncodedBuf(inbuf)
    length = c_int(len(inbuf) // wideCharBytes)
    outbuf = create_string_buffer(length.value * wideCharBytes)
    if not liblouis.lou_charToDots(tableBuf, _inbuf, outbuf, length, mode):
        raise RuntimeError(
            f"Can't convert char to dots: tables {tableList!r}, inbuf {inbuf!r}, mode {mode!r}"
        )
    return outbuf.raw[: length.value * wideCharBytes].decode(
        conversionEncoding,
        errors=ENCODING_ERROR_HANDLER,
    )


def registerLogCallback(logCallback) -> None:
    """Register logging callbacks.
    Set to `None` for default callback.
    :param logCallback: The callback to use.
        The callback must take two arguments:
        :param level: The log level on which a message is logged.
        :type level: int
        :param message: The logged message.
            Note that the callback should provide its own UTF-8 decoding routine.
        :type message: bytes

        Example callback:

        @louis.LogCallback
        def incomingLouisLog(level, message):
            print("Message %s logged at level %d" % (message.decode("ASCII"), level))

    @type logCallback: `LogCallback`
    """
    if logCallback is None:
        logCallback = LogCallback(0)
    elif not isinstance(logCallback, LogCallback):
        raise TypeError(
            f"logCallback should be of type {LogCallback.__name__} or NoneType"
        )
    return liblouis.lou_registerLogCallback(logCallback)


def setLogLevel(level: int) -> None:
    """Set the level for logging callback to be called at.
    :param level: one of the C{LOG_*} constants.
    :raise ValueError: If an invalid log level is provided.
    """
    if level not in logLevels:
        raise ValueError("Level %d is an invalid log level" % level)
    return liblouis.lou_setLogLevel(level)


def findTable(query: dict[str, str]) -> str | None:
    """Find the best match for a query

    Returns the name of the table, or None when no match can be
    found. An error message is given when the query is invalid. Freeing.
    :param query: The query to lookup.
    """
    queryStr = _createQueryStringBuf(query)
    result = liblouis.lou_findTable(queryStr)
    if not result:
        return None
    string = string_at(result).decode(fileSystemEncoding, errors=ENCODING_ERROR_HANDLER)
    liblouis.lou_freeTableFile(result)
    return string


def findTables(query: dict[str, str]) -> list[str]:
    """Find all matches for a query, best match first.

    Returns the names of the matched tables.
    An error message is given when the query is invalid.
    :param query: The query to lookup.
    """
    queryStr = _createQueryStringBuf(query)
    result = liblouis.lou_findTables(queryStr)
    return list(_processTableFiles(result))


def getTableInfo(tableName: str, key: str) -> str | None:
    """Read metadata from a file.

    Returns the value of the first occurring metadata field specified by
    `key' in `table', or None when the field does not exist.
    """
    bTableName = _createEncodedBuf(tableName, fileSystemEncoding)
    bKey = _createEncodedBuf(key, getdefaultencoding())
    result = liblouis.lou_getTableInfo(bTableName, bKey)
    if not result:
        return None
    string = string_at(result).decode(errors=ENCODING_ERROR_HANDLER)
    liblouis.lou_freeTableInfo(result)
    return string


def listTables() -> list[str]:
    """List available tables.

    Returns the names of available tables as list  of strings.
    Only tables that are discoverable, i.e. the have active metadata, are listed.
    """
    result = liblouis.lou_listTables()
    return list(_processTableFiles(result))


# { Typeforms
plain_text = 0x0000
emph_1 = comp_emph_1 = italic = 0x0001
emph_2 = comp_emph_2 = underline = 0x0002
emph_3 = comp_emph_3 = bold = 0x0004
emph_4 = 0x0008
emph_5 = 0x0010
emph_6 = 0x0020
emph_7 = 0x0040
emph_8 = 0x0080
emph_9 = 0x0100
emph_10 = 0x0200
computer_braille = 0x0400
no_translate = 0x0800
no_contract = 0x1000
# }

# { Translation modes
noContractions = 1
compbrlAtCursor = 2
dotsIO = 4
compbrlLeftCursor = 32
ucBrl = 64
noUndefined = 128
noUndefinedDots = noUndefined  # alias for backward compatiblity
partialTrans = 256
# }

# { logLevels
LOG_ALL = 0
LOG_DEBUG = 10000
LOG_INFO = 20000
LOG_WARN = 30000
LOG_ERROR = 40000
LOG_FATAL = 50000
LOG_OFF = 60000
# }

logLevels = (LOG_ALL, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL, LOG_OFF)

if __name__ == "__main__":
    # Just some common tests.
    print(version())
    print(translate([b"../tables/en-us-g2.ctb"], "Hello world!", cursorPos=5))
