## $Id: catalog.py 9805 2012-12-17 08:05:40Z henrik $
##
## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##
"""Catalog and searching components for academics stuff.
"""
import grok
from grok import index
from hurry.query import Eq, Text
from hurry.query.query import Query
from zope.catalog.interfaces import ICatalog
from zope.component import getUtility
from zope.component.interfaces import ComponentLookupError
from zope.index.text.parsetree import ParseError
from zope.intid import IIntIds
#from waeup.kofa.catalog import QueryResultItem
from waeup.kofa.interfaces import IUniversity, IQueryResultItem
from waeup.kofa.university.interfaces import (
    ICourse, ICertificateCourse, IDepartment,
    ICertificate,
    )

class CourseIndexes(grok.Indexes):
    """This catalog is needed for building sources.
    """
    grok.site(IUniversity)
    grok.name('courses_catalog')
    grok.context(ICourse)

    code = index.Field(attribute='code')
    title = index.Text(attribute='title')

class CertificatesIndexes(grok.Indexes):
    """This catalog is needed for building sources.
    """
    grok.site(IUniversity)
    grok.name('certificates_catalog')
    grok.context(ICertificate)

    code = index.Field(attribute='code')
    application_category = index.Field(attribute='application_category')
    title = index.Text(attribute='title')

class CertificateCoursesIndexes(grok.Indexes):
    """This catalog is needed for automatic removal of certificate courses
    and later for selection of course tickets in the students section.
    """
    grok.site(IUniversity)
    grok.name('certcourses_catalog')
    grok.context(ICertificateCourse)

    course_code = index.Field(attribute='getCourseCode')
    level = index.Field(attribute='level')

@grok.subscribe(ICourse, grok.IObjectAddedEvent)
def handle_course_added(obj, event):
    """Index an add course with the local catalog.

    Courses are not indexed automatically, as they are not a
    dictionary subitem of the accompanied site object
    (`IUniversity`). I.e. one cannot get them by asking for
    ``app['FACCODE']['DEPTCODE']['COURSECODE']`` but one has to ask for
    ``app.faculties['FACCODE']['DEPTCODE'].courses['COURSECODE']``.

    Once, a course is indexed we can leave the further handling to
    the default component architechture. At least removals will
    be handled correctly then (and the course unindexed).
    """
    try:
        cat = getUtility(ICatalog, name='courses_catalog')
    except ComponentLookupError:
        # catalog not available. This might happen during tests.
        return
    intids = getUtility(IIntIds)
    index = cat['code']
    index.index_doc(intids.getId(obj), obj)

@grok.subscribe(ICourse, grok.IObjectAddedEvent)
def handle_certificate_added(obj, event):
    """Index an added certificate with the local catalog.

    See handleCourseAdd.
    """
    try:
      cat = getUtility(ICatalog, name='certificates_catalog')
    except ComponentLookupError:
      # catalog not available. This might happen during tests.
      return
    intids = getUtility(IIntIds)
    index = cat['code']
    index.index_doc(intids.getId(obj), obj)

@grok.subscribe(ICertificateCourse, grok.IObjectAddedEvent)
def handlecertificatecourse_added(obj, event):
    """Index an added certificatecourse with the local catalog.

    See handleCourseAdd.
    """
    try:
      cat = getUtility(ICatalog, name='certcourses_catalog')
    except ComponentLookupError:
      # catalog not available. This might happen during tests.
      return
    intids = getUtility(IIntIds)
    index = cat['course_code']
    index.index_doc(intids.getId(obj), obj)

@grok.subscribe(IDepartment, grok.IObjectRemovedEvent)
def handle_department_removed(obj, event):
    """Clear courses and certificates when a department is killed.
    """
    # We cannot use the 'clear()' method of respective subcontainers
    # (courses, certificates), because that would not trigger
    # IObjectRemoved events.
    for subobj_name in ['courses', 'certificates']:
        key_list = list(getattr(obj, subobj_name, []).keys())
        for key in key_list:
            del getattr(obj, subobj_name)[key]
    return

class CoursesQueryResultItem(object):
    grok.implements(IQueryResultItem)

    def __init__(self, context, view):
        self.context = context
        self.url = view.url(context)
        self.title = context.title
        self.code = context.code
        self.dep = context.__parent__.__parent__.code
        self.fac = context.__parent__.__parent__.__parent__.code
        self.type = 'Course'

class CertificatesQueryResultItem(object):
    grok.implements(IQueryResultItem)

    def __init__(self, context, view):
        self.context = context
        self.url = view.url(context)
        self.title = context.title
        self.code = context.code
        self.dep = context.__parent__.__parent__.code
        self.fac = context.__parent__.__parent__.__parent__.code
        self.type = 'Certificate'

class CertificateCoursesQueryResultItem(object):
    grok.implements(IQueryResultItem)

    def __init__(self, context, view):
        self.context = context
        self.url = view.url(context)
        self.title = context.course.title
        self.code = context.getCourseCode
        self.dep = context.__parent__.__parent__.__parent__.code
        self.fac = context.__parent__.__parent__.__parent__.__parent__.code
        self.type = 'Certificate Course'

def search(query=None, view=None):
    if not query:
        view.flash('Empty search string.')
        return

    hitlist = []
    try:
        results = Query().searchResults(
            Eq(('courses_catalog', 'code'), query))
        for result in results:
            hitlist.append(CoursesQueryResultItem(result, view=view))
        results = Query().searchResults(
            Text(('courses_catalog', 'title'), query))
        for result in results:
            hitlist.append(CoursesQueryResultItem(result, view=view))
        results = Query().searchResults(
            Eq(('certificates_catalog', 'code'), query))
        for result in results:
            hitlist.append(CertificatesQueryResultItem(result, view=view))
        results = Query().searchResults(
            Text(('certificates_catalog', 'title'), query))
        for result in results:
            hitlist.append(CertificatesQueryResultItem(result, view=view))
        results = Query().searchResults(
            Eq(('certcourses_catalog', 'course_code'), query))
        for result in results:
            hitlist.append(CertificateCoursesQueryResultItem(result, view=view))
    except ParseError:
        view.flash('Search string not allowed.')
        return
    return hitlist
