from copy import deepcopy
from functools import wraps
from typing import Dict, Any, Union, List, Callable
from .base import BaseTree
from ...utils import init_magic
class _RawWrapped:
    def __init__(self, value):
        self.__value = value
    @property
    def value(self):
        return self.__value
_RAW_NEEDED_TYPES = (dict,)
[docs]def raw(value) -> _RawWrapped:
    """
    Overview:
        Wrap raw value to init tree or set item, can be used for dict.
        Can only performs when value is a dict object, otherwise just return the original value.
    Arguments:
        - value (:obj:): Original value.
    Returns:
        - wrapped (:obj:`_RawWrapped`): Wrapped value.
    Example:
        >>> t = Tree({
        >>>     'a': raw({'a': 1, 'b': 2}),
        >>>     'b': raw({'a': 3, 'b': 4}),
        >>>     'x': {
        >>>         'c': raw({'a': 5, 'b': 6}),
        >>>         'd': raw({'a': 7, 'b': 8}),
        >>>     }
        >>> })
        >>>
        >>> t['a']  # {'a': 1, 'b': 2}
        >>> t['b']  # {'a': 3, 'b': 4}
        >>> t['x']['c']  # {'a': 5, 'b': 6}
        >>> t['x']['d']  # {'a': 7, 'b': 8}
        >>>
        >>> t['a'] = raw({'a': 9, 'b': 10})
        >>> t['a']  # {'a': 9, 'b': 10}
        >>> t['a'] = {'a': 9, 'b': 10}
        >>> t['a']  # should be a Tree object when raw not used
    """
    if not isinstance(value, _RawWrapped) and isinstance(value, _RAW_NEEDED_TYPES):
        return _RawWrapped(value)
    else:
        return value 
def _unraw(value):
    if isinstance(value, _RawWrapped):
        return value.value
    else:
        return value
def _to_tree_decorator(init_func):
    @wraps(init_func)
    def _new_init_func(data):
        if isinstance(data, BaseTree):
            _new_init_func(_tree_dump(data))
        elif isinstance(data, dict):
            init_func({
                str(key): Tree(value) if isinstance(value, dict) else _unraw(value)
                for key, value in data.items()
            })
        else:
            raise TypeError(
                "Dict value expected for dispatch value but {type} actually.".format(type=repr(type(data).__name__)))
    return _new_init_func
def _copy_func(copy):
    if hasattr(copy, '__call__'):
        return copy
    elif copy is None or isinstance(copy, (bool,)):
        return (lambda x: deepcopy(x)) if copy else (lambda x: x)
    elif isinstance(copy, (list, tuple)):
        dumper, loader = copy[:2]
        return lambda x: loader(dumper(x))
    else:
        dumper, loader = getattr(copy, 'dumps'), getattr(copy, 'loads')
        return lambda x: loader(dumper(x))
def _tree_dump(tree: 'BaseTree', copy_value: Union[None, bool, Callable] = None):
    copy_value = _copy_func(copy_value)
    def _recursion(t):
        if isinstance(t, BaseTree):
            return {key: _recursion(value) for key, value in t.items()}
        else:
            return raw(copy_value(t))
    return _recursion(tree.actual())
[docs]@init_magic(_to_tree_decorator)
class Tree(BaseTree):
    """
    Overview:
        Tree node data model, based on `BaseTree`.
    """
[docs]    def __init__(self, data: Union[Dict[str, Union['Tree', Any]], 'Tree']):
        """
        Overview:
            Constructor of `Tree`, can be `dict`, `Tree`, `TreeView`.
            When dict passed in, a new tree structure will be created once.
            When `Tree` or `TreeView` passed in, a fully copy will be constructed in this object.
        Arguments:
            - data (:obj:`Union[Dict[str, Union['Tree', Any]], 'Tree']`): Any data can be parsed into `Tree`.
        Example:
            >>> t = Tree({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})  # t is a new tree structure
            >>> t2 = Tree(t)                                       # t2 is a full copy of t1
            >>> t3 = Tree({'a': t, 'b': t2})                       # t3 is a tree with subtree of t and t2 (not copy)
        """
        self.__dict = data 
    def __check_key_exist(self, key):
        if key not in self.__dict.keys():
            raise KeyError("Key {key} not found.".format(key=repr(key)))
[docs]    def __getitem__(self, key):
        self.__check_key_exist(key)
        return self.__dict[key] 
[docs]    def __setitem__(self, key, value):
        if isinstance(value, dict):
            value = Tree(value)
        self.__dict[key] = _unraw(value) 
[docs]    def __delitem__(self, key):
        self.__check_key_exist(key)
        del self.__dict[key] 
[docs]    def view(self, path: List[str]):
        from .view import TreeView
        return TreeView(self, path) 
[docs]    def clone(self, copy_value: Union[None, bool, Callable, Any] = None):
        return self.__class__(_tree_dump(self, copy_value)) 
[docs]    def items(self):
        return self.__dict.items() 
[docs]    def keys(self):
        return self.__dict.keys() 
[docs]    def values(self):
        return self.__dict.values() 
[docs]    def actual(self):
        return self