## $Id: utils.py 11865 2014-10-21 19:16:07Z 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
##
import grok
import random
from time import time
from zope.component import createObject, getUtility
from zope.catalog.interfaces import ICatalog
from waeup.kofa.interfaces import CLEARED, RETURNING, PAID
from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
from waeup.kofa.accesscodes import create_accesscode
from waeup.kofa.interfaces import CLEARED, RETURNING, ADMITTED, IKofaUtils
from waeup.kofa.fees import FeeTable
from waeup.kofa.hostels.hostel import NOT_OCCUPIED
from waeup.kwarapoly.interfaces import MessageFactory as _


# 10  = PreND (1)
# 100 = ND1 (2)
# 110 = ND1R (3)
# 200 = ND2 (4)
# 210 = ND2R (5)
# 300 = ND3 (6)
# 400 = HND1 (7)
# 410 = HND1R (8)
# 500 = HND2 (9)
# 510 = HND2R (10)
# 600 = HND3 (11)
# 999 = PGD (12)

PAYMENT_LEVELS = (10, 100, 110, 200, 210, 300, 400, 410, 500, 510, 600, 999)

FEES_PARAMS = (
        ('ft', 'pt'),
        ('local', 'non-local'),
        ('science','arts'),
        PAYMENT_LEVELS
    )

FEES_VALUES = (
        (
          ( # 10       100      110      200      210     300    400     410      500      510     600   999
            (34500.0, 39400.0, 28800.0, 30500.0, 28800.0, 0.0, 40000.0, 29900.0, 33700.0, 29900.0, 0.0, 48750.0), # science
            (34500.0, 37900.0, 27300.0, 29000.0, 27300.0, 0.0, 38500.0, 28400.0, 32200.0, 28400.0, 0.0, 47200.0)  # arts
          ), # local
          ( # 10       100      110      200      210     300    400     410      500      510     600   999
            (49600.0, 53900.0, 35900.0, 33090.0, 35900.0, 0.0, 56400.0, 38600.0, 36900.0, 38600.0, 0.0, 63180.0), # science
            (49600.0, 52400.0, 34400.0, 31590.0, 34400.0, 0.0, 54900.0, 37100.0, 35400.0, 37100.0, 0.0, 61680.0)  # arts
          ), # non-local
        ), # ft
        (
          ( # 10    100       110    200      210      300      400        410    500     510       600     999
            (0.0, 40700.0, 28800.0, 30900.0, 28800.0, 30900.0, 41100.0, 29900.0, 33050.0, 29900.0, 33050.0, 0.0), # science
            (0.0, 39200.0, 27300.0, 29400.0, 27300.0, 29400.0, 39600.0, 28400.0, 31550.0, 28400.0, 31550.0, 0.0)  # arts
          ), # local
          ( # 10   100         110    200       210      300      400     410      500     510      600     999
            (0.0, 55400.0, 35900.0, 34850.0, 35900.0, 34850.0, 57800.0, 38600.0, 44350.0, 38600.0, 44350.0, 0.0), # science
            (0.0, 53900.0, 34400.0, 33350.0, 34400.0, 33350.0, 56300.0, 37100.0, 42850.0, 37100.0, 42850.0, 0.0)  # arts
          ), # non-local
        ), # pt
    )

SCHOOL_FEES = FeeTable(FEES_PARAMS, FEES_VALUES)

def local_nonlocal(student):
    lga = getattr(student, 'lga')
    if lga and lga.startswith('kwara'):
        return 'local'
    else:
        return 'non-local'

def arts_science(student):
    if student.faccode == 'IFMS':
        return 'arts'
    else:
        return 'science'

def pt_ft(student):
    if student.current_mode.endswith('pt'):
        return 'pt'
    else:
        return 'ft'

class CustomStudentsUtils(NigeriaStudentsUtils):
    """A collection of customized methods.

    """


    def selectBed(self, available_beds):
        """Randomly select a bed from a list of available beds.

        """
        return random.choice(available_beds)

    def getReturningData(self, student):
        """ This method defines what happens after school fee payment
        of returning students depending on the student's senate verdict.
        """
        prev_level = student['studycourse'].current_level
        cur_verdict = student['studycourse'].current_verdict
        if cur_verdict in ('A','B','L','M','N','Z',):
            # Successful student
            new_level = divmod(int(prev_level),100)[0]*100 + 100
        elif cur_verdict == 'C':
            # Student on probation
            new_level = int(prev_level) + 10
        else:
            # Student is somehow in an undefined state.
            # Level has to be set manually.
            new_level = prev_level
        new_session = student['studycourse'].current_session + 1
        return new_session, new_level

    def _maintPaymentMade(self, student, session):
        if len(student['payments']):
            for ticket in student['payments'].values():
                if ticket.p_category == 'hostel_maintenance' and \
                    ticket.p_session == session and ticket.p_state == 'paid':
                        return True
        return False

    def _bedAvailable(self, student):
        acc_details  = self.getAccommodationDetails(student)
        cat = getUtility(ICatalog, name='beds_catalog')
        entries = cat.searchResults(
            owner=(student.student_id,student.student_id))
        if len(entries):
            # Bed has already been booked.
            return True
        entries = cat.searchResults(
            bed_type=(acc_details['bt'],acc_details['bt']))
        available_beds = [
            entry for entry in entries if entry.owner == NOT_OCCUPIED]
        if available_beds:
            # Bed has not yet been booked but beds are available.
            return True
        return False

    def _isPaymentDisabled(self, p_session, category, student):
        academic_session = self._getSessionConfiguration(p_session)
        if category == 'schoolfee':
            if 'sf_all' in academic_session.payment_disabled:
                return True
            if 'sf_non_pg' in academic_session.payment_disabled and \
                not student.is_postgrad:
                return True
        return False

    def setPaymentDetails(self, category, student,
            previous_session=None, previous_level=None):
        """Create Payment object and set the payment data of a student for
        the payment category specified.

        """
        details = {}
        p_item = u''
        amount = 0.0
        error = u''
        if previous_session:
            return _('Previous session payment not yet implemented.'), None
        p_session = student['studycourse'].current_session
        p_level = student['studycourse'].current_level
        p_current = True
        academic_session = self._getSessionConfiguration(p_session)
        if academic_session == None:
            return _(u'Session configuration object is not available.'), None
        # Determine fee.
        if category == 'transfer':
            amount = academic_session.transfer_fee
        elif category == 'gown':
            amount = academic_session.gown_fee
        elif category == 'bed_allocation':
            amount = academic_session.booking_fee
        elif category == 'hostel_maintenance':
            amount = 0.0
            bedticket = student['accommodation'].get(
                str(student.current_session), None)
            if bedticket:
                p_item = bedticket.bed_coordinates
                if bedticket.bed.__parent__.maint_fee > 0:
                    amount = bedticket.bed.__parent__.maint_fee
                else:
                    # fallback
                    amount = academic_session.maint_fee
            else:
                # Should not happen because this is already checked
                # in the browser module, but anyway ...
                portal_language = getUtility(IKofaUtils).PORTAL_LANGUAGE
                p_item = trans(_('no bed allocated'), portal_language)
        elif category == 'clearance':
            amount = academic_session.clearance_fee
            try:
                p_item = student['studycourse'].certificate.code
            except (AttributeError, TypeError):
                return _('Study course data are incomplete.'), None
        elif category == 'schoolfee':
            try:
                certificate = student['studycourse'].certificate
                p_item = certificate.code
            except (AttributeError, TypeError):
                return _('Study course data are incomplete.'), None
            if student.state == RETURNING:
                # Override p_session and p_level
                p_session, p_level = self.getReturningData(student)
                academic_session = self._getSessionConfiguration(p_session)
                if academic_session == None:
                    return _(u'Session configuration object '
                              'is not available.'), None
            if student.state == CLEARED and student.current_mode in (
                                                            'hnd_ft', 'nd_ft'):
                # Fresh students must have booked and paid for accommodation.
                if self._bedAvailable(student):
                    if not self._maintPaymentMade(student, p_session):
                        return _('Book and pay for accommodation first '
                                 'before making school fee payments.'), None
            if student.state in (RETURNING, CLEARED):
                if p_level in PAYMENT_LEVELS:
                    amount = SCHOOL_FEES.get_fee(
                        (pt_ft(student),
                         local_nonlocal(student),
                         arts_science(student),
                         p_level)
                        )
        elif category == 'carryover1':
            amount = 6000.0
        elif category == 'carryover2':
            amount = 7000.0
        elif category == 'carryover3':
            amount = 8000.0

        else:
            fee_name = category + '_fee'
            amount = getattr(academic_session, fee_name, 0.0)
        if amount in (0.0, None):
            return _(u'Amount could not be determined.'), None
        if self.samePaymentMade(student, category, p_item, p_session):
            return _('This type of payment has already been made.'), None
        # Add session specific penalty fee.
        if category == 'schoolfee' and student.is_postgrad:
            amount += academic_session.penalty_pg
        elif category == 'schoolfee':
            amount += academic_session.penalty_ug
        # Recategorize carryover fees.
        if category.startswith('carryover'):
            p_item = getUtility(IKofaUtils).PAYMENT_CATEGORIES[category]
            p_item = unicode(p_item)
            # Now we change the category to reduce the number of categories.
            category = 'schoolfee'
        if self._isPaymentDisabled(p_session, category, student):
            return _('Payment temporarily disabled.'), None
        payment = createObject(u'waeup.StudentOnlinePayment')
        timestamp = ("%d" % int(time()*10000))[1:]
        payment.p_id = "p%s" % timestamp
        payment.p_category = category
        payment.p_item = p_item
        payment.p_session = p_session
        payment.p_level = p_level
        payment.p_current = p_current
        payment.amount_auth = float(amount)
        return None, payment

    def getAccommodationDetails(self, student):
        """Determine the accommodation data of a student.
        """
        d = {}
        d['error'] = u''
        hostels = grok.getSite()['hostels']
        d['booking_session'] = hostels.accommodation_session
        d['allowed_states'] = hostels.accommodation_states
        d['startdate'] = hostels.startdate
        d['enddate'] = hostels.enddate
        d['expired'] = hostels.expired
        # Determine bed type
        studycourse = student['studycourse']
        certificate = getattr(studycourse,'certificate',None)
        current_level = studycourse.current_level
        if None in (current_level, certificate):
            return d
        end_level = certificate.end_level
        if current_level == 10:
            bt = 'pr'
        elif current_level in (100, 400):
            bt = 'fr'
        elif current_level in (300, 600):
            bt = 'fi'
        else:
            bt = 're'
        if student.sex == 'f':
            sex = 'female'
        else:
            sex = 'male'
        special_handling = 'regular'
        if student.faccode == 'ITCH':
            special_handling = 'itch'
        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
        return d

    PWCHANGE_STATES = (ADMITTED, CLEARED, RETURNING)

    # KwaraPoly prefix
    STUDENT_ID_PREFIX = u'W'
