from persistent import Persistent
from zope.interface import implements, directlyProvides
from zope import event

from zope.app import zapi
from zope.app.container.contained import Contained
from zope.app.container.interfaces import INameChooser
from zope.app.intid.interfaces import IIntIds
from zope.app.event import objectevent
from zope.app.securitypolicy.interfaces import IPrincipalRoleManager

from hurry.workflow import interfaces
from hurry.workflow import workflow
from hurry.query.interfaces import IQuery
from hurry.query import Eq, NotEq, In

from documentlibrary.core.interfaces import IUserInfoLookup,\
    IDocumentAlertDates, IDeletedDocument
from documentlibrary.core import timezone

class WorkflowVersions(workflow.WorkflowVersions, Persistent, Contained):
    implements(interfaces.IWorkflowVersions)

    def getVersions(self, state, id):
        q = zapi.getUtility(IQuery)
        return q.searchResults(
            Eq(('document_catalog', 'workflow_state'),
               state) &
            Eq(('document_catalog', 'workflow_id'),
               id))

    def getVersionsWithAutomaticTransitions(self):
        q = zapi.getUtility(IQuery)
        return q.searchResults(
            Eq(('document_catalog', 'workflow_auto'), True))

    def hasVersion(self, state, id):
        return bool(len(self.getVersions(state, id)))

    def hasVersionId(self, id):
        q = zapi.getUtility(IQuery)
        result = q.searchResults(
            Eq(('document_catalog', 'workflow_id'), id))
        return bool(len(result))

SUBMITTED = 0
APPROVED = 1
REJECTED = 2
AVAILABLE = 3
ALERTED = 4
EXPIRED = 5
ARCHIVED = 6
DELETED = 7

_states = {
    SUBMITTED: u'submitted',
    APPROVED: u'approved',
    REJECTED: u'rejected',
    AVAILABLE: u'available',
    ALERTED: u'available', # should not show up in UI
    EXPIRED: u'expired',
    ARCHIVED: u'archived',
    DELETED: u'deleted'
    }

_RAISE = object()

def getNonArchivedStates():
    return [SUBMITTED, APPROVED, REJECTED, AVAILABLE, ALERTED, EXPIRED,
            DELETED]

def getStateName(state, default=_RAISE):
    try:
        return _states[state]
    except KeyError:
        if default is not _RAISE:
            return default
        raise

def availableAction(info, context):
    from zope.security.proxy import removeSecurityProxy
    context = removeSecurityProxy(context)
    manager = IPrincipalRoleManager(context)
    lookup = zapi.getUtility(IUserInfoLookup)
    # apply access rights to public contents
    for groupname in context.access:
        if groupname != 'zope.Everybody':
            group = lookup.getGroupInfo(groupname)
            if group is None:
                continue
            groupname = 'documentlibrary' + group.login
        manager.assignRoleToPrincipal('documentlibrary.DocumentViewer',
                                      groupname)
    info.fireTransitionForVersions(AVAILABLE, 'expire')
    info.fireTransitionForVersions(ALERTED, 'expire_alerted')
    
def availableCondition(info, context):
    return timezone.nowInUTC() >= context.availabledate

def alertCondition(info, context):
    return not context.deletion_requested

def autoAlertCondition(info, context):
    alert_date = IDocumentAlertDates(context).getExpiryAlertDate()
    # never do alerts if alert_period is 0
    if alert_date is None:
        return False
    return timezone.nowInUTC() >= alert_date

def expireAction(info, context):
    from zope.security.proxy import removeSecurityProxy
    context = removeSecurityProxy(context)
    manager = IPrincipalRoleManager(context)
    lookup = zapi.getUtility(IUserInfoLookup)
    # remove access rights from public contents
    for groupname in context.access:
        if groupname != 'zope.Everybody':
            group = lookup.getGroupInfo(groupname)
            if group is None:
                continue
            groupname = 'documentlibrary' + group.login
        manager.unsetRoleForPrincipal('documentlibrary.DocumentViewer',
                                      groupname)
    info.fireTransitionForVersions(EXPIRED, 'archive')

def expireCondition(info, context):
    return not context.deletion_requested

def autoExpireCondition(info, context):
    return timezone.nowInUTC() >= context.expirydate

def copyAction(info, context):
    created = context.copy()
    
    # need to remove security proxy as we don't have acquired
    # permissions yet
    from zope.security.proxy import removeSecurityProxy
    created = removeSecurityProxy(created)

    event.notify(objectevent.ObjectCreatedEvent(created))
    
    # add created to its category
    category = zapi.getUtility(IIntIds).getObject(created.category)
    folder = category['documents']
    name = INameChooser(folder).chooseName('', created)
    folder[name] = created
    return created

def deleteAction(info, context):
    del context.__parent__[context.__name__]
    
def deleteReceiptAction(info, context):
    context.deletion_receipt.deletion_date = timezone.nowInUTC()
    # cannot use directlyProvides on proxied object..
    from zope.security.proxy import removeSecurityProxy
    context = removeSecurityProxy(context)
    directlyProvides(context, IDeletedDocument)
    
def expiredDeleteCondition(info, context):
    # if an available version is present, it's okay to be allowed to
    # delete an expired version without deletion request
    return info.hasVersion(AVAILABLE)

def replaceFromAvailableCondition(info, context):
    return (
        not context.deletion_requested and
        not info.hasVersion(SUBMITTED) and
        not info.hasVersion(APPROVED))

def changeFromExpiredCondition(info, context):
    return (not info.hasVersion(SUBMITTED) and
            not info.hasVersion(APPROVED) and
            not info.hasVersion(AVAILABLE) and
            not info.hasVersion(ALERTED))

def createWorkflow():

    # initial transition
    submit_transition = workflow.Transition(
        transition_id='submit',
        title='submit',
        source=None,
        destination=SUBMITTED,
        template='submit',
        )
    
    approve_transition = workflow.Transition(
        transition_id='approve',
        title='approve',
        source=SUBMITTED,
        destination=APPROVED,
        permission='documentlibrary.ApproveDocument',
        order=2,
        template='approve',
        )

    reject_transition = workflow.Transition(
        transition_id='reject',
        title='reject',
        source=SUBMITTED,
        destination=REJECTED,
        permission='documentlibrary.ApproveDocument',
        order=3,
        template='reject',
        )

    reject_from_approved_transition = workflow.Transition(
        transition_id='reject_from_approved',
        title='reject',
        source=APPROVED,
        destination=REJECTED,
        permission='documentlibrary.ApproveDocument',
        order=3,
        template='reject',
        )

    available_transition = workflow.Transition(
        transition_id='available',
        title='make available',
        source=APPROVED,
        destination=AVAILABLE,
        action=availableAction,
        permission='documentlibrary.ApproveDocument',
        order=4,
        template='available',
        )

    auto_available_transition = workflow.Transition(
        transition_id='auto_available',
        title='make available',
        source=APPROVED,
        destination=AVAILABLE,
        action=availableAction,
        condition=availableCondition,
        trigger=interfaces.AUTOMATIC,
        permission='documentlibrary.ApproveDocument',
        template='available',
        )

    alert_transition = workflow.Transition(
        transition_id='alert',
        title='alert',
        source=AVAILABLE,
        destination=ALERTED,
        condition=alertCondition,
        permission='documentlibrary.ApproveDocument',
        order=5,
        no_edit=True,
        template='alert',
        )

    auto_alert_transition = workflow.Transition(
        transition_id='auto_alert',
        title='alert',
        source=AVAILABLE,
        destination=ALERTED,
        condition=autoAlertCondition,
        trigger=interfaces.AUTOMATIC,
        permission='documentlibrary.ApproveDocument',
        template='alert',
        )
    
    expire_transition = workflow.Transition(
        transition_id='expire',
        title='expire',
        source=AVAILABLE,
        destination=EXPIRED,
        condition=expireCondition,
        action=expireAction,
        permission='documentlibrary.ApproveDocument',
        order=5,
        no_edit=True,
        template='expire',
        )

    auto_expire_transition = workflow.Transition(
        transition_id='auto_expire',
        title='expire',
        source=AVAILABLE,
        destination=EXPIRED,
        action=expireAction,
        condition=autoExpireCondition,
        trigger=interfaces.AUTOMATIC,
        permission='documentlibrary.ApproveDocument',
        template='expire',
        )
    
    expire_alerted_transition = workflow.Transition(
        transition_id='expire_alerted',
        title='expire',
        source=ALERTED,
        destination=EXPIRED,
        condition=expireCondition,
        action=expireAction,
        permission='documentlibrary.ApproveDocument',
        order=5,
        no_edit=True,
        template='expire',
        )

    auto_expire_alerted_transition = workflow.Transition(
        transition_id='auto_expire_alerted',
        title='expire',
        source=ALERTED,
        destination=EXPIRED,
        action=expireAction,
        condition=autoExpireCondition,
        trigger=interfaces.AUTOMATIC,
        permission='documentlibrary.ApproveDocument',
        template='expire',
        )

    archive_transition = workflow.Transition(
        transition_id='archive',
        title='archive',
        source=EXPIRED,
        destination=ARCHIVED,
        permission='documentlibrary.ApproveDocument',
        trigger=interfaces.SYSTEM,
        )

    change_from_submitted_transition = workflow.Transition(
        transition_id='change_from_submitted',
        title='submit changes',
        source=SUBMITTED,
        destination=SUBMITTED,
        permission='documentlibrary.EditDocument',
        order=1,
        template='change',
        )
    
    change_from_approved_transition = workflow.Transition(
        transition_id='change_from_approved',
        title='submit changes',
        source=APPROVED,
        destination=SUBMITTED,
        permission='documentlibrary.EditDocument',
        order=1,
        template='change',
        )

    change_from_rejected_transition = workflow.Transition(
        transition_id='change_from_rejected',
        title='submit changes',
        source=REJECTED,
        destination=SUBMITTED,
        permission='documentlibrary.EditDocument',
        order=1,
        template='change',
        )
    
    replace_from_available_transition = workflow.Transition(
        transition_id='replace_from_available',
        title='replace',
        source=AVAILABLE,
        destination=SUBMITTED,
        action=copyAction,
        condition=replaceFromAvailableCondition,
        permission='documentlibrary.EditDocument',
        order=1,
        template='replace',
        )
    
    replace_from_alerted_transition = workflow.Transition(
        transition_id='replace_from_alerted',
        title='replace',
        source=ALERTED,
        destination=SUBMITTED,
        action=copyAction,
        condition=replaceFromAvailableCondition,
        permission='documentlibrary.EditDocument',
        order=1,
        template='replace',
        )
    
    change_from_expired_transition = workflow.Transition(
        transition_id='change_from_expired',
        title='submit changes',
        source=EXPIRED,
        destination=SUBMITTED,
        action=copyAction,
        condition=changeFromExpiredCondition,
        permission='documentlibrary.EditDocument',
        order=1,
        template='replace',
        )

    delete_from_expired_transition = workflow.Transition(
        transition_id='delete_from_expired',
        title='delete',
        source=EXPIRED,
        destination=None,
        condition=expiredDeleteCondition,
        action=deleteAction,
        permission='documentlibrary.EditDocument',
        order=2,
        no_edit=True,
        template='delete',
        )

    delete_from_rejected_transition = workflow.Transition(
        transition_id='delete_from_rejected',
        title='delete',
        source=REJECTED,
        destination=None,
        action=deleteAction,
        permission='documentlibrary.EditDocument',
        order=2,
        no_edit=True,
        template='delete',
        )

    delete_from_submitted_transition = workflow.Transition(
        transition_id='delete_from_submitted',
        title='delete',
        source=SUBMITTED,
        destination=None,
        action=deleteAction,
        permission='documentlibrary.EditDocument',
        order=2,
        no_edit=True,
        template='delete',
        )

    delete_from_approved_transition = workflow.Transition(
        transition_id='delete_from_approved',
        title='delete',
        source=APPROVED,
        destination=None,
        action=deleteAction,
        permission='documentlibrary.EditDocument',
        order=2,
        no_edit=True,
        template='delete',
        )

    delete_receipt_from_available_transition = workflow.Transition(
        transition_id='delete_receipt_from_available',
        title='delete',
        source=AVAILABLE,
        destination=DELETED,
        action=deleteReceiptAction,
        trigger=interfaces.SYSTEM,
        template='delete_receipt',
        )

    delete_receipt_from_alerted_transition = workflow.Transition(
        transition_id='delete_receipt_from_alerted',
        title='delete',
        source=ALERTED,
        destination=DELETED,
        action=deleteReceiptAction,
        trigger=interfaces.SYSTEM,
        template='delete_receipt',
        )

    delete_receipt_from_expired_transition = workflow.Transition(
        transition_id='delete_receipt_from_expired',
        title='delete',
        source=EXPIRED,
        destination=DELETED,
        action=deleteReceiptAction,
        trigger=interfaces.SYSTEM,
        template='delete_receipt',
        )

    # these transitions don't do anything but actually trigger
    # history/mail sending
    delete_request_from_available_transition = workflow.Transition(
        transition_id='delete_request_from_available',
        title='request deletion',
        source=AVAILABLE,
        destination=AVAILABLE,
        order=1,
        trigger=interfaces.SYSTEM,
        template='delete_request',
        )

    delete_request_from_alerted_transition = workflow.Transition(
        transition_id='delete_request_from_alerted',
        title='request deletion',
        source=ALERTED,
        destination=ALERTED,
        order=1,
        trigger=interfaces.SYSTEM,
        template='delete_request',
        )
    
    delete_request_from_expired_transition = workflow.Transition(
        transition_id='delete_request_from_expired',
        title='request deletion',
        source=EXPIRED,
        destination=EXPIRED,
        order=1,
        trigger=interfaces.SYSTEM,
        template='delete_request',
        )

    return [
        submit_transition,
        approve_transition,
        reject_transition,
        reject_from_approved_transition,
        available_transition,
        auto_available_transition,
        alert_transition,
        auto_alert_transition,
        expire_transition,
        auto_expire_transition,
        expire_alerted_transition,
        auto_expire_alerted_transition,
        archive_transition,
        change_from_submitted_transition,
        change_from_approved_transition,
        change_from_rejected_transition,
        replace_from_available_transition,
        replace_from_alerted_transition,
        change_from_expired_transition,
        delete_from_expired_transition,
        delete_from_rejected_transition,
        delete_from_submitted_transition,
        delete_from_approved_transition,
        delete_receipt_from_available_transition,
        delete_receipt_from_alerted_transition,
        delete_receipt_from_expired_transition,
        delete_request_from_available_transition,
        delete_request_from_alerted_transition,
        delete_request_from_expired_transition,
        ]

def visibleVersions(extra_query=None):
    index = 'document_catalog', 'workflow_state'
    query = In(index, [SUBMITTED, APPROVED, REJECTED,
                       AVAILABLE, ALERTED, EXPIRED])
    if extra_query:
        query = query & extra_query
    q = zapi.getUtility(IQuery)
    # XXX now do something we could prevent if we had a join algorithm
    # that could join on id. This is definitely not fast
    wf_versions = zapi.getUtility(interfaces.IWorkflowVersions)
    for version in q.searchResults(query):
        state = interfaces.IWorkflowState(version)
        s = state.getState()
        id = state.getId()
        if (s == AVAILABLE or s == ALERTED) and hasOtherVersion(
            wf_versions, id, [SUBMITTED, APPROVED, REJECTED]):
            continue
        if s == EXPIRED and hasOtherVersion(
            wf_versions, id, [SUBMITTED, APPROVED, REJECTED,
                              AVAILABLE, ALERTED]):
            continue
        yield version

def allVersions(extra_query=None):
    index = 'document_catalog', 'workflow_state'
    query = In(index, [SUBMITTED, APPROVED, REJECTED, AVAILABLE,
                       ALERTED, EXPIRED, DELETED])
    if extra_query:
        query = query & extra_query
    q = zapi.getUtility(IQuery)
    wf_versions = zapi.getUtility(interfaces.IWorkflowVersions)
    return q.searchResults(query)
        
def hasOtherVersion(wf_versions, id, states):
    for s in states:
        if wf_versions.hasVersion(s, id):
            return True
    return False
