from construct.lib.py3compat import *
import re
import collections


globalPrintFullStrings = False
globalPrintFalseFlags = False
globalPrintPrivateEntries = False


def setGlobalPrintFullStrings(enabled=False):
    r"""
    When enabled, Container __str__ produces full content of bytes and unicode strings, otherwise and by default, it produces truncated output (16 bytes and 32 characters).

    :param enabled: bool
    """
    global globalPrintFullStrings
    globalPrintFullStrings = enabled


def setGlobalPrintFalseFlags(enabled=False):
    r"""
    When enabled, Container __str__ that was produced by FlagsEnum parsing prints all values, otherwise and by default, it prints only the values that are True.

    :param enabled: bool
    """
    global globalPrintFalseFlags
    globalPrintFalseFlags = enabled


def setGlobalPrintPrivateEntries(enabled=False):
    r"""
    When enabled, Container __str__ shows keys like _ _index _etc, otherwise and by default, it hides those keys. __repr__ never shows private entries.

    :param enabled: bool
    """
    global globalPrintPrivateEntries
    globalPrintPrivateEntries = enabled


def recursion_lock(retval="<recursion detected>", lock_name="__recursion_lock__"):
    """Used internally."""
    def decorator(func):
        def wrapper(self, *args, **kw):
            if getattr(self, lock_name, False):
                return retval
            setattr(self, lock_name, True)
            try:
                return func(self, *args, **kw)
            finally:
                delattr(self, lock_name)

        wrapper.__name__ = func.__name__
        return wrapper

    return decorator


class Container(collections.OrderedDict):
    r"""
    Generic ordered dictionary that allows both key and attribute access, and preserves key order by insertion. Adding keys is preferred using \*\*entrieskw (requires Python 3.6). Equality does NOT check item order. Also provides regex searching.

    Example::

        # empty dict
        >>> Container()
        # list of pairs, not recommended
        >>> Container([ ("name","anonymous"), ("age",21) ])
        # This syntax requires Python 3.6
        >>> Container(name="anonymous", age=21)
        # This syntax is for internal use only
        >>> Container(name="anonymous")(age=21)
        # copies another dict
        >>> Container(dict2)
        >>> Container(container2)

    ::

        >>> print(repr(obj))
        Container(text='utf8 decoded string...')(value=123)
        >>> print(obj)
        Container
            text = u'utf8 decoded string...' (total 22)
            value = 123
    """
    __slots__ = ["__recursion_lock__"]

    def __getattr__(self, name):
        try:
            if name in self.__slots__:
                return object.__getattribute__(self, name)
            else:
                return self[name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        try:
            if name in self.__slots__:
                return object.__setattr__(self, name, value)
            else:
                self[name] = value
        except KeyError:
            raise AttributeError(name)

    def __delattr__(self, name):
        try:
            if name in self.__slots__:
                return object.__delattr__(self, name)
            else:
                del self[name]
        except KeyError:
            raise AttributeError(name)

    def __call__(self, **entrieskw):
        """Chains adding new entries to the same container. See ctor."""
        for k,v in entrieskw.items():
            self[k] = v
        return self

    def update(self, seqordict):
        """Appends items from another dict/Container or list-of-tuples."""
        if isinstance(seqordict, dict):
            seqordict = seqordict.items()
        for k,v in seqordict:
            self[k] = v

    def copy(self):
        return Container(self)

    __update__ = update

    __copy__ = copy

    def __dir__(self):
        """For auto completion of attributes based on container values."""
        return list(self.keys()) + list(self.__class__.__dict__) + dir(super(Container, self))

    def __eq__(self, other):
        if self is other:
            return True
        if not isinstance(other, dict):
            return False
        def isequal(v1, v2):
            if v1.__class__.__name__ == "ndarray" or v2.__class__.__name__ == "ndarray":
                import numpy
                return numpy.array_equal(v1, v2)
            return v1 == v2
        for k,v in self.items():
            if isinstance(k, unicodestringtype) and k.startswith(u"_"):
                continue
            if isinstance(k, bytestringtype) and k.startswith(b"_"):
                continue
            if k not in other or not isequal(v, other[k]):
                return False
        for k,v in other.items():
            if isinstance(k, unicodestringtype) and k.startswith(u"_"):
                continue
            if isinstance(k, bytestringtype) and k.startswith(b"_"):
                continue
            if k not in self or not isequal(v, self[k]):
                return False
        return True

    def __ne__(self, other):
       return not self == other

    @recursion_lock()
    def __repr__(self):
        parts = []
        for k,v in self.items():
            if isinstance(k, str) and k.startswith("_"):
                continue
            if isinstance(v, stringtypes):
                parts.append(str(k) + "=" + reprstring(v))
            else:
                parts.append(str(k) + "=" + repr(v))
        return "Container(%s)" % ", ".join(parts)

    @recursion_lock()
    def __str__(self):
        indentation = "\n    "
        text = ["Container: "]
        isflags = getattr(self, "_flagsenum", False)
        for k,v in self.items():
            if isinstance(k, str) and k.startswith("_") and not globalPrintPrivateEntries:
                continue
            if isflags and not v and not globalPrintFalseFlags:
                continue
            text.extend([indentation, str(k), " = "])
            if v.__class__.__name__ == "EnumInteger":
                text.append("(enum) (unknown) %s" % (v, ))
            elif v.__class__.__name__ == "EnumIntegerString":
                text.append("(enum) %s %s" % (v, v.intvalue, ))
            elif v.__class__.__name__ in ["HexDisplayedBytes", "HexDumpDisplayedBytes"]:
                text.append(indentation.join(str(v).split("\n")))
            elif isinstance(v, bytestringtype):
                printingcap = 16
                if len(v) <= printingcap or globalPrintFullStrings:
                    text.append("%s (total %d)" % (reprstring(v), len(v)))
                else:
                    text.append("%s... (truncated, total %d)" % (reprstring(v[:printingcap]), len(v)))
            elif isinstance(v, unicodestringtype):
                printingcap = 32
                if len(v) <= printingcap or globalPrintFullStrings:
                    text.append("%s (total %d)" % (reprstring(v), len(v)))
                else:
                    text.append("%s... (truncated, total %d)" % (reprstring(v[:printingcap]), len(v)))
            else:
                text.append(indentation.join(str(v).split("\n")))
        return "".join(text)

    def _search(self, compiled_pattern, search_all):
        items = []
        for key in self.keys():
            try:
                if isinstance(self[key], (Container,ListContainer)):
                    ret = self[key]._search(compiled_pattern, search_all)
                    if ret is not None:
                        if search_all:
                            items.extend(ret)
                        else:
                            return ret
                elif compiled_pattern.match(key):
                    if search_all:
                        items.append(self[key])
                    else:
                        return self[key]
            except:
                pass
        if search_all:
            return items
        else:
            return None

    def search(self, pattern):
        """
        Searches a container (non-recursively) using regex.
        """
        compiled_pattern = re.compile(pattern)
        return self._search(compiled_pattern, False)

    def search_all(self, pattern):
        """
        Searches a container (recursively) using regex.
        """
        compiled_pattern = re.compile(pattern)
        return self._search(compiled_pattern, True)


class ListContainer(list):
    r"""
    Generic container like list. Provides pretty-printing. Also provides regex searching.

    Example::

        >>> ListContainer()
        >>> ListContainer([1, 2, 3])

    ::

        >>> print(repr(obj))
        [1, 2, 3]
        >>> print(obj)
        ListContainer
            1
            2
            3
    """

    @recursion_lock()
    def __repr__(self):
        return "ListContainer(%s)" % (list.__repr__(self), )

    @recursion_lock()
    def __str__(self):
        indentation = "\n    "
        text = ["ListContainer: "]
        for k in self:
            text.append(indentation)
            lines = str(k).split("\n")
            text.append(indentation.join(lines))
        return "".join(text)

    def _search(self, compiled_pattern, search_all):
        items = []
        for item in self:
            try:
                ret = item._search(compiled_pattern, search_all)
            except:
                continue
            if ret is not None:
                if search_all:
                    items.extend(ret)
                else:
                    return ret
        if search_all:
            return items
        else:
            return None

    def search(self, pattern):
        """
        Searches a container (non-recursively) using regex.
        """
        compiled_pattern = re.compile(pattern)
        return self._search(compiled_pattern, False)

    def search_all(self, pattern):
        """
        Searches a container (recursively) using regex.
        """
        compiled_pattern = re.compile(pattern)
        return self._search(compiled_pattern, True)
