"""mapper.py - defines mappers for domain objects, mapping operations"""

import zblog.tables as tables
import zblog.user as user
from zblog.blog import *
from sqlalchemy import *
import sqlalchemy.util as util

def zblog_mappers():
    # User mapper.  Here, we redefine the names of some of the columns
    # to different property names.  normally the table columns are all
    # sucked in automatically.
    mapper(user.User, tables.users, properties={
        'id':tables.users.c.user_id,
        'name':tables.users.c.user_name,
        'group':tables.users.c.groupname,
        'crypt_password':tables.users.c.password,
    })

    # blog mapper.  this contains a reference to the user mapper,
    # and also installs a "backreference" on that relationship to handle it
    # in both ways. this will also attach a 'blogs' property to the user mapper.
    mapper(Blog, tables.blogs, properties={
        'id':tables.blogs.c.blog_id,
        'owner':relation(user.User, lazy=False, backref=backref('blogs', cascade="all, delete-orphan")),
    })

    # topic mapper.  map all topic columns to the Topic class.
    mapper(Topic, tables.topics)
        
    # TopicAssocation mapper.  This is an "association" object, which is similar to
    # a many-to-many relationship except extra data is associated with each pair 
    # of related data.  because the topic_xref table doesnt have a primary key,
    # the "primary key" columns of a TopicAssociation are defined manually here.
    mapper(TopicAssociation,tables.topic_xref, 
                primary_key=[tables.topic_xref.c.post_id, tables.topic_xref.c.topic_id], 
                properties={
                    'topic':relation(Topic, lazy=False),
                })

    # Post mapper, these are posts within a blog.  
    # since we want the count of comments for each post, create a select that will get the posts
    # and count the comments in one query.
    posts_with_ccount = select(
        [c for c in tables.posts.c if c.key != 'body'] + [
            func.count(tables.comments.c.comment_id).label('comment_count')
        ],
        from_obj = [
            outerjoin(tables.posts, tables.comments)
        ],
        group_by=[
            c for c in tables.posts.c if c.key != 'body'
        ]
        ) .alias('postswcount')

    # then create a Post mapper on that query.  
    # we have the body as "deferred" so that it loads only when needed,
    # the user as a Lazy load, since the lazy load will run only once per user and
    # its usually only one user's posts is needed per page,
    # the owning blog is a lazy load since its also probably loaded into the identity map
    # already, and topics is an eager load since that query has to be done per post in any
    # case.
    mapper(Post, posts_with_ccount, properties={
        'id':posts_with_ccount.c.post_id,
        'body':deferred(tables.posts.c.body),
        'user':relation(user.User, lazy=True, backref=backref('posts', cascade="all, delete-orphan")),
        'blog':relation(Blog, lazy=True, backref=backref('posts', cascade="all, delete-orphan")),
        'topics':relation(TopicAssociation, lazy=False, private=True, association=Topic, backref='post')
    }, order_by=[desc(posts_with_ccount.c.datetime)])


    # comment mapper.  This mapper is handling a hierarchical relationship on itself, and contains
    # a lazy reference both to its parent comment and its list of child comments.
    mapper(Comment, tables.comments, properties={
        'id':tables.comments.c.comment_id,
        'post':relation(Post, lazy=True, backref=backref('comments', cascade="all, delete-orphan")),
        'user':relation(user.User, lazy=False, backref=backref('comments', cascade="all, delete-orphan")),
        'parent':relation(Comment, primaryjoin=tables.comments.c.parent_comment_id==tables.comments.c.comment_id, foreignkey=tables.comments.c.comment_id, lazy=True, uselist=False),
        'replies':relation(Comment,primaryjoin=tables.comments.c.parent_comment_id==tables.comments.c.comment_id, lazy=True, uselist=True, cascade="all"),
    })

# we define one special find-by for the comments of a post, which is going to make its own "noload"
# mapper and organize the comments into their correct hierarchy in one pass. hierarchical
# data normally needs to be loaded by separate queries for each set of children, unless you
# use a proprietary extension like CONNECT BY.
def find_by_post(post):
    """returns a hierarchical collection of comments based on a given criterion.  
    uses a mapper that does not lazy load replies or parents, and instead
    organizes comments into a hierarchical tree when the result is produced.
    """
    q = session().query(Comment).options(noload('replies'), noload('parent'))
    comments = q.select_by(post_id=post.id)
    result = []
    d = {}
    for c in comments:
        d[c.id] = c
        if c.parent_comment_id is None:
            result.append(c)
            c.parent=None
        else:
            parent = d[c.parent_comment_id]
            parent.replies.append(c)
            c.parent = parent
    return result

Comment.find_by_post = staticmethod(find_by_post)

def start_session():
    """creates a new session for the start of a request."""
    trans.session = create_session(bind_to=zblog.database.engine )

def session():
    return trans.session
    
