import unittest
from datetime import datetime, timedelta
from oaipmh import client, server, metadata, error

from zope.event import notify
from zope.app.event.objectevent import ObjectModifiedEvent

from zope.app import zapi
from zope.app.intid.interfaces import IIntIds

from hurry.workflow.interfaces import IWorkflowState, IWorkflowInfo
from hurry.file import HurryFile

from documentlibrary.core import document
from documentlibrary.core import oai, interfaces, flow
from documentlibrary.core import timezone

from documentlibrary.core.ftests.test_documentlibrary import\
     DocumentLibraryTestCase

dl_reader = metadata.MetadataReader(
    fields={
    'title':            ('textList', 'dl:dl/dl:title/text()'),
    'description':      ('textList', 'dl:dl/dl:description/text()'),
    'versionstring':    ('textList', 'dl:dl/dl:versionstring/text()'),
    'authors':          ('textList', 'dl:dl/dl:authors/text()'),
    'modificationdate': ('textList', 'dl:dl/dl:modificationdate/text()'),
    'availabledate':    ('textList', 'dl:dl/dl:availabledate/text()'),
    'expirydate':       ('textList', 'dl:dl/dl:expirydate/text()'),
    'last_changed':     ('textList', 'dl:dl/dl:last_changed/text()'),
    'state':            ('textList', 'dl:dl/dl:state/text()'),
    'category':         ('textList', 'dl:dl/dl:category/text()'),
    'foi_topic':        ('textList', 'dl:dl/dl:foi_topic/text()'),
    },
    namespaces={
    'dl' : oai.NS_DL}
    )

metadata_registry = metadata.MetadataRegistry()
metadata_registry.registerReader('dl', dl_reader)

def addDocument(i, cat, foi, access=None):
    intids = zapi.getUtility(IIntIds)
    if access is None:
        access = []
    doc = document.Document(
        title='Foo %s' % i,
        category=intids.getId(cat),
        foi_topic=intids.getId(foi),
        authors=[('Foo', 'Foo', 'foo@infrae.com')],
        modificationdate=timezone.fromInput(datetime(2005, 1, 1)),
        versionstring='Foo',
        availabledate=timezone.fromInput(datetime(2005, 1, 1)),
        expirydate=timezone.fromInput(datetime(2005, 2, 2)),
        file_available=True,
        file=HurryFile('foo.file', 'foo'),
        owner='bar',
        note='',
        access=access)
    cat['documents'][str(i)] = doc
    info = IWorkflowInfo(doc)
    info.fireTransition('submit')
    info.fireTransition('approve')
    info.fireTransition('available')
    return doc

class TestPrivateOAI(DocumentLibraryTestCase):

    def setUp(self):
        super(TestPrivateOAI, self).setUp()
        cat = self.app['categories']['announcements']
        foi = self.app['foi_topics']['governance']
        dt = timezone.fromInput(datetime(2005, 6, 1, 0, 0))
        self.identifiers = identifiers = []
        self.documents = []
        for i in range(100):
            doc = addDocument(i, cat, foi)
            # we have to fake last changed for this test and get it
            # to catalog
            doc.last_changed = dt
            dt = dt + timedelta(1)
            notify(ObjectModifiedEvent(doc))
            identifiers.append(str(IWorkflowState(doc).getId()))
            self.documents.append(doc)
        metadata_registry.registerWriter('dl', oai.privateMetadataWriter)
        provider = oai.DocumentLibraryOAI(
            self.app, 'http://foo', show_private=True)
        s = server.Server(
            provider, metadata_registry, resumption_batch_size=100)
        self.server_client = client.ServerClient(s, metadata_registry)

    def test_getRecord(self):
        i = 0
        for identifier in self.identifiers:
            header, m, about = self.server_client.getRecord(
                metadataPrefix='dl',
                identifier=identifier)
            self.assertEquals(identifier, header.identifier())
            self.assertEquals('Foo %s' % i, m.getField('title')[0])
            i = i + 1

    def test_listIdentifiers(self):
        identifiers = self.server_client.listIdentifiers(metadataPrefix='dl')
        self.assertEquals(100, len(list(identifiers)))
        identifiers = self.server_client.listIdentifiers(
            metadataPrefix='dl',
            from_=datetime(2005, 6, 25))
        self.assertEquals(76, len(list(identifiers)))
        # XXX why 25 and 76?
        identifiers = self.server_client.listIdentifiers(
            metadataPrefix='dl',
            until=datetime(2005, 6, 25))
        self.assertEquals(25, len(list(identifiers)))

    def test_listRecords(self):
        records = self.server_client.listRecords(metadataPrefix='dl')
        self.assertEquals(100, len(list(records)))
        records = self.server_client.listRecords(
            metadataPrefix='dl',
            from_=datetime(2005, 6, 25))
        self.assertEquals(76, len(list(records)))
        # XXX why 25 and 76?
        records = self.server_client.listRecords(
            metadataPrefix='dl',
            until=datetime(2005, 6, 25))
        records = list(records)
        self.assertEquals(25, len(records))

        header, metadata, about = records[0]
        self.assertEquals(
            self.identifiers[0],
            header.identifier())
        self.assertEquals(
            ['Foo 0'],
            metadata.getField('title'))

    def test_expires(self):
        # expire a a document
        IWorkflowInfo(self.documents[10]).fireTransition('expire')
        # we should still see this show up as a deleted entry if
        # we ask for all documents
        identifiers = self.server_client.listIdentifiers(metadataPrefix='dl')
        self.assertEquals(100, len(list(identifiers)))
        # we however also will see it if we ask for the last documents
        # documents, even if it's not in range originally, as its
        # last changed has been changed to today
        identifiers = list(self.server_client.listIdentifiers(
            metadataPrefix='dl',
            from_=datetime(2005, 6, 25)))
        self.assertEquals(77, len(identifiers))
        self.assertEquals(1, countDeleted(identifiers))
        # but if we get the first records, it will be missing, as
        # if it never existed
        identifiers = list(self.server_client.listIdentifiers(
            metadataPrefix='dl',
            until=datetime(2005, 6, 25)))
        self.assertEquals(24, len(identifiers))
        self.assertEquals(0, countDeleted(identifiers))
        # we make a new copy available of the document
        info = IWorkflowInfo(self.documents[10])
        new = info.fireTransition('change_from_expired')
        cat = self.app['categories']['announcements']
        cat['200'] = new
        info = IWorkflowInfo(new)
        info.fireTransition('approve')
        info.fireTransition('available')
        # we expect this document to be in the range, as it'll be the
        # most recently changed one
        identifiers = list(self.server_client.listIdentifiers(
            metadataPrefix='dl',
            from_=datetime(2005, 6, 25)))
        self.assertEquals(77, len(identifiers))
        # but it isn't deleted
        self.assertEquals(0, countDeleted(identifiers))
        # and the old range should still contain 24 undeleted documents
        identifiers = list(self.server_client.listIdentifiers(
            metadataPrefix='dl',
            until=datetime(2005, 6, 25)))
        self.assertEquals(24, len(identifiers))
        self.assertEquals(0, countDeleted(identifiers))

class TestPublicOAI(DocumentLibraryTestCase):

    def setUp(self):
        super(TestPublicOAI, self).setUp()
        cat = self.app['categories']['announcements']
        foi = self.app['foi_topics']['governance']
        dt = timezone.fromInput(datetime(2005, 6, 1, 0, 0))
        self.identifiers = identifiers = []
        self.documents = []
        for i in range(100):
            doc = addDocument(i, cat, foi)
            # we have to fake last changed for this test and get it
            # to catalog
            doc.last_changed = dt
            dt = dt + timedelta(1)
            notify(ObjectModifiedEvent(doc))
            identifiers.append(str(IWorkflowState(doc).getId()))
            self.documents.append(doc)
        metadata_registry.registerWriter('dl', oai.publicMetadataWriter)
        provider = oai.DocumentLibraryOAI(
            self.app, 'http://foo', show_private=False)
        s = server.Server(
            provider, metadata_registry, resumption_batch_size=100)
        self.server_client = client.ServerClient(s, metadata_registry)

    def test_listRecords(self):
        # None of the documents have the zope.Everybody group, so 
        # nothing should be returned - in OAI speak this is a specific error, 
        # so we check for that.
        self.assertRaises(
            error.NoRecordsMatchError,  self.server_client.listRecords, 
            metadataPrefix='dl')
        # Now, set a couple of document's access to 'zope.Everybody', and
        # see if we indeed get one result back.
        for i in range(5):
            doc = self.documents[i]
            doc.access = ['zope.Everybody']
            notify(ObjectModifiedEvent(doc))
        records = self.server_client.listRecords(metadataPrefix='dl')
        self.assertEquals(5, len(list(records)))
        # Add another group; the docs should then still be available
        for i in range(5):
            doc = self.documents[i]
            doc.access = ['zope.Everybody', 'Foo Bar']
            notify(ObjectModifiedEvent(doc))
        records = self.server_client.listRecords(metadataPrefix='dl')
        self.assertEquals(5, len(list(records)))
        # Remove the 'zope.Everybody' group, so nothing should be availble
        # anymore
        for i in range(5):
            doc = self.documents[i]
            doc.access = ['Foo Bar']
            notify(ObjectModifiedEvent(doc))
        self.assertRaises(
            error.NoRecordsMatchError,  self.server_client.listRecords, 
            metadataPrefix='dl')
        
def countDeleted(headers):
    result = 0
    for header in headers:
        if header.isDeleted():
            result += 1
    return result

def test_suite():
    return unittest.TestSuite([
        unittest.makeSuite(TestPrivateOAI),
        unittest.makeSuite(TestPublicOAI),
        ])
