import sqlite3
import ctypes
import config

db_connection = sqlite3.connect(config.db_filename,detect_types=sqlite3.PARSE_DECLTYPES,check_same_thread = False)
db_connection.row_factory = sqlite3.Row

def register_db_type(name):
 def register(cls):
  sqlite3.register_adapter(cls, cls.__repr__)
  sqlite3.register_converter(name, cls)
  return cls
 return register

def executescript(script):
 with db_connection as c:
  c.executescript(script)

executescript("PRAGMA journal_mode = WAL;")
#executescript("PRAGMA synchronous = OFF;");

class ASPPMixin(object):
 'requires object with iterable |data| attribute at least 8 byte long'
 def __str__(self):
  return ''.join(["%02x" % (i) for i in self.data[7::-1]])

 __repr__ = __str__

 def parse(self,value):
  [self.data.__setitem__(7-i,int(value[2*i:2*i+2],16)) for i in xrange(8)]

 def __init__(self,value = None):
  if value != None: self.parse(value)

 @classmethod
 def convert(cls,value):
  return cls(value)

class MetaEvent(type(ctypes.Structure)):
 'MetaEvent inherits from ctypes.Structure metaclass to avoid conflicts.'

 @staticmethod
 def lookup(key,bases,attrs):
  if key in attrs:
   return attrs[key]
  for base in bases:
   if key in base.__dict__:
    return base.__dict__[key]

 def __new__(self,name,bases,attrs):
  table = MetaEvent.lookup('_table_',bases,attrs)
  if not '_table_' in attrs:
   fields  = [field_data[0] for field_data in attrs.get('_fields_',[])]
   default = MetaEvent.lookup('_default_',bases,attrs)
   extra   = MetaEvent.lookup('_extra_',bases,attrs)
   attrs['_init_keys_'] = default + extra + fields
   attrs['_save_keys_'] = ['EventCode'] + extra + fields

   save_args = (
    table,
    ','.join(attrs['_save_keys_']),
    ('?,'*len(attrs['_save_keys_']))[0:-1]
   )
   attrs['_save_query_'] = 'insert into {0}({1}) values({2})'.format(*save_args)

  attrs['_load_range_query_'] = 'select * from {0} where id between ? and ?'.format(table)
  attrs['_load_last_query_'] = 'select * from {0} order by id desc limit 0,1'.format(table)

  return super(MetaEvent,self).__new__(self,name,bases,attrs)

class Event(object):
 __metaclass__ = MetaEvent

 # Descendants should specify this class attribute to make metaclass process them
 _table_ = None

 # Fields in this list should have default values in database
 # i.e. they acquire their values during insert to db.
 # Being specified in this list means those keys aren't listed in structure fields.
 _default_ = []

 # Those field don't have default database values and should be explicitly
 # set some value (at least to None, as Event.__init__ does).
 # Being specified in this list means those keys aren't listed in structure fields.
 _extra_   = []

 # Registry for classes in hierarchy
 registry = {}

 @classmethod
 def register(cls,event_class):
  cls.registry[event_class.EventCode] = event_class
  return event_class

 def __init__(self,*args,**kw):
  # obj._init_keys_ generated by metaclasss should be assignable via constructor
  # keyword arguments, as this is the way they'll be deserialized from database
  [setattr(self,key,kw[key]) for key in kw if key in self._init_keys_]

 def save(self):
  '''
  Event.save performs saving predefined _save_keys_ of its class to the database.
  Return value: id of the last record saved to class table.
  '''
  values = [getattr(self,key,None) for key in self._save_keys_]

  with db_connection as c:
   cursor = c.cursor()
   cursor.execute(self._save_query_,values)
   return cursor.lastrowid

 def card_event_price(self):
  '''
  Default implementation of method to get a price for CardEvent.
  Returns Amount field from underlying structure or 0 if there is no such field.
  '''
  return getattr(self,'Amount',0)

 @classmethod
 def load_last(cls):
  with db_connection as c:
   rows = c.execute(cls._load_last_query_)
   events = [cls.registry.get(row['EventCode'],cls)(**row) for row in rows]
   return events[0] if events else None

 @classmethod
 def load(cls,(a,b)):
  with db_connection as c:
   rows = c.execute(cls._load_range_query_,(a,b))
   return [cls.registry.get(row['EventCode'],cls)(**row) for row in rows]
