"""
Utilities for turning objects into human-readable strings.

This module has been ported to Python 3.
"""

import os
from reprlib import Repr

class BetterRepr(Repr, object):
    def __init__(self):
        Repr.__init__(self)

        # Note: These levels can get adjusted dynamically!  My goal is to get more info when printing important debug stuff like exceptions and stack traces and less info when logging normal events.  --Zooko 2000-10-14
        self.maxlevel = 6
        self.maxdict = 6
        self.maxlist = 6
        self.maxtuple = 6
        self.maxstring = 300
        self.maxother = 300

    def repr_function(self, obj, level):
        if hasattr(obj, '__code__'):
            return '<' + obj.__name__ + '() at ' + os.path.basename(obj.__code__.co_filename) + ':' + str(obj.__code__.co_firstlineno) + '>'
        else:
            return '<' + obj.__name__ + '() at (builtin)'

    def repr_instance_method(self, obj, level):
        if hasattr(obj, '__code__'):
            return '<' + obj.__self__.__class__.__name__ + '.' + obj.__func__.__name__ + '() at ' + os.path.basename(obj.__func__.__code__.co_filename) + ':' + str(obj.__func__.__code__.co_firstlineno) + '>'
        else:
            return '<' + obj.__self__.__class__.__name__ + '.' + obj.__func__.__name__ + '() at (builtin)'

    def repr_long(self, obj, level):
        s = repr(obj) # XXX Hope this isn't too slow...
        if len(s) > self.maxlong:
            i = max(0, (self.maxlong-3) // 2)
            j = max(0, self.maxlong-3-i)
            s = s[:i] + '...' + s[len(s)-j:]
        if s[-1] == 'L':
            return s[:-1]
        return s

    def repr_instance(self, obj, level):
        """
        If it is an instance of Exception, format it nicely (trying to emulate
        the format that you see when an exception is actually raised, plus
        bracketing '<''s).  If it is an instance of dict call self.repr_dict()
        on it.  If it is an instance of list call self.repr_list() on it. Else
        call Repr.repr_instance().
        """
        if isinstance(obj, Exception):
            # Don't cut down exception strings so much.
            tms = self.maxstring
            self.maxstring = max(512, tms * 4)
            tml = self.maxlist
            self.maxlist = max(12, tml * 4)
            try:
                if hasattr(obj, 'args'):
                    if len(obj.args) == 1:
                        return '<' + obj.__class__.__name__ + ': ' + self.repr1(obj.args[0], level-1) + '>'
                    else:
                        return '<' + obj.__class__.__name__ + ': ' + self.repr1(obj.args, level-1) + '>'
                else:
                    return '<' + obj.__class__.__name__ + '>'
            finally:
                self.maxstring = tms
                self.maxlist = tml

        if isinstance(obj, dict):
            return self.repr_dict(obj, level)

        if isinstance(obj, list):
            return self.repr_list(obj, level)

        return Repr.repr_instance(self, obj, level)

    def repr_list(self, obj, level):
        """
        copied from standard repr.py and fixed to work on multithreadedly mutating lists.
        """
        if level <= 0: return '[...]'
        n = len(obj)
        myl = obj[:min(n, self.maxlist)]
        s = ''
        for item in myl:
            entry = self.repr1(item, level-1)
            if s: s = s + ', '
            s = s + entry
        if n > self.maxlist: s = s + ', ...'
        return '[' + s + ']'

    def repr_dict(self, obj, level):
        """
        copied from standard repr.py and fixed to work on multithreadedly mutating dicts.
        """
        if level <= 0: return '{...}'
        s = ''
        n = len(obj)
        items = list(obj.items())[:min(n, self.maxdict)]
        items.sort()
        for key, val in items:
            entry = self.repr1(key, level-1) + ':' + self.repr1(val, level-1)
            if s: s = s + ', '
            s = s + entry
        if n > self.maxdict: s = s + ', ...'
        return '{' + s + '}'

# This object can be changed by other code updating this module's "brepr"
# variables.  This is so that (a) code can use humanreadable with
# "from humanreadable import hr; hr(mything)", and (b) code can override
# humanreadable to provide application-specific human readable output
# (e.g. libbase32's base32id.AbbrevRepr).
brepr = BetterRepr()

def hr(x):
    return brepr.repr(x)
