#
# SchoolTool - common information systems platform for school administration
# Copyright (c) 2005 Shuttleworth Foundation
#
# 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 pytz
from persistent import Persistent
from datetime import datetime
import rwproperty

from zope.annotation.interfaces import IAttributeAnnotatable
from zope.authentication.interfaces import IUnauthenticatedPrincipal
from zope.container.contained import Contained, NameChooser
from zope.container.interfaces import INameChooser
from zope.container.btree import BTreeContainer
from zope.component import queryUtility
from zope.component import adapter, adapts
from zope.interface import implementer
from zope.interface import implements
from zope.lifecycleevent import ObjectRemovedEvent
from zope.lifecycleevent.interfaces import IObjectRemovedEvent
from zope.publisher.interfaces import IRequest
from zope.publisher.browser import TestRequest
from zope.security import management
from zope.security.proxy import removeSecurityProxy
from zope.traversing.api import getParent

from schooltool.app.app import InitBase, StartUpBase
from schooltool.app.interfaces import ISchoolToolApplication
from schooltool.app.membership import URIGroup
from schooltool.basicperson.interfaces import IBasicPerson
from schooltool.contact.interfaces import IContactable, IContact
from schooltool.course.interfaces import ISectionContainer, ISection
from schooltool.course.section import PersonInstructorsCrowd
from schooltool.group.interfaces import IGroupContainer
from schooltool.intervention import InterventionGettext as _
from schooltool.person.interfaces import IPerson
from schooltool.relationship.relationship import getRelatedObjects
from schooltool.schoolyear.interfaces import ISchoolYear
from schooltool.schoolyear.interfaces import ISchoolYearContainer
from schooltool.schoolyear.subscriber import ObjectEventAdapterSubscriber
from schooltool.securitypolicy import crowds
from schooltool.term.interfaces import IDateManager

import interfaces


def getRequest():
    """Return the request object for the current request.
       In the case of unit testing, return a TestRequest instance."""

    i = management.getInteraction()
    for p in i.participations:
        if IRequest.providedBy(p):
            return p
    return TestRequest()


def convertIdToPerson(id):
    return ISchoolToolApplication(None)['persons'][id]


def convertIdToNameWithSort(id):
    if id[-2] == ':':
        student = convertIdToPerson(id[:-2])
        for index, contact in enumerate(IContactable(student).contacts):
            if id[-1] == str(index + 1):
                first_name = contact.first_name
                last_name = contact.last_name
                break
        else:
            return ('', ''), _('Unknown Contact')
    else:
        person = convertIdToPerson(id)
        first_name = person.first_name
        last_name = person.last_name
    name = '%s %s' % (first_name, last_name)
    sort = (last_name, first_name)
    return sort, name


def convertIdToName(id):
    sort, name = convertIdToNameWithSort(id)
    return name


def convertIdsToNames(ids):
    return [name for sort, name in
        sorted([convertIdToNameWithSort(id) for id in ids])]


def convertIdToEmail(id):
    if id[-2] == ':':
        student = convertIdToPerson(id[:-2])
        for index, contact in enumerate(IContactable(student).contacts):
            if id[-1] == str(index + 1):
                return contact.email or u''
        else:
            raise IndexError
    person = convertIdToPerson(id)
    return IContact(person).email or u''


def convertIdsToEmail(ids):
    return sorted([convertIdToEmail(id) for id in ids])


def getPersonsResponsible(student):
    """Return the list of persons responsible for the student.
       This includes all teachers that teach the student, the student's
       advisors, and all members of the school administation.
       Additionally, we will include the student and the student's
       parents (designated by student id + :1 or :2) in this list
       for the purpose of sending emails, but they will be filtered
       out of any application elements that are for staff only."""

    responsible = [removeSecurityProxy(advisor).username
                       for advisor in student.advisors]

    term = queryUtility(IDateManager).current_term
    groups = IGroupContainer(ISchoolYear(term))
    sections = ISectionContainer(term)

    for member in groups['administrators'].members:
        if member.username not in responsible:
            responsible.append(member.username)

    for section in sections.values():
        if student in section.members:
            for teacher in section.instructors:
                if teacher.username not in responsible:
                    responsible.append(teacher.username)

    responsible.append(student.username)
    for index, contact in enumerate(IContactable(student).contacts):
        responsible.append(student.username + ':%d' % (index + 1))

    return responsible


class InterventionInstructorsCrowd(crowds.Crowd):
    """Crowd of instructors of the student indicated by the given 
       intervention object."""

    title = _(u'Instructors')
    description = _(u'Instructors of a student in any of his sections.')

    def _getSections(self, ob):
        return [section for section in getRelatedObjects(ob, URIGroup)
                if ISection.providedBy(section)]

    def contains(self, principal):
        if IUnauthenticatedPrincipal.providedBy(principal):
            return False
        teacher = IPerson(principal)
        student = removeSecurityProxy(self.context.student)
        for section in self._getSections(student):
            if teacher in section.instructors:
                return True
        return False


class InterventionAdvisorsCrowd(crowds.Crowd):
    """Crowd of advisors of the student indicated by the given 
       intervention object."""

    title = _(u'Advisors')
    description = _(u'Advisors of a student.')

    def contains(self, principal):
        if IUnauthenticatedPrincipal.providedBy(principal):
            return False
        teacher = IPerson(principal)
        student = removeSecurityProxy(self.context.student)
        return teacher in student.advisors


class BaseResponsibleCrowd(crowds.Crowd):
    """Crowd of any user who is on the list of persons responsible for the
       message or goal."""

    def contains(self, principal):
        if IUnauthenticatedPrincipal.providedBy(principal):
            return False
        if not interfaces.IInterventionMarker.providedBy(self.context):
            return False
        responsible = interfaces.IInterventionPersonsResponsible(self.context)
        return IPerson(principal).username in responsible


class StaffResponsibleCrowd(BaseResponsibleCrowd):
    """Crowd of school staff who are on the list of persons responsible for the
       message or goal."""

    title = _(u'Staff responsible')
    description = _(u'Staff members responsible for the message or goal.')

    def contains(self, principal):
        if not super(StaffResponsibleCrowd, self).contains(principal):
            return False
        student = removeSecurityProxy(self.context.student)
        return IPerson(principal).username != student.username


class StudentResponsibleCrowd(BaseResponsibleCrowd):
    """Crowd containing the student who is in the list of persons responsible
       for the message or goal."""

    title = _(u'Students responsible')
    description = _(u'Students responsible for the message or goal.')

    def contains(self, principal):
        if not super(StudentResponsibleCrowd, self).contains(principal):
            return False
        student = removeSecurityProxy(self.context.student)
        return IPerson(principal).username == student.username


class InterventionRoot(BTreeContainer):
    """Container of InterventionSchoolYear objects."""

    implements(interfaces.IInterventionRoot)


class InterventionSchoolYear(BTreeContainer):
    """Container of InteventionStudent objects."""

    implements(interfaces.IInterventionSchoolYear)


class InterventionStudent(BTreeContainer):
    """Container of the student's intervention containers."""

    implements(interfaces.IInterventionStudent)

    def __init__(self):
        super(InterventionStudent, self).__init__()

    @property
    def student(self):
        app = ISchoolToolApplication(None)
        if self.__name__ in app['persons']:
            return removeSecurityProxy(app['persons'][self.__name__])
        else:
            return None


class InterventionMessages(BTreeContainer):
    """Container of Tier1 InteventionMessage objects."""

    implements(interfaces.IInterventionMessages)

    @property
    def student(self):
        try:
            return removeSecurityProxy(self.__parent__).student
        except:
            return None


class InterventionMessage(Persistent, Contained):
    """Intervention message about a given student."""

    implements(interfaces.IInterventionMessage, IAttributeAnnotatable)

    created = None

    def __init__(self, sender, recipients, body, status_change=False):
        self.sender = sender
        self.recipients = recipients
        self.body = body
        self.status_change = status_change
        self.created = pytz.UTC.localize(datetime.utcnow())

    @property
    def student(self):
        try:
            student = removeSecurityProxy(self.__parent__).student
            return removeSecurityProxy(student)
        except:
            return None


class InterventionGoals(BTreeContainer):
    """Container of InterventionGoal objects."""

    implements(interfaces.IInterventionGoals)

    @property
    def student(self):
        try:
            return removeSecurityProxy(self.__parent__).student
        except:
            return None


class InterventionGoal(Persistent, Contained):
    """Intervention goal for a given student."""
    implements(interfaces.IInterventionGoal, IAttributeAnnotatable)

    creator = None
    created = None
    at_one_time_responsible = []
    _persons_responsible = []

    def __init__(self, presenting_concerns, goal, strengths, indicators,
                 intervention, timeline, persons_responsible, goal_met=False,
                 follow_up_notes=u'', creator=None):
        self.presenting_concerns = presenting_concerns
        self.goal = goal
        self.strengths = strengths
        self.indicators = indicators
        self.intervention = intervention
        self.timeline = timeline
        self.persons_responsible = persons_responsible
        self.goal_met = goal_met
        self.follow_up_notes = follow_up_notes
        self.notified = False
        self.creator = creator
        self.created = pytz.UTC.localize(datetime.utcnow())

    @property
    def student(self):
        try:
            student = removeSecurityProxy(self.__parent__).student
            return removeSecurityProxy(student)
        except:
            return None

    @rwproperty.getproperty
    def persons_responsible(self):
        return self._persons_responsible

    @rwproperty.setproperty
    def persons_responsible(self, value):
        self._persons_responsible = value
        new = list(set(self.at_one_time_responsible).union(value))
        self.at_one_time_responsible = new


def setUpInterventions(app):
    app[u'schooltool.interventions'] = InterventionRoot()


class InterventionStartup(StartUpBase):
    def __call__(self):
        if u'schooltool.interventions' not in self.app:
            setUpInterventions(self.app)


class InterventionInit(InitBase):
    """Create the InterventionRoot object."""

    def __call__(self):
        setUpInterventions(self.app)


@adapter(ISchoolYear)
@implementer(interfaces.IInterventionSchoolYear)
def getSchoolYearInterventionSchoolYear(schoolyear):
    return getInterventionSchoolYear(schoolyear.__name__)


@adapter(ISchoolToolApplication)
@implementer(interfaces.IInterventionSchoolYear)
def getSchoolToolApplicationInterventionSchoolYear(app):
    return getInterventionSchoolYear()


@adapter(interfaces.IInterventionSchoolYear)
@implementer(ISchoolYear)
def getInterventionSchoolYearSchoolYear(schoolyear):
    app = ISchoolToolApplication(None)
    return ISchoolYearContainer(app)[schoolyear.__name__]


@adapter(interfaces.IInterventionMarker)
@implementer(interfaces.IInterventionStudent)
def getMarkerInterventionStudent(marker):
    obj = marker.__parent__.__parent__
    if interfaces.IInterventionStudent.providedBy(obj):
        return obj
    student = obj.__parent__.student
    interventionStudent = removeSecurityProxy(obj.year[student.username])
    return interventionStudent


def getInterventionSchoolYearFromObj(obj):
    while not interfaces.IInterventionSchoolYear.providedBy(obj):
        if interfaces.IStudentSchoolYearProxy.providedBy(obj):
            return obj.year
        obj = getParent(obj)
    return obj


def getInterventionSchoolYear(schoolYearId=None):
    """Get InterventionSchoolYear object for the given school year.
       If school year not specified, use current school year."""

    app = ISchoolToolApplication(None)
    interventionRoot = app[u'schooltool.interventions']
    if not schoolYearId:
        term = queryUtility(IDateManager).current_term
        schoolyear = ISchoolYear(term)
        schoolYearId = schoolyear.__name__
    try:
        interventionSchoolYear = interventionRoot[schoolYearId]
    except KeyError:
        interventionSchoolYear = InterventionSchoolYear()
        interventionRoot[schoolYearId] = interventionSchoolYear
    return interventionSchoolYear


def getInterventionStudent(studentId, schoolYearId=None):
    """Get InterventionStudent object for the given student, school year pair.
       If school year not specified, use current school year.
       Create InterventionSchoolYear and InterventionStudent objects if
       not already present."""

    interventionSchoolYear = getInterventionSchoolYear(schoolYearId)
    try:
        interventionStudent = interventionSchoolYear[studentId]
    except KeyError:
        interventionStudent = InterventionStudent()
        interventionSchoolYear[studentId] = interventionStudent
        interventionStudent[u'messages'] = InterventionMessages()
        interventionStudent[u'goals'] = InterventionGoals()
    return interventionStudent


class SequenceNumberNameChooser(NameChooser):
    """A name chooser that returns a sequence number."""

    implements(INameChooser)

    def chooseName(self, name, obj):
        """See INameChooser."""
        numbers = [int(v.__name__) for v in self.context.values()
                   if v.__name__.isdigit()]
        if numbers:
            n = str(max(numbers) + 1)
        else:
            n = '1'
        self.checkName(n, obj)
        return n


class PersonRemovedSubsciber(ObjectEventAdapterSubscriber):
    adapts(IObjectRemovedEvent, IBasicPerson)

    def __call__(self):
        app = ISchoolToolApplication(None)
        interventionRoot = app[u'schooltool.interventions']
        for schoolYear in interventionRoot.values():
            if self.object.username in schoolYear:
                del schoolYear[self.object.username]


class SchoolYearRemovedSubsciber(ObjectEventAdapterSubscriber):
    adapts(IObjectRemovedEvent, ISchoolYear)

    def __call__(self):
        app = ISchoolToolApplication(None)
        interventionRoot = app[u'schooltool.interventions']
        if self.object.__name__ in interventionRoot:
            del interventionRoot[self.object.__name__]

