## $Id: export.py 9734 2012-11-28 01:14:33Z uli $
##
## 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
##
"""Exporters for student related stuff.
"""
import grok
from datetime import datetime
from zope.catalog.interfaces import ICatalog
from zope.component import queryUtility
from waeup.kofa.interfaces import MessageFactory as _
from waeup.kofa.students.interfaces import (
    IStudent, IStudentStudyCourse, IStudentStudyLevel, ICourseTicket,
    IStudentOnlinePayment, ICSVStudentExporter, IBedTicket)
from waeup.kofa.utils.batching import ExporterBase
from waeup.kofa.utils.helpers import iface_names

#: A tuple containing all exporter names referring to students or
#: subobjects thereof.
EXPORTER_NAMES = ('students', 'studentstudycourses', 'studentstudylevels',
                  'coursetickets', 'studentpayments')

class StudentsExportFilter(object):
    """A filter to find students that match certain criteria.

    The filter is meant as a component to aggregate search parameters
    for students. This way we can tell some exporter to use 'this
    filter' instead of passing in all (probably different) parameters.

    Just an idea right now. There might be better ways.
    """

    def __init__(self, session=None, level=None,
                 faculty_code=None, department_code=None):
        self.session = session
        self.level = level
        self.faculty_code = faculty_code
        self.department_code = department_code
        return

    def get_students(self, catalog):
        students = catalog.searchResults(
            student_id=(None, None),
            current_session=(self.session, self.session),
            #level=(self.level, self.level), # would be nice
            faccode=(self.faculty_code, self.faculty_code),
            depcode=(self.department_code, self.department_code)
            )
        # filter out by level
        if self.level is not None:
            return [x for x in students
                    if x['studycourse'].current_level == self.level]
        return students

def get_students(site, stud_filter=None):
    """Get all students registered in catalog in `site`.
    """
    catalog = queryUtility(
        ICatalog, context=site, name='students_catalog', default=None)
    if catalog is None:
        return []
    if stud_filter is None:
        students = catalog.searchResults(student_id=(None, None))
    else:
        students = stud_filter.get_students(catalog)
    return students

def get_studycourses(students):
    """Get studycourses of `students`.
    """
    return [x.get('studycourse', None) for x in students
            if x is not None]

def get_levels(students):
    """Get all studylevels of `students`.
    """
    levels = []
    for course in get_studycourses(students):
        for level in course.values():
            levels.append(level)
    return levels

def get_tickets(students):
    """Get all course tickets of `students`.
    """
    tickets = []
    for level in get_levels(students):
        for ticket in level.values():
            tickets.append(ticket)
    return tickets

def get_payments(students):
    """Get all payments of `students`.
    """
    payments = []
    for student in students:
        for payment in student.get('payments', {}).values():
            payments.append(payment)
    return payments

def get_bedtickets(students):
    """Get all bedtickets of `students`.
    """
    tickets = []
    for student in students:
        for ticket in student.get('accommodation', {}).values():
            tickets.append(ticket)
    return tickets

class StudentExporterBase(ExporterBase):
    """Exporter for students or related objects.

    This is a baseclass.
    """
    grok.baseclass()
    grok.implements(ICSVStudentExporter)
    grok.provides(ICSVStudentExporter)

    def export(self, values, filepath=None):
        """Export `values`, an iterable, as CSV file.

        If `filepath` is ``None``, a raw string with CSV data is returned.
        """
        writer, outfile = self.get_csv_writer(filepath)
        for value in values:
            self.write_item(value, writer)
        return self.close_outfile(filepath, outfile)

    def export_filtered(self, site, session, level, faculty_code=None,
                        department_code=None, filepath=None):
        pass


class StudentsExporter(grok.GlobalUtility, StudentExporterBase):
    """Exporter for Students.
    """
    grok.name('students')

    #: Fieldnames considered by this exporter
    fields = tuple(sorted(iface_names(
        IStudent, omit=['loggerInfo']))) + (
        'password', 'state', 'history', 'certcode', 'is_postgrad',
        'current_level', 'current_session')

    #: The title under which this exporter will be displayed
    title = _(u'Students')

    def mangle_value(self, value, name, context=None):
        if name == 'history':
            value = value.messages
        if name == 'phone' and value is not None:
            # Append hash '#' to phone numbers to circumvent
            # unwanted excel automatic
            value = str('%s#' % value)
        return super(
            StudentsExporter, self).mangle_value(
            value, name, context=context)

    def export_all(self, site, filepath=None):
        """Export students into filepath as CSV data.

        If `filepath` is ``None``, a raw string with CSV data is returned.
        """
        return self.export(get_students(site), filepath)

    def export_student(self, student, filepath=None):
        return self.export([student], filepath=filepath)


class StudentStudyCourseExporter(grok.GlobalUtility, StudentExporterBase):
    """Exporter for StudentStudyCourses.
    """
    grok.name('studentstudycourses')

    #: Fieldnames considered by this exporter
    fields = tuple(sorted(iface_names(IStudentStudyCourse))) + ('student_id',)

    #: The title under which this exporter will be displayed
    title = _(u'Student Study Courses')

    def mangle_value(self, value, name, context=None):
        """Treat location values special.
        """
        if name == 'certificate' and value is not None:
            # XXX: hopefully cert codes are unique site-wide
            value = value.code
        if name == 'student_id' and context is not None:
            student = context.student
            value = getattr(student, name, None)
        return super(
            StudentStudyCourseExporter, self).mangle_value(
            value, name, context=context)

    def export_all(self, site, filepath=None):
        """Export study courses into filepath as CSV data.

        If `filepath` is ``None``, a raw string with CSV data is returned.
        """
        return self.export(get_studycourses(get_students(site)), filepath)

    def export_student(self, student, filepath=None):
        """Export studycourse of a single student object.
        """
        return self.export(get_studycourses([student]), filepath)


class StudentStudyLevelExporter(grok.GlobalUtility, StudentExporterBase):
    """Exporter for StudentStudyLevels.
    """
    grok.name('studentstudylevels')

    #: Fieldnames considered by this exporter
    fields = tuple(sorted(iface_names(
        IStudentStudyLevel) + ['level'])) + (
        'student_id', 'number_of_tickets','certcode')

    #: The title under which this exporter will be displayed
    title = _(u'Student Study Levels')

    def mangle_value(self, value, name, context=None):
        """Treat location values special.
        """
        if name == 'student_id' and context is not None:
            student = context.student
            value = getattr(student, name, None)
        return super(
            StudentStudyLevelExporter, self).mangle_value(
            value, name, context=context)

    def export_all(self, site, filepath=None):
        """Export study levels into filepath as CSV data.

        If `filepath` is ``None``, a raw string with CSV data is returned.
        """
        return self.export(get_levels(get_students(site)), filepath)

    def export_student(self, student, filepath=None):
        return self.export(get_levels([student]), filepath)

class CourseTicketExporter(grok.GlobalUtility, StudentExporterBase):
    """Exporter for CourseTickets.
    """
    grok.name('coursetickets')

    #: Fieldnames considered by this exporter
    fields = tuple(sorted(iface_names(ICourseTicket) +
        ['level', 'code'])) + ('student_id', 'certcode')

    #: The title under which this exporter will be displayed
    title = _(u'Course Tickets')

    def mangle_value(self, value, name, context=None):
        """Treat location values special.
        """
        if context is not None:
            student = context.student
            if name == 'student_id' and student is not None:
                value = getattr(student, name, None)
            if name == 'level':
                value = getattr(context, 'getLevel', lambda: None)()
        return super(
            CourseTicketExporter, self).mangle_value(
            value, name, context=context)

    def export_all(self, site, filepath=None):
        """Export course tickets into filepath as CSV data.

        If `filepath` is ``None``, a raw string with CSV data is returned.
        """
        return self.export(get_tickets(get_students(site)), filepath)

    def export_student(self, student, filepath=None):
        return self.export(get_tickets([student]), filepath)


class PaymentsExporter(grok.GlobalUtility, StudentExporterBase):
    """Exporter for OnlinePayment instances.
    """
    grok.name('studentpayments')

    #: Fieldnames considered by this exporter
    fields = tuple(
        sorted(iface_names(
            IStudentOnlinePayment, exclude_attribs=False))) + (
                'student_id','student_state','current_session')

    #: The title under which this exporter will be displayed
    title = _(u'Student Payments')

    def mangle_value(self, value, name, context=None):
        """Treat location values special.
        """
        if context is not None:
            student = context.student
            if name in ['student_id'] and student is not None:
                value = getattr(student, name, None)
        return super(
            PaymentsExporter, self).mangle_value(
            value, name, context=context)

    def export_all(self, site, filepath=None):
        """Export payments into filepath as CSV data.

        If `filepath` is ``None``, a raw string with CSV data is returned.
        """
        return self.export(get_payments(get_students(site)), filepath)

    def export_student(self, student, filepath=None):
        return self.export(get_payments([student]), filepath)

class BedTicketsExporter(grok.GlobalUtility, StudentExporterBase):
    """Exporter for BedTicket instances.
    """
    grok.name('bedtickets')

    #: Fieldnames considered by this exporter
    fields = tuple(
        sorted(iface_names(
            IBedTicket, exclude_attribs=False))) + (
                'student_id', 'actual_bed_type')

    #: The title under which this exporter will be displayed
    title = _(u'Bed Tickets')

    def mangle_value(self, value, name, context=None):
        """Treat location values and others special.
        """
        if context is not None:
            student = context.student
            if name in ['student_id'] and student is not None:
                value = getattr(student, name, None)
        if name == 'bed' and value is not None:
            value = getattr(value, 'bed_id', None)
        if name == 'actual_bed_type':
            value = getattr(getattr(context, 'bed', None), 'bed_type')
        return super(
            BedTicketsExporter, self).mangle_value(
            value, name, context=context)

    def export_all(self, site, filepath=None):
        """Export payments into filepath as CSV data.

        If `filepath` is ``None``, a raw string with CSV data is returned.
        """
        return self.export(get_bedtickets(get_students(site)), filepath)

    def export_student(self, student, filepath=None):
        return self.export(get_bedtickets([student]), filepath)

class StudentPaymentsOverviewExporter(StudentsExporter):
    """Exporter for students with payment overview.
    """
    grok.name('paymentsoverview')

    curr_year = datetime.now().year
    year_range = range(curr_year - 9, curr_year + 1)
    year_range_tuple = tuple([str(year) for year in year_range])

    #: Fieldnames considered by this exporter
    fields = ('student_id', ) + (
        'state', 'certcode', 'faccode', 'depcode', 'is_postgrad',
        'current_level', 'current_session',
        ) + year_range_tuple

    #: The title under which this exporter will be displayed
    title = _(u'Student Payments Overview')

    def mangle_value(self, value, name, context=None):
        if name in self.year_range_tuple and context is not None:
            value = ''
            for ticket in context['payments'].values():
                if ticket.p_state == 'paid' and \
                    ticket.p_category == 'schoolfee' and \
                    ticket.p_session == int(name):
                    value = ticket.amount_auth
                    break
        return super(
            StudentsExporter, self).mangle_value(
            value, name, context=context)
