__author__ = 'jonathan'

from lib.rome.core.utils import merge_dicts
from lib.rome.core.utils import current_milli_time
from lib.rome.core.lazy_reference import LazyReference
from redlock import Redlock as Redlock

import time
import random
import logging

import uuid

class SessionException(Exception):
    pass

class SessionControlledExecution():

    def __init__(self, session):
        self.session = session

    def __enter__(self):
        pass

    def __exit__(self, type, value, traceback):
        if traceback:
            self.session.handle_error(traceback)
            # print(traceback)
        else:
            self.session.flush()

class Session(object):

    max_duration = 300

    def __init__(self):
        self.session_id = uuid.uuid1()
        self.session_objects_add = []
        self.session_objects_delete = []
        self.session_timeout = current_milli_time() + Session.max_duration
        self.dlm = Redlock([{"host": "localhost", "port": 6379, "db": 0}, ], retry_count=10)
        self.locks = []
        self.current_count = 0

    def add(self, *objs):
        self.session_objects_add += objs

    def delete(self, *objs):
        self.session_objects_delete += objs

    def query(self, *entities, **kwargs):
        from lib.rome.core.orm.query import Query
        return Query(*entities, **merge_dicts(kwargs, {"session": self}))

    def begin(self, *args, **kwargs):
        return SessionControlledExecution(session=self)

    def flush(self, *args, **kwargs):
        logging.info("flushing session %s" % (self.session_id))
        if self.commit_request():
            logging.info("committing session %s" % (self.session_id))
            self.commit()
        else:
            self.handle_error()

    def handle_error(self, traceback=None):
            self.current_count += 1
            if self.current_count < 10:
                time_to_wait = random.randint(1, 10) / 100.0
                time.sleep(time_to_wait)
                self.begin()
            else:
                raise traceback

    def can_be_used(self, obj):
        if getattr(obj, "session", None) is None:
            return True
        else:
            if obj.session["session_id"] == self.session_id:
                return True
            if current_milli_time >= obj.session["session_timeout"]:
                return True
        logging.error("session %s cannot use object %s" % (self.session_id, obj))
        return False

    def commit_request(self):
        processed_objects = []
        success = True
        tablenames_index = {}
        for obj in self.session_objects_add + self.session_objects_delete:
            tablenames_index[obj.__tablename__] = True
            if obj.id is not None:
                recent_version = LazyReference(obj.__tablename__, obj.id, self.session_id, None).load()
                if self.can_be_used(recent_version):
                    session_object = {"session_id": str(self.session_id), "session_timeout": self.session_timeout}
                    recent_version.update({"session": session_object})
                    recent_version.save(force=True, session=self, no_nested_save=True, increase_version=False)
                    processed_objects += [recent_version]
                else:
                    success = False
        for tablename in tablenames_index:
            lockname = "lock-%s" % (tablename)
            lock = self.dlm.lock(lockname, 1000)
            if lock is False:
                success = False
            self.locks += [lock]

        if not success:
            logging.error("session %s encountered a conflict, aborting commit" % (self.session_id))
            for obj in processed_objects:
                obj.session = None
                obj.save(force=True, no_nested_save=True)
            for lock in self.locks:
                self.dlm.unlock(lock)
        return success

    def commit(self):
        logging.info("session %s will start commit" % (self.session_id))
        for obj in self.session_objects_add:
            obj.update({"session": None}, skip_session=True)
            obj.save(force=True)
        for obj in self.session_objects_delete:
            obj.update({"session": None}, skip_session=True)
            obj.soft_delete(force=True)
        for lock in self.locks:
            self.dlm.unlock(lock)
        logging.info("session %s committed" % (self.session_id))
        self.session_objects_add = []
        self.session_objects_delete = []

@@ -522,32 +522,34 @@
 ack:%0A           
+ #
  self.session.ha
@@ -581,18 +581,16 @@
         
- #
  print(t
@@ -915,24 +915,26 @@
 tion%0A       
+ #
  self.dlm = 
@@ -1010,24 +1010,26 @@
 =10)%0A       
+ #
  self.locks 
@@ -1864,18 +1864,17 @@
 count %3C 
-10
+3
 :%0A      
@@ -2546,24 +2546,26 @@
 True%0A       
+ #
  tablenames_
@@ -2661,16 +2661,18 @@
         
+ #
  tablena
@@ -3315,24 +3315,26 @@
 alse%0A       
+ #
  for tablena
@@ -3361,28 +3361,30 @@
 ex:%0A        
+#
     
+ 
 lockname = %22
@@ -3409,24 +3409,26 @@
 ame)%0A       
+ #
      lock = 
@@ -3456,32 +3456,34 @@
 e, 1000)%0A       
+ #
      if lock is 
@@ -3493,32 +3493,33 @@
 se:%0A        
+#
         
 success = Fa
@@ -3498,32 +3498,33 @@
        #        
+ 
 success = False%0A
@@ -3522,32 +3522,34 @@
  = False%0A       
+ #
      self.locks 
@@ -3825,24 +3825,26 @@
 %0A           
+ #
  for lock in
@@ -3859,32 +3859,34 @@
 cks:%0A           
+ #
      self.dlm.un
@@ -4306,24 +4306,26 @@
 rue)%0A       
+ #
  for lock in
@@ -4336,32 +4336,34 @@
 f.locks:%0A       
+ #
      self.dlm.un

fix a bug;