import sys, threading

from . import custom
from .benchmark import benchmarking
from .cache import cache_hook

# Transaction-kankei

class TransactionAborted(Exception):
    pass

state = threading.local()

# Set by model
sql_hook = None

class withness(object):
    def __enter__(self):
        return self
    def __exit__(self,a,b,c):
        if a is None:
            commit_transaction()
        else:
            abort_transaction()

# Current commit protocol:
# begin MySQL
# do stuff, beginning version_control_hook at the first mod
# prepare MySQL (i.e., flush everything to make sure we have all the locks we need and won't deadlock)
# commit the version_control_hook
# if that succeeds:
#   commit MySQL (shouldn't fail because it doesn't require more locks)
#
# See also hook method descs in bazbase.custom.

def begin_transaction(benchmark=True):
    from cStringIO import StringIO
    state.readonly = True
    state.log = StringIO()
    state.revision = None
    if benchmark:
        benchmarking.start(state.log)
    with benchmarking('beginning sql'):
        sql_hook.begin()
    with benchmarking('beginning cache'):
        cache_hook.begin()
    
    return withness()
    
# Raises TransactionAborted if the commit fails
def commit_transaction():
    success = False
    try:
        with benchmarking('preparing sql'):
            sql_hook.prepare()
    except Exception,e:
        sql_hook.abort()
        if not state.readonly and custom.version_control_hook is not None:
            custom.version_control_hook.abort()
        raise #TransactionAborted(e)
    try:
        if not state.readonly and custom.version_control_hook is not None:
            with benchmarking('committing vcs'):
                state.revision = custom.version_control_hook.commit()
    except:
        cache_hook.abort()
        sql_hook.abort()
        raise
    else:
        with benchmarking('committing cache'):
            cache_hook.commit()
        with benchmarking('committing sql'):
            sql_hook.commit()
def abort_transaction():
    # It's ok to keep cached stuff if there weren't any changes made.
    try:
        if state.readonly:
            with benchmarking('abort committing cache'):
                cache_hook.commit()
        else:
            cache_hook.abort()
    finally:
        # We want to do this even if there's something up with the cache hook.
        sql_hook.abort()
        if not state.readonly and custom.version_control_hook is not None:
            custom.version_control_hook.abort()

def __passthrough(name):
    def hookhelper(*args,**kw):
        if state.readonly:
            state.readonly = False
            if custom.version_control_hook is not None:
                with benchmarking('beginning vcs'):
                    state.revision = custom.version_control_hook.begin()
        getattr(sql_hook, name)(*args, **kw)
        if custom.version_control_hook is not None:
            getattr(custom.version_control_hook, name)(*args, **kw)

        cache_hook.clear()
        
        if hasattr(sql_hook,'post_'+name):
            getattr(sql_hook,'post_'+name)(*args,**kw)
    return hookhelper

# Hook passthrough functions
setprop = __passthrough('setprop')
delete = __passthrough('delete')
esetattr = __passthrough('esetattr')
edelete = __passthrough('edelete')
psetattr = __passthrough('psetattr')
pdelete = __passthrough('pdelete')

def get_log():
    if hasattr(state, 'log'):
        return state.log.getvalue()
    else:
        return ""

def get_revision():
    if state.revision is None and custom.version_control_hook is not None:
        state.revision = custom.version_control_hook.get_revision()
    return state.revision
