"""
@package pmsco.helpers
helper classes

a collection of small and generic code bits mostly collected from the www.

"""

import contextlib
import ctypes
import io
import os
import sys
from typing import BinaryIO


class BraceMessage(object):
    """
    a string formatting proxy class useful for logging and exceptions.

    use BraceMessage("{0} {1}", "formatted", "message")
    in place of "{0} {1}".format("formatted", "message").
    the advantage is that the format method is called only if the string is actually used.
    """
    def __init__(self, fmt, *args, **kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return self.fmt.format(*self.args, **self.kwargs)


libc = ctypes.CDLL(None)
c_stdout = ctypes.c_void_p.in_dll(libc, 'stdout')


@contextlib.contextmanager
def stdout_redirected(dest_file: BinaryIO):
    """
    A context manager to temporarily redirect stdout to a file.

    Redirects all standard output from Python and the C library to the specified file.
    This can be used, e.g., to capture output from Fortran code.

    credit: https://eli.thegreenplace.net/2015/redirecting-all-kinds-of-stdout-in-python/

    @param dest_file: binary file open for writing ('wb' mode).
        This function requires just the fileno function.
    @return: None
    """

    original_stdout_fd = sys.stdout.fileno()

    def _redirect_stdout(to_fd):
        """Redirect stdout to the given file descriptor."""
        libc.fflush(c_stdout)
        sys.stdout.close()
        os.dup2(to_fd, original_stdout_fd)
        sys.stdout = io.TextIOWrapper(os.fdopen(original_stdout_fd, 'wb'))

    saved_stdout_fd = os.dup(original_stdout_fd)
    try:
        _redirect_stdout(dest_file.fileno())
        yield
        _redirect_stdout(saved_stdout_fd)
    finally:
        os.close(saved_stdout_fd)
