"""
    gitdata.stores.entity

    Entity Store

    An EntityStore is a high level class that provides a standard interface
    for storing and working with Records but using a universal data model
    schema rather than a static data model schmea.  This allows fast prototyping
    and interating of data based applications with reasonable performance
    while the application is small ( < 1,000,000 records ).

    Because the interface is consistent with the other stores in the gitdata.stores
    subpackage, data models can be easly switched over to a more appropriate data
    storage technology once the data model is known without the cost of significant
    applicatino code changes.

    All of the gitdata.stores package Store classes operate on any dict which
    but are most commonly used with Records as defined in gitdata.utils.

"""

import base64
import datetime
import decimal
import os

import gitdata
import gitdata.utils
import gitdata.json


class UnsupportedTypeException(Exception):
    """Unsupported Type Exception"""


Record = gitdata.utils.Record
RecordList = gitdata.utils.RecordList


def entify(rs, storage):
    """
    converts query result into an EntityList
    """
    klass = storage.klass
    entities = {}

    if hasattr(rs, 'data'):  # maintain backward compatibility with
        rs = rs.data         # legacy database module

    for _, _, row_id, attribute, datatype, value in rs:

        if datatype == 'str':
            pass

        elif datatype == 'unicode' and isinstance(value, str):
            pass

        elif datatype == 'unicode':
            value = value.decode('utf8')

        elif datatype == "int":
            value = int(value)

        elif datatype == 'float':
            value = float(value)

        elif datatype == 'decimal.Decimal':
            value = decimal.Decimal(value)

        elif datatype == "datetime.date":
            y = int(value[:4])
            m = int(value[5:7])
            d = int(value[8:10])
            value = datetime.date(y, m, d)

        elif datatype == "datetime.datetime":
            y = int(value[:4])
            m = int(value[5:7])
            d = int(value[8:10])
            hr = int(value[11:13])
            mn = int(value[14:16])
            sc = int(value[17:19])
            value = datetime.datetime(y, m, d, hr, mn, sc)

        elif datatype == 'bool':
            value = (value == '1' or value == 'True')

        elif datatype == 'NoneType':
            value = None

        elif datatype == 'bytes':
            value = base64.b64decode(value)

        elif datatype == 'list':
            value = gitdata.json.loads(value)

        elif datatype == 'tuple':
            value = tuple(gitdata.json.loads(value))

        else:
            msg = 'unsupported data type: ' + repr(datatype)
            raise UnsupportedTypeException(msg)

        entities.setdefault(row_id, klass(_id=row_id, __store=storage))[attribute] = value

    return RecordList(entities.values())


class Store(object):

    def before_update(self, record):
        pass

    def after_update(self, record):
        pass

    def before_insert(self, record):
        pass

    def after_insert(self, record):
        pass

    def before_delete(self, record):
        pass

    def after_delete(self, record):
        pass


class EntityStore(Store):
    """stores entities

        >>> db = gitdata.database.setup_test_database()

        >>> stuff = EntityStore(db)
        >>> stuff.put(dict(name='Joe', age=14))
        1
        >>> stuff.put(dict(name='Sally', age=34))
        2
        >>> stuff.put(dict(name='Sam', age=34))
        3
        >>> print(stuff.find(name='Joe'))
        dict
        _id name age
        --- ---- ---
          1 Joe   14
        1 dict records
        >>> s = stuff.find(age=34)
        >>> print(s)
        dict
        _id name  age
        --- ----- ---
          2 Sally  34
          3 Sam    34
        2 dict records

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> people.kind
        'person'
        >>> joe = Person(name='Joe', age=20, birthdate=datetime.date(1992,5,5))
        >>> joe
        <Person {'name': 'Joe', 'age': 20, 'birthdate': datetime.date(1992, 5, 5)}>
        >>> people.put(joe)
        1
        >>> person = people.get(1)
        >>> person
        <Person {'name': 'Joe', 'age': 20, 'birthdate': datetime.date(1992, 5, 5)}>
        >>> sally = Person(name='Sally', kids=0, birthdate=datetime.date(1992,5,5))
        >>> people.put(sally)
        2
        >>> sally = people.find(name='Sally')
        >>> sally
        [<Person {'name': 'Sally', 'kids': 0, 'birthdate': datetime.date(1992, 5, 5)}>]
        >>> sally = people.first(name='Sally')
        >>> sally
        <Person {'name': 'Sally', 'kids': 0, 'birthdate': datetime.date(1992, 5, 5)}>
        >>> sally.kids += 1
        >>> people.put(sally)
        2
        >>> people.first(name='Sally')
        <Person {'name': 'Sally', 'kids': 1, 'birthdate': datetime.date(1992, 5, 5)}>
        >>> sally = people.first(name='Sally')
        >>> sally.kids += 1
        >>> people.put(sally)
        2
        >>> people.first(name='Sally')
        <Person {'name': 'Sally', 'kids': 2, 'birthdate': datetime.date(1992, 5, 5)}>
        >>> sally = people.first(name='Sally')
        >>> sally.kids += 1
        >>> people.put(sally)
        2
        >>> people.first(name='Sally')
        <Person {'name': 'Sally', 'kids': 3, 'birthdate': datetime.date(1992, 5, 5)}>

        >>> class Misc(EntityStore): pass
        >>> misc = Misc(db, dict)
        >>> config_info = dict(host='database', name='somename')
        >>> id = misc.put(config_info)
        >>> x = misc.put(dict(other='this', stuff='that'))
        >>> my_info = misc.get(id)
        >>> Record(my_info)
        <Record {'name': 'somename', 'host': 'database'}>
        >>> Record(misc.get(x))
        <Record {'other': 'this', 'stuff': 'that'}>

        >>> people = EntityStore(db, 'person')
        >>> people.klass
        <class 'dict'>
        >>> people.kind
        'person'
        >>> print(sorted(people.first(name='Sally').items()))
        [('__store', <EntityStore(dict)>), ('_id', 2), ('birthdate', datetime.date(1992, 5, 5)), ('kids', 3), ('name', 'Sally')]
        >>> print(Person(people.first(name='Sally')))
        Person
          name ................: 'Sally'
          kids ................: 3
          birthdate ...........: datetime.date(1992, 5, 5)
        >>> EntityStore(db, 'person').first(name='Joe')['age']
        20
        >>>
        >>> name = 'somename'
        >>> id = misc.put(dict(host='database', name=name))
        >>> my_info = misc.get(id)
        >>> assert type(my_info['name'])==type(name)

    """

    def __init__(self, db=None, klass=dict, kind=None):

        if db is None:
            self.db = gitdata.database.connect()
        else:
            self.db = db

        self.klass = type(klass) == str and dict or klass
        self.kind = kind or type(klass) == str and klass or gitdata.utils.kind(klass())
        self.id_name = '_id'

    def put(self, entity):
        """
        stores an entity

        >>> db = gitdata.database.setup_test_database()

        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> sally = Person(name='Sally', age=25)
        >>> sally
        <Person {'name': 'Sally', 'age': 25}>
        >>> id = people.put(Person(name='Sally', age=25))
        >>> id
        1
        >>> sally
        <Person {'name': 'Sally', 'age': 25}>
        >>> sally = people.get(id)
        >>> sally
        <Person {'name': 'Sally', 'age': 25}>
        >>> sally.age = 35
        >>> people.put(sally)
        1
        >>> person = people.get(id)
        >>> person
        <Person {'name': 'Sally', 'age': 35}>
        >>> id = people.put({'name':'James', 'age':15})
        >>> id
        2
        >>> people.get(id)
        <Person {'name': 'James', 'age': 15}>
        >>> classes = ['one', 'Not this one']
        >>> grades = (('one', 'A'), ('Not this one', 'C+'), )
        >>> id = people.put({'name':'James', 'classes':classes, 'grades': grades})
        >>> assert classes == people.get(id).classes
        >>> assert len(people.get(id).grades) == 2  # json dump/load will bring back all tuples as lists
        >>> db.close()

        """
        def fixval(d):
            if type(d) == datetime.datetime:
                # avoids mysqldb reliance on strftime that lacks support
                # for dates before 1900
                return "%02d-%02d-%02d %02d:%02d:%02d" % (
                    d.year,
                    d.month,
                    d.day,
                    d.hour,
                    d.minute,
                    d.second
                    )
            if type(d) == decimal.Decimal:
                return str(d)
            if isinstance(d, (list, tuple)):
                return gitdata.json.dumps(d)
            if isinstance(d, bytes):
                return base64.b64encode(d)
            return d

        def get_type_str(v):
            t = repr(type(v))
            if 'type' in t:
                return t.strip('<type >').strip("'")
            elif 'class' in t:
                return t.strip('<class >').strip("'")
            else:
                return t

        db = self.db

        updating = '_id' in entity
        if updating:
            self.before_update(entity)
        else:
            self.before_insert(entity)

        keys = [k for k in list(entity.keys()) if k not in ('_id', '__store')]
        values = [entity[k] for k in keys]
        datatypes = [get_type_str(v) for v in values]
        values = [fixval(i) for i in values]  # same fix as above
        valid_types = [
            'str', 'bytes', 'int', 'float', 'decimal.Decimal',
            'datetime.date', 'datetime.datetime', 'bool', 'NoneType',
            'list', 'tuple'
            ]

        for n, atype in enumerate(datatypes):
            if atype not in valid_types:
                msg = 'unsupported type <type %s> in value %r'
                raise UnsupportedTypeException(msg % (atype, keys[n]))

        if updating:
            id = entity['_id']
            db('delete from attributes where row_id=%s', id)
        else:
            db('insert into entities (kind) values (%s)', self.kind)
            id = entity['_id'] = db.lastrowid
            entity['__store'] = self

        n = len(keys)
        lkeys = [k.lower() for k in keys]
        param_list = list(zip([self.kind]*n, [id]*n, lkeys, datatypes, values))
        cmd = (
            'insert into attributes ('
            '    kind, row_id, attribute, datatype, value'
            ') values (%s,%s,%s,%s,%s)'
            )
        db.execute_many(cmd, param_list)

        if updating:
            self.after_update(entity)
        else:
            self.after_insert(entity)

        return id

    def get(self, keys):
        """
        retrives entities

        >>> db = gitdata.database.setup_test_database()

        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id = people.put(Person(**{'name': 'Sam', 'age':15,
        ...     'salary': decimal.Decimal('100.00')}))
        >>> sam = people.get(id)
        >>> sam
        <Person {'name': 'Sam', 'age': 15, 'salary': Decimal('100.00')}>
        >>> people.put(Person(name='Jim', age=21,
        ...    salary=decimal.Decimal('50')))
        2
        >>> people.put(Person(name='Alice', age=29))
        3
        >>> print(people)
        person
        _id name  age salary
        --- ----- --- ------
          1 Sam    15 100.00
          2 Jim    21 50
          3 Alice  29 None
        3 person records

        >>> print(people.get([1, '3']))
        person
        _id name  age salary
        --- ----- --- ------
          1 Sam    15 100.00
          3 Alice  29 None
        2 person records

        >>> db.close()
        """
        if keys is None:
            return None

        if not isinstance(keys, (list, tuple)):
            keys = (keys,)
            as_list = 0
        else:
            as_list = 1
        keys = [int(key) for key in keys]

        if not keys:
            if as_list:
                return []
            else:
                return None

        cmd = 'select * from attributes where kind=%s and row_id in (%s)' % (
            '%s', ','.join(['%s']*len(keys))
            )
        rs = self.db(cmd, self.kind, *keys)

        result = entify(rs, self)

        if as_list:
            return result
        if result:
            return result[0]

    def get_attributes(self):
        """
        get complete set of attributes for the entity type

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> sam = Person(**{'name': 'Sam', 'age':15})
        >>> sorted(sam.keys())
        ['age', 'name']
        >>> id = people.put(sam)
        >>> sorted(people.get_attributes())
        ['age', 'name']
        >>> db.close()

        """
        # order by id desc so that newly introduced attributes appear at
        # the end of the keys list
        cmd = (
            'select distinct attribute, id '
            'from attributes '
            'where kind=%s order by id desc'
        )
        rs = self.db(cmd, self.kind)
        values = [rec[0] for rec in rs]
        return values

    def _delete(self, ids):
        if ids:
            affected = list(self.get(ids))

            for rec in affected:
                self.before_delete(rec)

            spots = ','.join('%s' for _ in ids)
            cmd = 'delete from attributes where row_id in ({})'.format(spots)
            self.db(cmd, *ids)
            cmd = 'delete from entities where id in ({})'.format(spots)
            self.db(cmd, *ids)

            for rec in affected:
                self.after_delete(rec)

            return ids

    def delete(self, *args, **kwargs):
        """
        delete entities

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id = people.put(Person(name='Sally', age=25))
        >>> id = people.put(Person(name='Sam', age=25))
        >>> id = people.put(Person(name='Joe', age=25))
        >>> joe = people.get(id)
        >>> id
        3
        >>> bool(joe)
        True
        >>> joe
        <Person {'name': 'Joe', 'age': 25}>
        >>> people.delete(id)
        [3]
        >>> joe = people.get(id)
        >>> joe
        >>> bool(joe)
        False

        >>> bool(people.find(name='Sally'))
        True
        >>> people.delete(name='Sallie')
        >>> bool(people.find(name='Sally'))
        True
        >>> people.delete()
        >>> people.delete(name='Sally')
        [1]
        >>> bool(people.find(name='Sally'))
        False

        >>> db.close()

        """
        ids = []
        for key in args:
            if hasattr(key, 'get'):
                key = key['_id']
            ids.append(key)
        if kwargs:
            ids.extend(self._find(**kwargs))
        return self._delete(ids)

    def exists(self, keys=None):
        """
        tests for existence of an entity

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id = people.put(Person(name='Sally', age=25))
        >>> sally = people.get(id)
        >>> sally
        <Person {'name': 'Sally', 'age': 25}>
        >>> people.exists(1)
        True
        >>> people.exists(2)
        False
        >>> people.exists([1, 2])
        [True, False]
        >>> id = people.put(Person(name='Sam', age=25))
        >>> people.exists([1, 2])
        [True, True]
        >>> db.close()

        """
        if not isinstance(keys, (list, tuple)):
            keys = (keys,)
        slots = (','.join(['%s']*len(keys)))
        cmd = (
            'select distinct row_id '
            'from attributes '
            'where row_id in (%s)'
            ) % slots
        rs = self.db(cmd, *keys)

        found_keys = [rec[0] for rec in rs]
        if len(keys) > 1:
            result = [(key in found_keys) for key in keys]
        else:
            result = keys[0] in found_keys
        return result

    def all(self):
        """
        Retrieves all entities

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id = people.put(Person(name='Sally', age=25))
        >>> id = people.put(Person(name='Sam', age=25))
        >>> id = people.put(Person(name='Joe', age=25))
        >>> people.all()
        [<Person {'name': 'Sally', 'age': 25}>, <Person {'name': 'Sam', 'age': 25}>, <Person {'name': 'Joe', 'age': 25}>]
        >>> db.close()

        """
        cmd = 'select * from attributes where kind="%s"' % (self.kind)
        return entify(self.db(cmd), self)

    def zap(self):
        """
        deletes all entities of the given kind

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id = people.put(Person(name='Sally', age=25))
        >>> id = people.put(Person(name='Sam', age=25))
        >>> id = people.put(Person(name='Joe', age=25))
        >>> people.all()
        [<Person {'name': 'Sally', 'age': 25}>, <Person {'name': 'Sam', 'age': 25}>, <Person {'name': 'Joe', 'age': 25}>]
        >>> people.zap()
        >>> people.all()
        []
        >>> db.close()

        """
        cmd = 'delete from attributes where kind=%s'
        self.db(cmd, self.kind)
        cmd = 'delete from entities where kind=%s'
        self.db(cmd, self.kind)

    def __len__(self):
        """
        returns number of entities

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> len(people)
        0
        >>> id = people.put(Person(name='Sam', age=15))
        >>> id = people.put(Person(name='Sally', age=25))
        >>> len(people)
        2
        >>> db.close()

        """
        cmd = ('select count(*) n from '
               '(select distinct row_id from attributes where kind=%s) a')
        r = self.db(cmd, self.kind)
        return int(list(r)[0][0])

    def _find(self, **kv):
        """
        Find keys that meet search critieria
        """
        db = self.db
        all_keys = []
        for field_name in kv.keys():
            value = kv[field_name]
            if value is not None:
                if not isinstance(value, (list, tuple)):
                    wc = 'value=%s'
                    v = (value,)
                else:
                    wc = 'value in ('+','.join(['%s']*len(value))+')'
                    v = value
                cmd = 'select distinct row_id from attributes where kind=%s and attribute=%s and '+wc
                rs = db(cmd, self.kind, field_name.lower(), *v)
                all_keys.append([rec[0] for rec in rs])
        answer = all_keys and set(all_keys[0]) or set()
        for keys in all_keys[1:]:
            answer = set(keys) & answer
        if answer:
            return list(answer)
        else:
            return []

    def find(self, **kv):
        """
        finds entities that meet search criteria

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id = people.put(Person(name='Sam', age=25))
        >>> id = people.put(Person(name='Sally', age=55))
        >>> id = people.put(Person(name='Bob', age=25))

        >>> print(people.find(age=25))
        person
        _id name age
        --- ---- ---
          1 Sam   25
          3 Bob   25
        2 person records

        >>> len(people.find(name='Sam'))
        1

        >>> db.close()

        """
        return self.get(self._find(**kv))

    def first(self, **kv):
        """
        finds the first entity that meet search criteria

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id = people.put(Person(name='Sam', age=25))
        >>> id = people.put(Person(name='Sally', age=55))
        >>> id = people.put(Person(name='Bob', age=25))
        >>> people.first(age=5)
        >>> people.first(age=55)
        <Person {'name': 'Sally', 'age': 55}>
        >>> people.first()
        <Person {'name': 'Sam', 'age': 25}>
        >>> db.close()

        """
        if kv:
            for item in self.find(**kv):
                return item
        else:
            for item in self:
                return item

    def last(self, **kv):
        """
        finds the last entity that meet search criteria

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id = people.put(Person(name='Sam', age=25))
        >>> id = people.put(Person(name='Sally', age=55))
        >>> id = people.put(Person(name='Bob', age=25))
        >>> people.last(age=5)
        >>> people.last(age=25)
        <Person {'name': 'Bob', 'age': 25}>
        >>> db.close()

        """
        rows = self._find(**kv)
        if rows:
            return self.get(rows[-1])
        return None

    def search(self, text):
        """
        search for entities that match text

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id = people.put(Person(name='Sam', age=25))
        >>> id = people.put(Person(name='Sally', age=55))
        >>> id = people.put(Person(name='Bob', age=25))

        >>> list(people.search('bob'))
        [<Person {'name': 'Bob', 'age': 25}>]

        >>> for r in list(people.search(25)): print(r)
        Person
          name ................: 'Sam'
          age .................: 25
        Person
          name ................: 'Bob'
          age .................: 25

        >>> list(people.search('Bill'))
        []
        >>> db.close()

        """
        t = str(text).lower()
        for rec in self:
            if t in repr(list(rec.values())).lower():
                yield rec

    def __iter__(self):
        """
        interates through records

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id = people.put(Person(name='Sam', age=25))
        >>> id = people.put(Person(name='Sally', age=55))
        >>> id = people.put(Person(name='Bob', age=25))
        >>> for rec in people: print(rec)
        Person
          name ................: 'Sam'
          age .................: 25
        Person
          name ................: 'Sally'
          age .................: 55
        Person
          name ................: 'Bob'
          age .................: 25
        >>> sum(person.age for person in people)
        105

        """
        return self.all()

    def __getitem__(self, key):
        """
        return entities or slices of entities by position

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id1 = people.put(Person(name='Sam', age=25))
        >>> id2 = people.put(Person(name='Sally', age=55))
        >>> id3 = people.put(Person(name='Bob', age=25))

        >>> print(people)
        person
        _id name  age
        --- ----- ---
          1 Sam    25
          2 Sally  55
          3 Bob    25
        3 person records

        >>> people[0]
        <Person {'name': 'Sam', 'age': 25}>

        >>> people[1]
        <Person {'name': 'Sally', 'age': 55}>

        >>> people[-1]
        <Person {'name': 'Bob', 'age': 25}>

        >>> people[0:2]
        [<Person {'name': 'Sam', 'age': 25}>, <Person {'name': 'Sally', 'age': 55}>]

        >>> people[::2]
        [<Person {'name': 'Sam', 'age': 25}>, <Person {'name': 'Bob', 'age': 25}>]

        >>> people[::-2]
        [<Person {'name': 'Bob', 'age': 25}>, <Person {'name': 'Sam', 'age': 25}>]

        >>> people[1:-1]
        [<Person {'name': 'Sally', 'age': 55}>]

        >>> try:
        ...     people[3]
        ... except IndexError as e:
        ...     print(e)
        Index (3) out of range

        >>> db.close()

        """

        n = len(self)
        if isinstance(key, slice):
            # get the start, stop, and step from the slice
            start, stop, step = key.indices(n)
            return [self[ii] for ii in range(start, stop, step)]
        elif isinstance(key, int):
            if key < 0:
                key += n
            elif key >= n:
                raise IndexError('Index ({}) out of range'.format(key))
            cmd = (
                'select distinct row_id '
                'from attributes '
                'where kind="%s" '
                'limit %s, 1'
                ) % (self.kind, key)
            rs = list(self.db(cmd))
            if rs:
                record_id = list(rs)[0][0]
                return self.get(record_id)
            else:
                return 'no records'
        else:
            raise TypeError('Invalid argument type')

    def __str__(self):
        """
        format for humans

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id = people.put(Person(name='Sam', age=25))
        >>> id = people.put(Person(name='Sally', age=55))
        >>> id = people.put(Person(name='Bob', age=25))
        >>> print(people)
        person
        _id name  age
        --- ----- ---
          1 Sam    25
          2 Sally  55
          3 Bob    25
        3 person records

        >>> people.zap()
        >>> print(people)
        Empty list

        """
        return str(self.all())

    def __repr__(self):
        """
        unabiguous representation

        >>> db = gitdata.database.setup_test_database()
        >>> class Person(Record): pass
        >>> class People(EntityStore): pass
        >>> people = People(db, Person)
        >>> id = people.put(Person(name='Sam', age=25))
        >>> id = people.put(Person(name='Sally', age=55))
        >>> id = people.put(Person(name='Bob', age=25))
        >>> repr(people)
        '<EntityStore(Person)>'
        >>> len(people)
        3
        >>> people.zap()
        >>> people
        <EntityStore(Person)>
        >>> len(people)
        0

        """
        return '<EntityStore({})>'.format(self.klass.__name__)


def setup_entity_store(db):
    """Creates the entity store tables"""
    path = os.path.dirname(__file__)
    pathname = os.path.realpath(os.path.join(path, 'entity_store.sql'))
    db.run(pathname)


def store_of(klass, db=None, name=None):
    """Returns a store of the given entity class

    The klass parameter can be a subclass of gitdata.Model or
    an entity kind.  If a gitdata.Model is provided the actual
    entity kind is derived from the class name.  If the entity
    kind is provivded as a string then it's taken as-is.

    Uses the current site database if none is provided.

    >>> db = gitdata.database.setup_test_database()

    >>> class Person(Record): pass
    >>> people = store_of(Person, db=db)
    >>> id = people.put(Person(name='Sam', age=25))
    >>> id = people.put(Person(name='Sally', age=55))
    >>> id = people.put(Person(name='Bob', age=25))
    >>> person = people.first(name='Sally')
    >>> person['age']
    55

    >>> class Thing(Record): pass
    >>> things = store_of(Thing, db, 'person')
    >>> thing = things.first(name='Sam')
    >>> thing.age
    25

    """
    db = db or gitdata.database.connect()
    return EntityStore(db, klass, kind=name)
