from DateTime import DateTime
from datetime import date
from kss.core import kssaction
from plone.app.kss.plonekssview import PloneKSSView
from Acquisition import Explicit
from Products.CMFCore.utils import getToolByName
from Products.CMFPlone.utils import getSiteEncoding
from Products.Five import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from zope.interface import Interface
from zope.component import getMultiAdapter
from zope.i18n import translate

from plonehrm.absence.interfaces import IAbsenceAdapter
from plonehrm.absence import AbsenceMessageFactory as _
from plonehrm.absence.absence import AbsenceDateError

MAXITEMS = 3


class IAbsenceListViewlet(Interface):

    def get_absence_ist():
        """Return a list of (maximum is MAXITEMS) absences
        """

    def show_absence_form():
        """Return true if the absence form should be shown
        """

    def show_absence_list():
        """Return true if the absence list should be shown
        """

    def get_current_absence():
        """Return the current absence, if any.
        """


class IAddAbsenceView(Interface):
    """Marker interface to add absencelist
    """


def simple_add_absence(context, text, start_date=None):
    if text is None or text =='':
        message = _(u'msg_no_input', default=u'No input provided')
    else:
        absencelist = IAbsenceAdapter(context)
        if not isinstance(text, unicode):
            text = unicode(text, getSiteEncoding(context))
        if start_date is None:
            start_date = date.today()
        try:
            absencelist.add_absence(text, start_date)
            message = None
        except AbsenceDateError, exc:
            message = exc
    return message


def simple_close_absence(context, length=None, end_date = None):
    """ close the open absence
    """
    absencelist = IAbsenceAdapter(context)
    if end_date is None:
        end_date = date.today()
    try:
        absencelist.close_absence(end_date, length)
        message = None
    except AbsenceDateError, exc:
        message = exc
    return message


class AddAbsence(BrowserView):
    """Add an Absence.

    Note that this is the non-kss version.  We may want to concentrate
    on the kss version instead (see below).
    """

    def __call__(self):
        """Create a new Absence if the inline form is used.

        Only react if the form was submitted
        Only react if someone filled in text for the Absence
        Make a new Absence with the correct text and date.
        When not set, the date will be set to today.
        """
        text = self.request.get('text', None)
        start_date = self.request.get('new_absence_date', None)
        if start_date:
            year = int(self.request.get('new_absence_date_year'))
            month = int(self.request.get('new_absence_date_month'))
            day = int(self.request.get('new_absence_date_day'))
            start_date = date(year, month, day)
        else:
            start_date = date.today()
        message = simple_add_absence(self.context, text, start_date)
        self.context.plone_utils.addPortalMessage(message)
        response = self.request.response
        here_url = self.context.absolute_url()
        response.redirect(here_url)


class AbsenceViewlet(Explicit):
    """A viewlet for seeing and adding absencelist.
    """

    template = ViewPageTemplateFile('absence_viewlet.pt')

    def __init__(self, context, request, view=None, manager=None):
        self.context = context
        self.request = request
        self.view = view
        self.form_state = False
        self.close_form_state = False
        self.edit_form_state = False
        self.error_message = None

        # List of possible status, each one defines how the viewlet
        # is rendered.
        self.status_list = ['list',
                            'add_form',
                            'edit_form',
                            'close_form']
        self.status = 'list'

        self.manager = manager
        membership = getToolByName(self.context, 'portal_membership')
        self.checkPermission = membership.checkPermission
        self.encoding = getSiteEncoding(context)

    def set_status(self, status):
        """ Changes the way the viewlet is rendered.
        """
        if not self.can_edit_absences():
            self.status = 'list'
        elif status in self.status_list:
            self.status = status

    def get_absence_list(self):
        absences = IAbsenceAdapter(self.context)
        absencelist = absences.absencelist
        result = absencelist[-MAXITEMS:]
        result.reverse()
        return result

    def get_current_absence(self):
        absences = IAbsenceAdapter(self.context)
        return absences.current_absence()

    def show_absence_list(self):
        return self.status == 'list'

    def can_edit_absences(self):
        return self.checkPermission('plonehrm: Add absence', self.context)

    def allow_adding_absence(self):
        if not self.can_edit_absences():
            return False
        if IAbsenceAdapter(self.context).is_absent():
            return False
        return True

    def show_close_absence_button(self):
        return IAbsenceAdapter(self.context).is_absent()

    def show_add_absence_button(self):
        if not self.allow_adding_absence():
            return False
        return self.status == 'list'

    def show_absence_form(self):
        if not self.allow_adding_absence():
            return False
        return self.status == 'add_form'

    def show_absence_close_form(self):
        if not self.can_edit_absences():
            return False       
        return self.status == 'close_form'

    def show_absence_edit_form(self):
        if not self.can_edit_absences():
            return False       
        return self.status == 'edit_form'

    def render(self):
        val = self.template()
        #if not isinstance(val, unicode):
        #    val = unicode(val, self.encoding)
        return val


class AbsenceList(BrowserView):
    """ A view for listing the complete absencelist of an employee."""

    def __init__(self, context, request):
        self.context = context
        self.request = request

    def get_absence_list(self):
        absences = IAbsenceAdapter(self.context)
        absencelist = absences.absencelist
        # Create a copy and reverse that copy, instead of mistakenly
        # reversing the original.
        result = absencelist[:]
        result.reverse()
        return result


class AbsenceKss(PloneKSSView):
    """ This class contains every KSS views used in the absence
    viewlet.
    """

    @property
    def lang(self):
        props = getToolByName(self.context, 'portal_properties')
        return props.site_properties.getProperty('default_language')

    def refresh_viewlet(self, status = 'list'):
        """ Sets the view to the asked status and refreshes it.
        """
        core = self.getCommandSet('core')
        selector = core.getHtmlIdSelector('absencelist')

        # Get and configure the view.
        view = self.context.restrictedTraverse('@@plonehrm.absence')
        view.set_status(status)

        # Display it.
        rendered = view.render()
        core.replaceHTML(selector, rendered)

    @kssaction
    def show_list(self):
        """ Shows the list of absences.
        """
        self.refresh_viewlet('list')

    @kssaction
    def show_add_form(self):
        """ Shows the form to add an absence.
        """
        self.refresh_viewlet('add_form')

    @kssaction
    def show_edit_form(self):
        """ Shows the form to edit an absence.
        """
        self.refresh_viewlet('edit_form')

    @kssaction
    def show_close_form(self):
        """ Shows the form to close an absence.
        """
        self.refresh_viewlet('close_form')

    @kssaction
    def cancel(self):
        """ Hides the existing status message and refreshes
        the viewlet.
        """
        self.getCommandSet('plone').issuePortalMessage('')
        self.show_list()


    def validate_add_or_edit_form(self, form):
        """ Checks that the form submitted when adding
        or editing an absence is correct.
        """

        # Checks that every field has been submitted.
        fields = ['text',
                  'new_absence_date',
                  'new_absence_date_year',
                  'new_absence_date_month',
                  'new_absence_date_day']
        for field in fields:
            if not field in form:
                # Should not happen.
                return
            
        errors = []
        # Check that a title has been provided.
        if not form['text']:
            errors.append('absence_viewlet_error_no_description')

        # Check that the date is valid.
        if form['new_absence_date']:
            year = int(form['new_absence_date_year'])
            month = int(form['new_absence_date_month'])
            day = int(form['new_absence_date_day'])

            try:
                start_date = date(year, month, day)
            except:
                errors.append('absence_viewlet_error_invalid_date')
                start_date = date.today()
        else:
            start_date = date.today()

        # Check that the start date is tomorrow or earlier.
        if start_date.toordinal() > date.today().toordinal() + 1:
            errors.append('absence_viewlet_error_date_future')

        return (start_date, errors)

    def clear_errors(self):
        """ Hides every errors displayed in the form.
        """
        errors = ['absence_viewlet_error_no_description',
                  'absence_viewlet_error_invalid_date',
                  'absence_viewlet_error_conflict_date',
                  'absence_viewlet_error_date_future',
                  'absence_viewlet_error_lenght_null',
                  'absence_viewlet_error_lenght_no_integer',
                  'absence_viewlet_error_end_date_before_start_date']

        core = self.getCommandSet('core')
        for error in errors:
            selector = core.getHtmlIdSelector(error)
            core.setAttribute(selector, 'class', 'dont-show')

        selector = core.getHtmlIdSelector('absence_viewlet_global_error')
        core.setAttribute(selector, 'class', 'dont-show')
        core.replaceInnerHTML(selector, '')
        

    def show_errors(self, errors = [], global_error = ''):
        """ Show errors found while validating the form.
        """
        # Clears the previous errors.
        self.clear_errors()
        
        core = self.getCommandSet('core')
        for error in errors:
            selector = core.getHtmlIdSelector(error)
            core.setAttribute(selector, 'class', 'errormessage')

        if global_error:
            selector = core.getHtmlIdSelector('absence_viewlet_global_error')
            core.setAttribute(selector, 'class', 'errormessage')
            core.replaceInnerHTML(selector, global_error)

        status = translate(_(u'msg_absence_error',
                             u'Error have been found while saving the absence'),
                           target_language = self.lang)
        
        self.getCommandSet('plone').issuePortalMessage(status,
                                                       msgtype='Error')

    @kssaction
    def add_absence(self):
        """ Creates the new absence and displays the list again.
        """
        form = self.request.form

        try:
            start_date, errors = self.validate_add_or_edit_form(form)
        except TypeError:
            # Happens when validate_form returned None, meaning
            # that the form has not been correctly submitted.
            # This should not happen.
            return

        # Checks that the date does not conflict with the previous
        # absence.
        absence_list_view = self.context.restrictedTraverse('@@absencelist')
        absence_list = absence_list_view.get_absence_list()

        if (absence_list and
            start_date.toordinal() <= \
            absence_list[0].end_date.toordinal()):
            errors.append("absence_viewlet_error_conflict_date")

        if errors:
            self.show_errors(errors = errors)
            return

        # Now we can add the absence.
        message = simple_add_absence(self.context, form['text'], start_date)

        if message:
            # Should not happen.
            self.show_errors(global_error = message)
            return

        # Display the status success message.
        status = translate(_(u'msg_absence_added',
                             u'Absence added'),
                           target_language = self.lang)
        self.getCommandSet('plone').issuePortalMessage(status)

        # Shows the list again.
        self.show_list()

    @kssaction
    def edit_absence(self):
        """ Updates the current absence text and start date.
        """
        form = self.request.form
        try:
            start_date, errors = self.validate_add_or_edit_form(form)
        except TypeError:
            # Happens when validate_form returned None, meaning
            # that the form has not been correctly submitted.
            # This should not happen.
            return

        # Get the absence list.
        absence_list_view = self.context.restrictedTraverse('@@absencelist')
        absence_list = absence_list_view.get_absence_list()
        if not absence_list:
            # Should not happen.
            return
        
        # Checks that the date does not conflict with the previous
        # absence. As we edit the current absence (which is the last one),
        # the previous absence is absencelist[1].
        if (len(absence_list) > 1 and
            start_date.toordinal() <= \
            absence_list[1].end_date.toordinal()):
            errors.append("absence_viewlet_error_conflict_date")

        if errors:
            self.show_errors(errors = errors)
            return

        current_absence = absence_list[0]
        current_absence.title = form['text']
        current_absence.start_date = start_date

        # Display the status success message.
        status = translate(_(u'msg_absence_edited',
                             u'Absence edited'),
                           target_language = self.lang)
        self.getCommandSet('plone').issuePortalMessage(status)

        # Shows the list again.
        self.show_list()

    @kssaction
    def close_absence(self):
        """ Closes the current absence.
        """
        form = self.request.form

        absence_list_view = self.context.restrictedTraverse('@@absencelist')
        absence_list = absence_list_view.get_absence_list()

        if not absence_list:
            # Should not happen.
            return
        current_absence = absence_list[0]

        # Checks that every fields 
        fields = ['absence_length',
                  'close_absence_date',
                  'close_absence_date_year',
                  'close_absence_date_month',
                  'close_absence_date_day']

        for field in fields:
            if not field in form:
                # Should not happen.
                return

        errors = []

        # Check that the length is valid.
        try:
            absence_lenght = int(form['absence_length'])
        except:
            errors.append('absence_viewlet_error_lenght_no_integer')
            absence_lenght = 1

        if absence_lenght < 1:
            errors.append('absence_viewlet_error_lenght_null')

        # Check that the date is valid and correct.
        if form['close_absence_date']:
            year = int(form['close_absence_date_year'])
            month = int(form['close_absence_date_month'])
            day = int(form['close_absence_date_day'])

            try:
                end_date = date(year, month, day)
            except:
                errors.append('absence_viewlet_error_invalid_date')
                end_date = date.today()
        else:
            end_date = date.today()
        
        # Check that the end date is after the start date.
        if end_date.toordinal() < current_absence.start_date.toordinal():
            errors.append('absence_viewlet_error_end_date_before_start_date')
            
        if errors:
            self.show_errors(errors=errors)
            return

        # Close the absence.
        message = simple_close_absence(self.context, form['absence_length'],
                                       end_date)

        if message:
            # Should not happen.
            self.show_errors(global_error = message)
            return

        # Display the status success message.
        status = translate(_(u'msg_absence_closed',
                             u'Absence closed'),
                           target_language = self.lang)
        self.getCommandSet('plone').issuePortalMessage(status)

        # Shows the list again.
        self.show_list()
