from __future__ import with_statement

from functools import wraps

from altered.base import (dictget, dictset, dictdel, change,
                          restore, Expando, E, forget, dictlike)

try:
    from contextlib import ContextDecorator
except ImportError:
    from contextdecorator import ContextDecorator

def changers(obj):
    """
    Chooses suitable change operations for `obj`.
    """
    return (dictlike(obj) and (dictget, dictset, dictdel) or
            (getattr, setattr, delattr) )

class state(ContextDecorator):
    """
    This combined context manager and decorator is the main API to use
    Altered States.
    """

    def __init__(self, orig, **attrs):
        self.orig = orig
        self.attrs = attrs
        return super(state, self).__init__()

    def __enter__(self):
        self.getter, self.setter, self.deleter = changers(self.orig)
        self.diff = change(self.orig, self.getter,
                           self.setter, self.deleter, **self.attrs)
        return self

    def __exit__(self, *args, **kw):
        restore(self.orig, self.diff, self.getter, self.setter, self.deleter)
        return False

    def __call__(self, f):
        @wraps(f)
        def decorated(*args, **kwds):
            with self:
                return f(*args, **kwds)
        return decorated

def alter(obj, **changes):
    """
    Alternative API entry: a two step altering procedure. Same  as
    `state`, but returns a function that will restore the changes at a
    later point.
    """
    getter, setter, deleter = changers(obj)

    diff = change(obj, getter, setter, deleter, **changes)

    def restoration():
        restore(obj, diff, getter, setter, deleter)

    return restoration
