import six
import re


def _escape_non_ascii(unicode_obj):
    """
    Escape non-printable (or non-ASCII) characters using Java-compatible Unicode escape sequences.

    This function is based on code from the JSON library module shipped with Python 2.7.3
    (json/encoder.py, function py_encode_basestring_ascii), which is Copyright (c) 2001, 2002, 2003,
    2004, 2005, 2006 Python Software Foundation; All Rights Reserved. See the file LICENSE included
    with PyjProperties for the full license terms. If that file is not available, then please see:
    https://www.python.org/download/releases/2.7.3/license/

    Differences to the aforementioned original version of py_encode_basestring_ascii():
      - Always tries to decode str objects as UTF-8, even if they don't contain any UTF-8
        characters.  This is so that we always return an unicode object.
      - Only processes non-printable or non-ASCII characters. Also _always_ replaces these
        characters with Java-compatible Unicode escape sequences (the original function replaced
        e. g. newlines with "\n" etc.).
      - Does not wrap the resulting string in double quotes (").

    :type unicode_obj: unicode
    :param unicode_obj: The source string containing data to escape.
    :rtype : unicode
    :return: A unicode object. This does not contain any non-ASCII characters anymore.
    """
    def replace(match):
        s = match.group(0)
        n = ord(s)
        if n < 0x10000:
            return u'\\u{0:04x}'.format(n)
        else:
            # surrogate pair
            n -= 0x10000
            s1 = 0xd800 | ((n >> 10) & 0x3ff)
            s2 = 0xdc00 | (n & 0x3ff)
            return u'\\u{0:04x}\\u{1:04x}'.format(s1, s2)

    # Just to be sure: If we get passed a str object, then try to decode it as UTF-8.
    if isinstance(unicode_obj, six.binary_type):
        unicode_obj = unicode_obj.decode('utf-8')

    return re.sub(
        six.text_type(r'[^ -~]'),
        replace,
        unicode_obj
    )