#!/usr/bin/python
#
# Copyright 2009 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Data model classes for parsing and generating XML for the DocList Data API"""

__author__ = 'e.bidelman (Eric Bidelman)'


import re
import atom.core
import atom.data
import gdata.acl.data
import gdata.data

DOCUMENTS_NS = 'http://schemas.google.com/docs/2007'
DOCUMENTS_TEMPLATE = '{http://schemas.google.com/docs/2007}%s'
ACL_FEEDLINK_REL = 'http://schemas.google.com/acl/2007#accessControlList'
REVISION_FEEDLINK_REL = DOCUMENTS_NS + '/revisions'

# XML Namespaces used in Google Documents entities.
DATA_KIND_SCHEME = 'http://schemas.google.com/g/2005#kind'
DOCUMENT_LABEL = 'document'
SPREADSHEET_LABEL = 'spreadsheet'
PRESENTATION_LABEL = 'presentation'
FOLDER_LABEL = 'folder'
PDF_LABEL = 'pdf'

LABEL_SCHEME = 'http://schemas.google.com/g/2005/labels'
STARRED_LABEL_TERM = LABEL_SCHEME + '#starred'
TRASHED_LABEL_TERM = LABEL_SCHEME + '#trashed'
HIDDEN_LABEL_TERM = LABEL_SCHEME + '#hidden'
MINE_LABEL_TERM = LABEL_SCHEME + '#mine'
PRIVATE_LABEL_TERM = LABEL_SCHEME + '#private'
SHARED_WITH_DOMAIN_LABEL_TERM = LABEL_SCHEME + '#shared-with-domain'
VIEWED_LABEL_TERM = LABEL_SCHEME + '#viewed'

DOCS_PARENT_LINK_REL = DOCUMENTS_NS + '#parent'
DOCS_PUBLISH_LINK_REL = DOCUMENTS_NS + '#publish'

FILE_EXT_PATTERN = re.compile('.*\.([a-zA-Z]{3,}$)')
RESOURCE_ID_PATTERN = re.compile('^([a-z]*)(:|%3A)([\w-]*)$')

# File extension/mimetype pairs of common format.
MIMETYPES = {
  'CSV': 'text/csv',
  'TSV': 'text/tab-separated-values',
  'TAB': 'text/tab-separated-values',
  'DOC': 'application/msword',
  'DOCX': ('application/vnd.openxmlformats-officedocument.'
           'wordprocessingml.document'),
  'ODS': 'application/x-vnd.oasis.opendocument.spreadsheet',
  'ODT': 'application/vnd.oasis.opendocument.text',
  'RTF': 'application/rtf',
  'SXW': 'application/vnd.sun.xml.writer',
  'TXT': 'text/plain',
  'XLS': 'application/vnd.ms-excel',
  'XLSX': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  'PDF': 'application/pdf',
  'PNG': 'image/png',
  'PPT': 'application/vnd.ms-powerpoint',
  'PPS': 'application/vnd.ms-powerpoint',
  'HTM': 'text/html',
  'HTML': 'text/html',
  'ZIP': 'application/zip',
  'SWF': 'application/x-shockwave-flash'
  }


def make_kind_category(label):
  """Builds the appropriate atom.data.Category for the label passed in.

  Args:
    label: str The value for the category entry.

  Returns:
    An atom.data.Category or None if label is None.
  """
  if label is None:
    return None

  return atom.data.Category(
    scheme=DATA_KIND_SCHEME, term='%s#%s' % (DOCUMENTS_NS, label), label=label)

MakeKindCategory = make_kind_category

def make_content_link_from_resource_id(resource_id):
  """Constructs export URL for a given resource.

  Args:
    resource_id: str The document/item's resource id. Example presentation:
        'presentation%3A0A1234567890'.

  Raises:
    gdata.client.ValueError if the resource_id is not a valid format.
  """
  match = RESOURCE_ID_PATTERN.match(resource_id)

  if match:
    label = match.group(1)
    doc_id = match.group(3)
    if label == DOCUMENT_LABEL:
      return '/feeds/download/documents/Export?docId=%s' % doc_id
    if label == PRESENTATION_LABEL:
      return '/feeds/download/presentations/Export?docId=%s' % doc_id
    if label == SPREADSHEET_LABEL:
      return ('https://spreadsheets.google.com/feeds/download/spreadsheets/'
              'Export?key=%s' % doc_id)
  raise ValueError, ('Invalid resource id: %s, or manually creating the '
                     'download url for this type of doc is not possible'
                     % resource_id)

MakeContentLinkFromResourceId = make_content_link_from_resource_id


class ResourceId(atom.core.XmlElement):
  """The DocList gd:resourceId element."""
  _qname = gdata.data.GDATA_TEMPLATE  % 'resourceId'


class LastModifiedBy(atom.data.Person):
  """The DocList gd:lastModifiedBy element."""
  _qname = gdata.data.GDATA_TEMPLATE  % 'lastModifiedBy'


class LastViewed(atom.data.Person):
  """The DocList gd:lastViewed element."""
  _qname = gdata.data.GDATA_TEMPLATE  % 'lastViewed'


class WritersCanInvite(atom.core.XmlElement):
  """The DocList docs:writersCanInvite element."""
  _qname = DOCUMENTS_TEMPLATE  % 'writersCanInvite'
  value = 'value'


class QuotaBytesUsed(atom.core.XmlElement):
  """The DocList gd:quotaBytesUsed element."""
  _qname = gdata.data.GDATA_TEMPLATE  % 'quotaBytesUsed'


class Publish(atom.core.XmlElement):
  """The DocList docs:publish element."""
  _qname = DOCUMENTS_TEMPLATE  % 'publish'
  value = 'value'


class PublishAuto(atom.core.XmlElement):
  """The DocList docs:publishAuto element."""
  _qname = DOCUMENTS_TEMPLATE  % 'publishAuto'
  value = 'value'


class PublishOutsideDomain(atom.core.XmlElement):
  """The DocList docs:publishOutsideDomain element."""
  _qname = DOCUMENTS_TEMPLATE  % 'publishOutsideDomain'
  value = 'value'


class DocsEntry(gdata.data.GDEntry):
  """A DocList version of an Atom Entry."""

  last_viewed = LastViewed
  last_modified_by = LastModifiedBy
  resource_id = ResourceId
  writers_can_invite = WritersCanInvite
  quota_bytes_used = QuotaBytesUsed
  feed_link = [gdata.data.FeedLink]

  def get_document_type(self):
    """Extracts the type of document this DocsEntry is.

    This method returns the type of document the DocsEntry represents. Possible
    values are document, presentation, spreadsheet, folder, or pdf.

    Returns:
      A string representing the type of document.
    """
    if self.category:
      for category in self.category:
        if category.scheme == DATA_KIND_SCHEME:
          return category.label
    else:
      return None

  GetDocumentType = get_document_type

  def get_acl_feed_link(self):
    """Extracts the DocsEntry's ACL feed <gd:feedLink>.

    Returns:
      A gdata.data.FeedLink object.
    """
    for feed_link in self.feed_link:
      if feed_link.rel == ACL_FEEDLINK_REL:
        return feed_link
    return None

  GetAclFeedLink = get_acl_feed_link

  def get_revisions_feed_link(self):
    """Extracts the DocsEntry's revisions feed <gd:feedLink>.

    Returns:
      A gdata.data.FeedLink object.
    """
    for feed_link in self.feed_link:
      if feed_link.rel == REVISION_FEEDLINK_REL:
        return feed_link
    return None

  GetRevisionsFeedLink = get_revisions_feed_link

  def in_folders(self):
    """Returns the parents link(s) (folders) of this entry."""
    links = []
    for link in self.link:
      if link.rel == DOCS_PARENT_LINK_REL and link.href:
        links.append(link)
    return links

  InFolders = in_folders


class Acl(gdata.acl.data.AclEntry):
  """A document ACL entry."""


class DocList(gdata.data.GDFeed):
  """The main DocList feed containing a list of Google Documents."""
  entry = [DocsEntry]


class AclFeed(gdata.acl.data.AclFeed):
  """A DocList ACL feed."""
  entry = [Acl]


class Revision(gdata.data.GDEntry):
  """A document Revision entry."""
  publish = Publish
  publish_auto = PublishAuto
  publish_outside_domain = PublishOutsideDomain

  def find_publish_link(self):
    """Get the link that points to the published document on the web.

    Returns:
      A str for the URL in the link with a rel ending in #publish.
    """
    return self.find_url(DOCS_PUBLISH_LINK_REL)

  FindPublishLink = find_publish_link

  def get_publish_link(self):
    """Get the link that points to the published document on the web.

    Returns:
      A gdata.data.Link for the link with a rel ending in #publish.
    """
    return self.get_link(DOCS_PUBLISH_LINK_REL)

  GetPublishLink = get_publish_link


class RevisionFeed(gdata.data.GDFeed):
  """A DocList Revision feed."""
  entry = [Revision]
