# Copyright (c) 2005 Infrae. All rights reserved.
# See also LICENSE.txt

from datetime import timedelta

from zope.interface import Interface, Attribute, invariant, Invalid
from zope.schema.interfaces import InvalidValue
from zope.app.container.interfaces import INameChooser    
from zope.app.folder.interfaces import IFolder

from zope.schema.interfaces import ITitledTokenizedTerm
from zope.schema import \
    List, TextLine, Choice, Int, Set, Datetime, Text, \
    Tuple, Password, Bool, Object
from zope.schema.vocabulary import SimpleVocabulary
from zope.app.container.constraints import contains, containers
from zope.app.mail.interfaces import ISMTPMailer

from hurry.file.schema import File

class IDocumentLibrary(IFolder):
    """Interface describing the top-level container for the
    DocumentLibrary application.

    All application relevant information is tied to an instance
    implementing this interface.
    
    A DocumentLibrary basically is a Folder, and thus can be turned
    into a site folder.
    """


UNRESTRICTED = u'Unrestricted'
STAFF = u'Staff'
LOGIN_ONLY = u'Login only'
    
VIEWER = u'Viewer'
SUBMITTER = u'Submitter'
LIBRARIAN = u'Librarian'
MANAGER = u'Manager'

_user_roles = SimpleVocabulary.fromValues([VIEWER, SUBMITTER,
                                           LIBRARIAN, MANAGER])

class MustBeBeforeToday(InvalidValue):
    __doc__ = "Date cannot be after today."

class MustBeAfterToday(InvalidValue):
    __doc__ = "Date cannot be before today"
    
def beforeTodayConstraint(value):
    # XXX UTC today?
    import timezone
    if value.date() <= timezone.startOfDay().date():
        return True
    raise MustBeBeforeToday(value)

def afterTodayConstraint(value):
    # XXX UTC today?
    import timezone
    if value.date() >= timezone.startOfDay().date():
        return True
    raise MustBeAfterToday(value)

class IDocumentPayload(Interface):

    file = File(
        title=u'file',
        description=(
            u'Browse and select a document to upload to the library.'),
        required=False
        )

    file_available = Bool(
        title=u'file downloadable',
        description=u'Allow the document to be downloaded from the library.',
        required=False,
        )
    
    pdf = File(
        title=u'PDF',
        description=(
            u'Browse and select a PDF version of the document for '
            u'downloading from the library.'),
        required=False)

    generate_pdf = Bool(
        title=u'generate PDF',
        description=(
            u'Ignore any uploaded PDF, generate a PDF based on the original '
            u'document and allow it to be downloaded from the library.'),
        required=False,
        )
    
    plaintext = File(
        title=u'plain text',
        description=(
            u'Browse and select a plain text version of the document for '
            u'downloading from the library.'),
        required=False)

    generate_plaintext = Bool(
        title=u'generate plain text',
        description=(
            u'Ignore any uploaded text file, generate a new one (based on '
            u'the original document) and allow it to be downloaded from '
            u'the library.'),
        required=False,
        default=True,
        )

    # XXX how does this interact with zipfile uploads?
    @invariant
    def shouldHaveAvailableFiles(doc):
        # we're fine if some file is available
        if (getattr(doc, 'file_available', None) or
            getattr(doc, 'pdf', None) or
            getattr(doc, 'plaintext', None)):
            return
        # we're fine if we intend to create pdf or plaintext versions
        if (getattr(doc, 'generate_pdf', None) or
            getattr(doc, 'generate_plaintext', None)):
            return
        # okay, we're not fine now - no file will be available
        raise Invalid(
            "You must make at least one version of your document "
            "available for download. Please make the original file "
            "downloadable, or upload or generate a PDF and/or plain "
            "text version.")
    
class IDeletionReceipt(Interface):
    requester_name = TextLine(
        title=u'requested by',
        description=(
          u'Name of the person who requested this deletion'),
        default=u'',
        required=False,
        )

    requester_email = TextLine(
        title=u"requester's email address",
        description=(
        u"Requester's email address"),
        default=u'',
        required=False
        )
    
    explanation = Text(
        title=u'reason for deletion',
        description=(
        u'reason for deletion request'),
        default=u'',
        required=False
        )

    deletion_date = Datetime(
        title=u'date of deletion',
        description=(
           u'Deletion date'),
        required=False
        )

class IDocumentMetadata(Interface):

    title = TextLine( 
        title=u'title',
        description=(
            u'If left empty, the title will be derived from the uploaded '
            u'document.'),
        default=u'',
        required=False)

    description = Text(
        title=u'description',
        description=(u'Enter a short description for the document.'),
        default=u'',
        required=False)
    
    category = Choice(
        title=u'category',
        description=(u'Select a category or sub-category for the document.'),
        required=True,
        vocabulary='Category Vocabulary')

    foi_topic = Choice(
        title=u'FOI topic',
        description=(u'Select an FOI topic or sub-topic for the document '
        u'(if applicable).'),
        required=False,
        vocabulary='FOI Topic Vocabulary')

    authors = List(
        title=u'authors', 
        description=(
            u'Each document must have at least one author. Use the + '
            u'button to add extra authors.'),
        value_type=Tuple(),
        default=[],
        required=True)
        
    modificationdate = Datetime(
        title=u'modification date',
        description=(
            u'Enter the date that the uploaded document was last '
            u'modified.'),
        required=True,
        constraint=beforeTodayConstraint)
    
    versionstring = TextLine(
        title=u'version',
        description=(
            u'Enter a version for the document (e.g. 1.0, 1.1, A, B, etc).'),
        required=True)
    
    availabledate = Datetime(
        title=u'available date',
        description=(
            u'Enter the date when the document should be made available '
            u'in the library.'),
        required=True,
        constraint=afterTodayConstraint)
    
    expirydate = Datetime(
        title=u'expiry date',
        description=(
            u'Enter the renewal or expiry date for the document.'),
        required=True)
    
    access = Set(
        title=u'group access',
        description=(
            u'Choose at least one group from the list. Chosen group '
            u'members will be able to view the document when it is '
            u'available in the library. Use Ctrl + mouseclick to select '
            u'multiple groups.'),
        required=False,
        value_type=Choice(vocabulary='All Group Vocabulary'))
    
    last_changed = Datetime(
        title=u'last changed',
        description=(
            u'Datetime indicating the last change made to this document.'),
        required=False)

    owner = TextLine(
        title=u'Reference to the Submitter who uploaded this Document.',
        default=u'',
        required=False)

    note = Text(
        title=u'notes',
        description=(
            u'Enter notes for your own reference or for the attention of '
            u'relevant author(s) or librarian(s).'),
        required=False,
        )

    handle_id = TextLine(
        title=u"permanent address",
        description=(u'Permanent address of document'),
        required=False,
        )

    deletion_requested = Bool(
        title=u"deletion has been requested",
        description=(u'Deletion has been requested'),
        required=False,
        )

    deletion_receipt = Object(IDeletionReceipt)
    
    @invariant
    def expiryDateGreaterThanAvailableDate(doc):
        if (doc.expirydate.date() <
            (doc.availabledate.date() + timedelta(days=1))):
            raise Invalid(
                "Expiry date must be after the available date")
    
    def authorEmails():
        """Return a list of all email addresses of the authors.
        """

    def getCategory():
        """Get the category object of the document.
        """
        
    def getCategoryName():
        """Get the name of the category as string.
        """

    def getFoiTopic():
        """Get FOI Topic object of the document.
        """
        
    def getFoiTopicName():
        """Get the name of the FOI topic as string.
        """

    def getOwnerPrincipal():
        """Get principal object of owner of this document.
        """

    def getStateName():
        """Get name of state.
        """

    # XXX does this really belong here?
    def copy():
        """Produce a copy of this document.
        """

class IDocument(IDocumentPayload, IDocumentMetadata):
    """Document content type to be contained in DocumentFolder instances.
    """

class IDeletedDocument(IDocument):
    """Marker interface to declare a document deleted.
    """
    
class IDownload(Interface):
    data = Attribute("Data to download")

    filename = Attribute("Filename of data to download")

class IDocumentFolder(IFolder):
    """Contained container type to hold Document instances.
    """
    contains(IDocument)

class IDocumentContained(IDocument):
    containers(IDocumentFolder)

class IDocumentAlertDates(Interface):
    def getExpiryAlertDate():
        """The date the document should give an expiry alert.

        None is returned if no expiry is desired.
        """

class IResponsibleDocumentUsers(Interface):
    
    def getLibrarians():
        """List of Librarians for the Document's category (and parent
        categories).
        """

    def getAuthors():
        """List of Authors for the Document.
        """
        
    def getAllResponsible():
        """List fo Librarians for the Document's category (and parent
        categories) and the Authors of the Document.
        """
    
    def getNearestLibarian():
        """First Librarian we can find for the Document.
        """
        
class IHierarchicalTermsUtility(Interface):
    def getTop():
        pass

class ICategorySchema(Interface):

    name = TextLine( 
        title=u'title',
        description=u'The title for the category.',
        default=u'',
        required=True)
    
    retention_period = Int( 
        title=u'retention period',
        description=(
            u'Enforce a calculated "expiry" date for documents by entering '
            u'the number of days here.'),
        default=0,
        min=0,
        max=365*2,
        required=False)
    
    librarians = Set(
        title=u'librarians',
        description=(
            u'Select the librarians who will be in charge of this category.'),
        required=False,
        value_type=Choice(vocabulary='Librarian Vocabulary'))

    submitters = Set(
        title=u'submitters',
        description=(
            u'Select the submitters who will be able to submit documents to '
            u'this category.'),
        required=False,
        value_type=Choice(vocabulary='Submitter Vocabulary'))

    def getOwningLibrarians():
        """Get all librarians that own this category.
        """

    def getNearestLibrarian():
        """Get the nearest librarian of this category.

        Returns None if no librarian can be found.
        """

    def isEmpty():
        """Returns True if this category is empty.
        """
        
class ICategory(IFolder, ICategorySchema):
    pass

class ICategoryTree(IFolder):
    contains(ICategory)

    def getOwnedCategories():
        """Get all categories owned by this user.
        """

    def getOwnedCategoryIds():
        """Get all category ids for categories owned by this user.
        """
        
class ICategoryContained(ICategory):
    contains(ICategory)
    containers(ICategory, ICategoryTree)
    
class ICategoryRoleManager(Interface):
    def removeRoles():
        """Remove all category related roles from this category.
        """

    def addRoles(librarians, submitters):
        """Add roles for librarians and submitters to this category.
        """

    def setRoles(librarians, submitters):
        """First remove old role settings, then add roles.
        """

class IFOITopicSchema(Interface):

    name = TextLine( 
        title=u'title',
        description=u'The title for the FOI Topic.',
        default=u'',
        required=True)
    
class IFOITopic(IFolder, IFOITopicSchema):
    pass

class IFOITopicTree(IFolder):
    contains(IFOITopic)

class IFOITopicContained(IFOITopic):
    contains(IFOITopic)
    containers(IFOITopic, IFOITopicTree)
        
class ITitledTokenizedHierarchicalTerm(ITitledTokenizedTerm):
    
    depth = Attribute('Depth in hierarchical tree of terms')

class IHistoryEntry(Interface):
    """History entries are used to describe which action has taken place on
       a document. These entries are stored separately from the documents
       and are kept when a document is deleted.
    """

    doc_title = TextLine(
        title=u'title',
        description=u'Document title',
        default=u'',
        required=False)

    doc_id = TextLine(
        title=u'id',
        description=u'Document id',
        default=u'',
        required=True)
    
    action = TextLine(
        title=u'action that took place',
        description=u'',
        required=True
        )

    source_state = Int(
        title=u'source workflow state',
        description=u'',
        required=True,
        )

    destination_state = Int(
        title=u'destination workflow state',
        description=u'',
        required=True,
        )
    
    actor_id = TextLine(
        title=u'id of actor',
        description=u'',
        default=u'',
        required=False)

    comment = Text(
        title=u'comment',
        description=u'',
        default=u'',
        required=True)

    timestamp = Datetime(
        title=u'date',
        description=u'Date and time for this entry',
        required=True)

    message = Text(
        title=u'message',
        description=u'Message',
        required=True)
    
class IHistoryFolder(IFolder):
    contains(IHistoryEntry)
    
class IHistoryEntryContained(IHistoryEntry):
    containers(IHistoryFolder)
    
class IUserInfo(Interface):
    """Information object for users in the document library
    """    

    login = TextLine(
        title=u'login',
        description=u'Login name',
        required=True,
        )
    
    password = Password(
        title=u'password',
        description=u'Password',
        required=True,
        )

    role = Choice(
        title=u'role',
        description=u'Select the user role.',
        required=True,
        vocabulary=_user_roles,
        default=VIEWER,
        )
    
    title = TextLine(
        title=u'name',
        description=u'Name for the user',
        required=True,
        )
    
    email = TextLine(
        title=u'email',
        description=u'Email address',
        required=True,
        )
    
    description = Text(
        title=u'description',
        description=u'Description',
        required=False,
        default=u'',
        )

    groups = Set(
        title=u'groups',
        description=u'Groups this user is in',
        required=False,
        value_type=Choice(vocabulary='Group Vocabulary')
        )
    
class IUserFolder(IFolder, INameChooser):
    contains(IUserInfo)

class IUserInfoContained(IUserInfo):
    containers(IUserFolder)

class IGroupInfo(Interface):
    """Information object for groups in the document library.
    """
    login = TextLine(
        title=u'group id',
        description=u'Group ID',
        required=True,
        )

    title = TextLine(
        title=u'group title',
        description=u'Group title',
        required=True
        )
    
    description = Text(
        title=u'description',
        description=u'Description',
        required=False,
        default=u'',
        )

    users = Set(
        title=u'users',
        description=u'Usernames of users in the group',
        required=False,
        value_type=Choice(vocabulary='User Vocabulary')
        )
    
class IGroupFolder(IFolder, INameChooser):
    contains(IGroupInfo)

class IGroupInfoContained(IGroupInfo):
    containers(IGroupFolder)

class IIpRange(Interface):
    # marker schema
    pass

class IIpGroupInfo(Interface):
    """Information object for ip groups.
    """
    login = TextLine(
        title=u'group id',
        description=u'Group ID',
        required=True)

    title = TextLine(
        title=u'group title',
        description=u'Gropu title',
        required=True)

    description = Text(
        title=u'description',
        description=u'Description',
        required=False,
        default=u'',
        )

    ip_ranges = List(
        title=u'ip ranges',
        description=(
          u'Only specified IP numbers will be able to see the document links. '
          'State a single IP, e.g. 10.1.1.1, a range of IPs, e.g. 10.1.1.1 - '
          '10.1.1.255 or an IP/Netmask, e.g. 10.1.1.0/255.255.255.0'),
        value_type=Object(IIpRange),
        default=[],
        required=True)
    
    def inGroup(ip):
        """Returns True if an ip address is in one of the ranges.

        The user whose ip address this belongs to is therefore in the
        group.
        """

class IIpGroupFolder(IFolder, INameChooser):
    contains(IIpGroupInfo)

class IIpGroupInfoContained(IIpGroupInfo):
    containers(IIpGroupFolder)

class IUserInfoLookup(Interface):
    def getUserInfo(username):
        """Given username, return user info.

        If user info cannot be found for this username, return None.
        """

    def getUserInfoDefault(username):
        """Given username, return user info.

        Give default null userinfo based on username if real user info
        cannot be found.
        """

    def getGroupInfo(groupname):
        """Given group name, return group info.

        If group info cannot be found, return None.
        """
        
    def getEmailAddresses(usernames):
        """Given a list of usernames, return email addresses.

        If a user does not have an email address, it doesn't appear in the
        list.
        """

    def getFullname(username):
        """Given a username, return user's full name.

        If username does not have a userinfo associated, return username
        """

    def getUserInfoForEmail(email):
        """Given an email address, return user info object.

        If no user can be found for this email address, return None.
        """

class IOAIQuery(Interface):
    def getDocumentsForIdentifier(identifier):
        """Get all documents (should be one) that exist for identifier.
        """

    def getDocumentsInRange(from_, until):
        """Get all documents in a time range.
        """

class IPersistentSMTPMailer(ISMTPMailer):

    # XXX have to override fields from ISMTPMailer to actually display
    # correctly
    username = TextLine(
        title=u'username',
        description=u'Username used for optional SMTP authentication.',
        default=None,
        required=False)

    password = Password(
        title=u'password',
        description=u'Password used for optional SMTP authentication.',
        default=None,
        required=False)

class ForbiddenFilenameError(Exception):
    pass

class IFilenameValidation(Interface):
    
    forbidden_extensions = TextLine(
        title=u'forbidden file extensions',
        description=u'file extensions that are forbidden.'
        'Use dots in front of the extensions and spaces to separate them '
        '(.exe .com)',
        default=u'',
        required=False,
        )

    forbidden_spaces = Bool(
        title=u'space character forbidden',
        description=u'Select this if space characters are to be forbidden  '
        'in filenames.',
        default=True,
        required=False,
        )
    
    def checkFilename(filename):
        """Check filename.

        Raise ForbiddenFilenameError if filename is not allowed,
        containing the reason.
        """

class ICustomText(Interface):
    frontpage_text = Text(
        title=u'welcome text',
        description=(
        u'Enter welcome text that should appear on the frontpage.'),
        default=u'',
        required=False)

    help_url = TextLine(
        title=u'help URL',
        description=(u'Help URL that should appear in menu.'),
        default=u'',
        required=False)
    
class IMaximumRetentionPeriod(Interface):

    maximum_retention_period = Int(
        title=u'maximum retention period',
        description=(
        u'Maximum allowed retention period for all categories, in days.'),
        default=730,
        required=True)

class ITimezone(Interface):
    
    timezone_name = Choice(
        title=u'Timezone',
        description=(u'Select the timezone for the Document Library.'),
        required=True,
        vocabulary='Timezone Vocabulary',
        default='UTC'
        )
