from elixir.statements import Statement
from elixir import Entity, has_field, using_options, Integer, Unicode
from sqlalchemy import and_
import types

class Tag(Entity):
    has_field('target_id', Integer)
    has_field('target_table', Unicode)
    has_field('tagname', Unicode)
    using_options(tablename='tags')


class Taggable(object):
    taggable_entities = []
    
    def __init__(self, entity, *args, **kwargs):
        Taggable.taggable_entities.append(entity)
        
        def get_by_tag(cls, tag):
            return entity.select(and_(Tag.c.target_id==entity.c.id, 
                                      Tag.c.target_table==entity.table.name, 
                                      Tag.c.tagname==tag))
            
        def add_tag(self, tag):
            Tag(target_id=self.id, target_table=self.table.name, tagname=tag)
        
        entity.get_by_tag = classmethod(get_by_tag)
        entity.add_tag = add_tag
    
    
    @classmethod
    def entity_for_table(cls, table):
        if not hasattr(Taggable, 'table_to_entity'):
            Taggable.table_to_entity = dict()
            for entity in Taggable.taggable_entities:
                Taggable.table_to_entity[entity.table.name] = entity
        return Taggable.table_to_entity.get(table)
    
    
    @classmethod
    def get(cls, tag, of_kind=None):
        if not of_kind: of_kind = Taggable.taggable_entities
        if type(of_kind) not in (types.TupleType, types.ListType): 
            of_kind = [of_kind]
        for entity in of_kind:
            for instance in entity.get_by_tag(tag):
                yield instance
    
    
    @classmethod
    def get_fast(cls, tag, of_kind=None):
        if not of_kind: of_kind = Taggable.taggable_entities
        if type(of_kind) not in (types.TupleType, types.ListType):
            of_kind = [of_kind]
        tablenames = [entity.table.name for entity in of_kind]
        for tag in Tag.select(Tag.c.tagname==tag):
            if tag.target_table not in tablenames: continue
            yield tag.target_id, Taggable.entity_for_table(tag.target_table)


acts_as_taggable = Statement(Taggable)
