from zope.formlib import form

from zope.app import zapi
from zope.app.intid.interfaces import IIntIds
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.component.hooks import getSite
from zope.app.publisher.browser.menu import getMenu

from hurry.workflow.interfaces import IWorkflow, IWorkflowState,\
     IWorkflowInfo, IWorkflowVersions, InvalidTransitionError
from hurry.query import Eq, NotEq, Text, Between, And, Or, In
from hurry.query.set import AnyOf

from documentlibrary.core import flow, interfaces, timezone
from documentlibrary.core.browser.feedback import IFeedback
from documentlibrary.core.browser.form import FormMixin
from documentlibrary.core.browser import batch

class DocumentListPage(form.PageForm, FormMixin):

    form_fields = ()
    actions = ()
    
    label = u'Documents list'
    
    template = ViewPageTemplateFile('templates/documents.pt')
    
    def items(self):        
        documents = self.documents()
        
        if documents is None:
            return []
        
        intid = zapi.getUtility(IIntIds)
        wf_versions = zapi.getUtility(IWorkflowVersions)
        result = []
        for document in documents:
            wf_state = IWorkflowState(document)
            state_name = flow.getStateName(wf_state.getState())
            alerted_css_class = None
            if wf_state.getState() == flow.ALERTED:
                alerted_css_class = 'alerted'
            result.append(
                {'id': intid.getId(document),
                'document': document,
                'state': state_name,
                'other_states': self.getOtherStateInfo(wf_versions, wf_state),
                'alerted_css_class': alerted_css_class, 
                'last_changed': document.last_changed # for sorting later on
                })

        # XXX sorting the results for each batch isn't too efficient, but
        # oh well
        # sort by last_changed, reversed so most recent first
        result.sort(key=lambda o:o['last_changed'])
        result.reverse()
        return result
    
    def batched_items(self):
        items = self.items()
        form = self.request.form
        start = int(form.get('start', 0))
        size = int(form.get('size', 20))
        return batch.Batch(items, start, size)

    def url(self):
        return zapi.absoluteURL(self, self.request)
    
    def getOtherStateInfo(self, wf_versions, wf_state):
        state = wf_state.getState()
        id = wf_state.getId()
        for possible_state in flow.getNonArchivedStates():
            if possible_state == state:
                continue
            versions = wf_versions.getVersions(possible_state, id)
            if not versions:
                continue
            assert len(versions) == 1
            version = iter(versions).next()
            url = (zapi.absoluteURL(version, self.request) +
                   '/@@display_document')
            yield {
                'state': flow.getStateName(possible_state),
                'url': url,
                }
    
    def getSelectedDocuments(self):
        form = self.request.form
        # the checkboxes' name
        ids = form.get(self.prefix+'.ids', [])
        ids = [int(id) for id in ids]
        intid = zapi.getUtility(IIntIds)
        for id in ids:
            try:
                yield intid.getObject(id)
            except KeyError:
                # if any documents are gone, silently ignore them
                pass

    def documents(self):
        raise NotImplementedError

    def bulkWorkflow(self, transition_id):
        workflow = zapi.getUtility(IWorkflow)
        transition = workflow.getTransitionById(transition_id)
        
        handled = []
        not_handled = []
        for document in self.getSelectedDocuments():
            try:
                IWorkflowInfo(document).fireTransition(transition_id)
            except InvalidTransitionError:
                not_handled.append(document)
            else:
                handled.append(document)
        if handled:
            self.addFeedback('Action %s for: %s' %
                             (transition.title, self._listDocuments(handled)))
        if not_handled:
            self.addError(
                'Action %s failed for: %s' %
                (transition.title, self._listDocuments(not_handled)))

    def bulkWorkflowToward(self, state, title):
        handled = []
        not_handled = []
        for document in self.getSelectedDocuments():
            info = IWorkflowInfo(document)
            try:
                info.fireTransitionToward(state)
                handled.append(document)
            except InvalidTransitionError:
                not_handled.append(document)
        if handled:
            self.addFeedback('Action %s for: %s' %
                             (title, self._listDocuments(handled)))
        if not_handled:
            self.addError(
                'Action %s failed for: %s' %
                (title, self._listDocuments(not_handled)))
        
    def _listDocuments(self, documents):
        return ', '.join(['"%s"' % doc.title for doc in documents])

class MySubmissionsPage(DocumentListPage):

    label = u'My authored documents'
    
    def documents(self):
        principal_id = self.request.principal.id
        # XXX principal prefix handling sucks.
        if principal_id.startswith('documentlibrary'):
            principal_id = principal_id[len('documentlibrary'):]
        user_info = zapi.getUtility(
            interfaces.IUserInfoLookup).getUserInfo(principal_id)
        if user_info is None:
            return []
        return flow.allVersions(
            AnyOf(('document_catalog', 'emails'), [user_info.email]))

    @form.action('delete')
    def handleDeleteAction(self, form, data):
        self.bulkWorkflowToward(None, 'delete')
        
class SubmissionsPage(DocumentListPage):

    label = u'Submissions'

    def documents(self):
        category_ids = getSite()['categories'].getOwnedCategoryIds()
        return flow.allVersions(
            And(
            In(('document_catalog', 'category'), category_ids),
            Eq(('document_catalog', 'workflow_state'), flow.SUBMITTED)))

    @form.action('approve')
    def handleApproveAction(self, form, data):
        self.bulkWorkflow('approve')
        
    @form.action('reject')
    def handleRejectAction(self, form, data):
        self.bulkWorkflow('reject')

    @form.action('delete')
    def handleDeleteAction(self, form, data):
        self.bulkWorkflowToward(None, 'delete')
        
class ApprovedPage(DocumentListPage):

    label = u'Approved documents'

    def documents(self):
        category_ids = getSite()['categories'].getOwnedCategoryIds()
        return flow.allVersions(
            And(In(('document_catalog', 'category'), category_ids),
                Eq(('document_catalog', 'workflow_state'), flow.APPROVED)))

    @form.action('reject')
    def handleRejectAction(self, form, data):
        self.bulkWorkflow('reject_from_approved')

    @form.action('make available')
    def handleMakeAvailableAction(self, form, data):
        self.bulkWorkflow('available')
        
    @form.action('delete')
    def handleDeleteAction(self, form, data):
        self.bulkWorkflowToward(None, 'delete')
        
class AvailablePage(DocumentListPage):
    label = u'Available documents'

    def documents(self):
        category_ids = getSite()['categories'].getOwnedCategoryIds()
        return flow.allVersions(
            And(In(('document_catalog', 'category'), category_ids),
                In(('document_catalog', 'workflow_state'),
                   [flow.AVAILABLE, flow.ALERTED])))

    @form.action('expire')
    def handleExpireAction(self, form, data):
        self.bulkWorkflowToward(flow.EXPIRED, 'expire')

class ExpiredPage(DocumentListPage):
    label = u'Expired documents'

    def documents(self):
        category_ids = getSite()['categories'].getOwnedCategoryIds()
        return flow.allVersions(
            And(In(('document_catalog', 'category'), category_ids),
                Eq(('document_catalog', 'workflow_state'), flow.EXPIRED)))

    @form.action('delete')
    def handleDeleteAction(self, form, data):
        self.bulkWorkflowToward(None, 'delete')
    
class RejectedPage(DocumentListPage):
    label = u'Rejected documents'

    def documents(self):
        category_ids = getSite()['categories'].getOwnedCategoryIds()
        return flow.allVersions(
            And(In(('document_catalog', 'category'), category_ids),
                Eq(('document_catalog', 'workflow_state'), flow.REJECTED)))

    @form.action('delete')
    def handleEmptyAction(self, form, action):
        self.bulkWorkflowToward(None, 'delete')

class DeletionRequestedPage(DocumentListPage):
    label = u'Documents to be deleted'

    def documents(self):
        category_ids = getSite()['categories'].getOwnedCategoryIds()
        return flow.allVersions(
            And(In(('document_catalog', 'category'), category_ids),
                In(('document_catalog', 'workflow_state'),
                   [flow.AVAILABLE, flow.ALERTED, flow.EXPIRED]),
                Eq(('document_catalog', 'deletion_requested'), True)))

class DeletedPage(DocumentListPage):
    label = u'View deletion receipts'

    def documents(self):
        category_ids = getSite()['categories'].getOwnedCategoryIds()
        return flow.allVersions(
            And(In(('document_catalog', 'category'), category_ids),
                In(('document_catalog', 'workflow_state'),
                   [flow.DELETED])))
    
class CategoryDocumentsPage(DocumentListPage):
    
    label = u'Documents in &#xab;%s&#xbb;'
    
    template = ViewPageTemplateFile('templates/category.pt')
    
    def __init__(self, context, request):
        super(CategoryDocumentsPage, self).__init__(context, request)
        self.label = self.label % self.context.name
    
    def documents(self):
        intid = zapi.getUtility(IIntIds)
        category_id = intid.getId(self.context)
        return flow.allVersions(
            Eq(('document_catalog', 'category'), category_id))

    # XXX can these be removed? FormMixin is in base classes
    def useFeedback(self):
        return IFeedback(self.request).useFeedback()

    def useErrors(self):
        return IFeedback(self.request).useErrors()

    def getMenu(self):
        return getMenu('category_menu', self.context, self.request)

class DateDisplay(object):
    
    format = '%d-%b-%Y'
    
    def __init__(self, context, request):
        self.context = context
        self.request = request
        
    def __call__(self):
        return timezone.forDisplay(self.context).strftime(self.format)
    
class DateTimeDisplay(DateDisplay):
    
    format = '%d-%b-%Y %H:%M'
    
