import datetime
from . import errors


# User-configurable functions and variables available in all contexts.
builtins = {
    'now': datetime.datetime.now,
    'range': range,
}


# A wrapper around a stack of dictionaries.
class DataStack:

    def __init__(self):
       self.stack = []

    def __getitem__(self, key):
        for d in reversed(self.stack):
            if key in d:
                return d[key]
        raise KeyError(key)


# A Context object is a wrapper around the user's input data. Its `.resolve()` method contains
# the lookup-logic for resolving dotted variable names.
class Context:

    def __init__(self, data_dict, template):

        # Data store of resolvable variable names for the .resolve() method.
        self.data = DataStack()

        # Standard builtins.
        self.data.stack.append({
            'context': self,
            'is_defined': self.is_defined,
        })

        # User-configurable builtins.
        self.data.stack.append(builtins)

        # Instance-specific data.
        self.data.stack.append(data_dict)

        # Nodes can store state information here to avoid threading issues.
        self.stash = {}

        # This reference gives nodes access to their parent template object.
        self.template = template

    def __setitem__(self, key, value):
        self.data.stack[-1][key] = value

    def __getitem__(self, key):
        return self.data[key]

    def push(self, data=None):
        self.data.stack.append(data or {})

    def pop(self):
        self.data.stack.pop()

    def get(self, key, default=None):
        for d in reversed(self.data.stack):
            if key in d:
                return d[key]
        return default

    def update(self, data_dict):
        self.data.stack[-1].update(data_dict)

    def resolve(self, varstring, token):
        words = []
        result = self.data
        for word in varstring.split('.'):
            words.append(word)
            if hasattr(result, word):
                result = getattr(result, word)
            else:
                try:
                    result = result[word]
                except:
                    try:
                        result = result[int(word)]
                    except:
                        msg = f"Cannot resolve the variable '{'.'.join(words)}'"
                        raise errors.UndefinedVariable(msg, token) from None
        return result

    def is_defined(self, varstring):
        current = self.data
        for word in varstring.split('.'):
            if hasattr(current, word):
                current = getattr(current, word)
            else:
                try:
                    current = current[word]
                except:
                    try:
                        current = current[int(word)]
                    except:
                        return False
        return True
