"""
Functions for working with "safe strings": strings that can be displayed safely
without further escaping in HTML. Marking something as a "safe string" means
that the producer of the string has already turned characters that should not
be interpreted by the HTML engine (e.g. '<') into the appropriate entities.
"""
from functional import curry, Promise

class EscapeData(object):
    pass

class EscapeString(str, EscapeData):
    """
    A string that should be HTML-escaped when output.
    """
    pass

class EscapeUnicode(unicode, EscapeData):
    """
    A unicode object that should be HTML-escaped when output.
    """
    pass

class SafeData(object):
    pass

class SafeString(str, SafeData):
    """
    A string subclass that has been specifically marked as "safe" (requires no
    further escaping) for HTML output purposes.
    """
    def __add__(self, rhs):
        """
        Concatenating a safe string with another safe string or safe unicode
        object is safe. Otherwise, the result is no longer safe.
        """
        t = super(SafeString, self).__add__(rhs)
        if isinstance(rhs, SafeUnicode):
            return SafeUnicode(t)
        elif isinstance(rhs, SafeString):
            return SafeString(t)
        return t

    def _proxy_method(self, *args, **kwargs):
        """
        Wrap a call to a normal unicode method up so that we return safe
        results. The method that is being wrapped is passed in the 'method'
        argument.
        """
        method = kwargs.pop('method')
        data = method(self, *args, **kwargs)
        if isinstance(data, str):
            return SafeString(data)
        else:
            return SafeUnicode(data)

    decode = curry(_proxy_method, method = str.decode)

class SafeUnicode(unicode, SafeData):
    """
    A unicode subclass that has been specifically marked as "safe" for HTML
    output purposes.
    """
    def __add__(self, rhs):
        """
        Concatenating a safe unicode object with another safe string or safe
        unicode object is safe. Otherwise, the result is no longer safe.
        """
        t = super(SafeUnicode, self).__add__(rhs)
        if isinstance(rhs, SafeData):
            return SafeUnicode(t)
        return t

    def _proxy_method(self, *args, **kwargs):
        """
        Wrap a call to a normal unicode method up so that we return safe
        results. The method that is being wrapped is passed in the 'method'
        argument.
        """
        method = kwargs.pop('method')
        data = method(self, *args, **kwargs)
        if isinstance(data, str):
            return SafeString(data)
        else:
            return SafeUnicode(data)

    encode = curry(_proxy_method, method = unicode.encode)

def mark_safe(s):
    """
    Explicitly mark a string as safe for (HTML) output purposes. The returned
    object can be used everywhere a string or unicode object is appropriate.

    Can be called multiple times on a single string.
    """
    if isinstance(s, SafeData):
        return s
    if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
        return SafeString(s)
    if isinstance(s, (unicode, Promise)):
        return SafeUnicode(s)
    return SafeString(str(s))

def mark_for_escaping(s):
    """
    Explicitly mark a string as requiring HTML escaping upon output. Has no
    effect on SafeData subclasses.

    Can be called multiple times on a single string (the resulting escaping is
    only applied once).
    """
    if isinstance(s, (SafeData, EscapeData)):
        return s
    if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str):
        return EscapeString(s)
    if isinstance(s, (unicode, Promise)):
        return EscapeUnicode(s)
    return EscapeString(str(s))

