# coding: utf-8
# Created on: 04.01.2018
# Author: Roman Miroshnychenko aka Roman V.M. (roman1972@gmail.com)
"""
Utility functions for string conversion depending on Python version
"""

import sys
import inspect

__all__ = [
    'PY2',
    'py2_encode',
    'py2_decode',
    'encode_decode',
    'patch_object',
    'ModuleWrapper',
]

PY2 = sys.version_info[0] == 2  #: ``True`` for Python 2


def py2_encode(s, encoding='utf-8', errors='strict'):
    """
    Encode Python 2 ``unicode`` to ``str``

    In Python 3 the string is not changed.
    """
    if PY2 and isinstance(s, unicode):
        s = s.encode(encoding, errors)
    return s


def py2_decode(s, encoding='utf-8', errors='strict'):
    """
    Decode Python 2 ``str`` to ``unicode``

    In Python 3 the string is not changed.
    """
    if PY2 and isinstance(s, str):
        s = s.decode(encoding, errors)
    return s


def encode_decode(func):
    """
    A decorator that encodes all unicode function arguments to UTF-8-encoded
    byte strings and decodes function str return value to unicode.

    This decorator is no-op in Python 3.

    :param func: wrapped function or a method
    :type func: types.FunctionType or types.MethodType
    :return: function wrapper
    :rtype: types.FunctionType
    """
    if PY2:
        def wrapper(*args, **kwargs):
            mod_args = tuple(py2_encode(item) for item in args)
            mod_kwargs = {key: py2_encode(value) for key, value
                          in kwargs.iteritems()}
            return py2_decode(func(*mod_args, **mod_kwargs),
                              errors='replace')
        wrapper.__name__ = 'wrapped_func_{0}'.format(func.__name__)
        return wrapper
    return func


def _wrap_class(cls):
    class ClassWrapper(cls):
        pass
    ClassWrapper.__name__ = 'wrapped_class_{0}'.format(cls.__name__)
    return ClassWrapper


def patch_object(obj):
    """
    Applies func:`encode_decode` decorator to an object

    :param obj: object for patching
    :return: patched object
    """
    if inspect.isbuiltin(obj):
        obj = encode_decode(obj)
    elif inspect.isclass(obj):
        # We cannot patch methods of Kodi classes directly.
        obj = _wrap_class(obj)
        for memb_name, member in inspect.getmembers(obj):
            # Do not patch special methods!
            if (inspect.ismethoddescriptor(member) and
                    not memb_name.endswith('__')):
                setattr(obj, memb_name, encode_decode(member))
    return obj


class ModuleWrapper(object):
    """
    Implements lazy patching of Kodi Python API modules

    This class applies func:`encode_decode` decorator to Kodi API functions
    and classes on demand when a function or a class is first used.
    """
    def __init__(self, base_module):
        self._base_module = base_module

    def __getattr__(self, item):
        if not hasattr(self._base_module, item):
            raise AttributeError(
                'Module {0} does not have attribute "{1}"!'.format(
                    self._base_module, item
                )
            )
        obj = getattr(self._base_module, item)
        obj = patch_object(obj)
        setattr(self, item, obj)
        return obj
