import os
import errno
import platform
import sys


def mkdir_p(path):
    """Create directory recursive

    :rtype: str | unicode
    :rtype: None
    """
    try:
        os.makedirs(path)
    except OSError as exc:
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else:
            raise


def safe_format(*args, **kwargs):
    """Fault-tolerant string format. Use this function for non-critical formatting, for example logging and reports.

    1) This function allows formatting binary data into user's messages.
    If some argument is a binary string and it can not be converted to unicode -
    it will be inserted as Python representation.
    2) This function makes code more fault-tolerant.
    If some argument is missing for format string, it will be left as is. No exception will be thrown.

    First positional argument is a format string. All the other arguments must be named arguments.

    Output is always an unicode string that could be logged, saved to report file, etc.

    :rtype: unicode
    """
    if len(args) == 0:
        return u''

    safe_kwargs = {
        k: safe_string_repr(v, omit_quotes=True) for k, v in kwargs.items()
    }

    # limit to avoid
    max_missing_keys = 100

    if sys.version_info.major >= 3:
        max_missing_keys_range = range(max_missing_keys)
        str_type = str
    else:
        max_missing_keys_range = xrange(max_missing_keys)
        str_type = unicode

    for _ in max_missing_keys_range:
        try:
            return str_type(args[0]).format(**safe_kwargs)
        except KeyError as e:
            missing_key = e.args[0]
            safe_kwargs[missing_key] = '{%s}' % missing_key


def safe_string_repr(s, omit_quotes=False):
    """If string can be interpreted as UTF-8, then return it as unicode string. Otherwise return its __repr__.
    Could be useful for safe logging, when usually you want to get just plain string, but if there are some issues
    with encoding - at least get its Python representation.

    :type s: str | unicode | object
    :type omit_quotes: bool
    :rtype: unicode
    """
    if sys.version_info.major >= 3:
        bytes_type = bytes
    else:
        bytes_type = basestring
    try:
        return u"%s" % (s,)
    except UnicodeError:
        if omit_quotes and isinstance(s, bytes_type):
            return (u"%r" % (s,))[1:-1]
        else:
            return u"%r" % (s,)


def is_run_on_windows():
    return platform.system() == 'Windows'
