# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

from AccessControl.ZopeGuards import guarded_getattr
from AccessControl import ClassSecurityInfo
from zLOG import LOG, WARNING
from Products.ERP5Type.Base import WorkflowMethod
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.utils import _setCacheHeaders, _ViewEmulator
from OFS.Image import Pdata
from Products.ERP5Type import Permissions, PropertySheet
from Products.ERP5.Document.Document import Document, ConversionError,\
                                            NotConvertedError
from Products.ERP5.Document.File import File
from Products.ERP5Type.WebDAVSupport import TextContent
import re
import md5
import cStringIO

# Mixin Import
from Products.ERP5.mixin.cached_convertable import CachedConvertableMixin
from Products.ERP5.mixin.base_convertable import BaseConvertableFileMixin
try:
  from string import Template
except ImportError:
  from Products.ERP5Type.patches.string import Template

DEFAULT_CONTENT_TYPE = 'text/html'
_MARKER = []

class TextDocument(CachedConvertableMixin, BaseConvertableFileMixin,
                                                            TextContent, File):
    """A TextDocument impletents IDocument, IFile, IBaseConvertable, ICachedconvertable
    and ITextConvertable
    """

    meta_type = 'ERP5 Text Document'
    portal_type = 'Text Document'
    add_permission = Permissions.AddPortalContent

    # Declarative security
    security = ClassSecurityInfo()
    security.declareObjectProtected(Permissions.AccessContentsInformation)

    # Declarative properties
    property_sheets = ( PropertySheet.Base
                      , PropertySheet.XMLObject
                      , PropertySheet.CategoryCore
                      , PropertySheet.DublinCore
                      , PropertySheet.Version
                      , PropertySheet.Document
                      , PropertySheet.Snapshot
                      , PropertySheet.ExternalDocument
                      , PropertySheet.Url
                      , PropertySheet.TextDocument
                      , PropertySheet.Data
                      , PropertySheet.Reference
                      )

    def _substituteTextContent(self, text, safe_substitute=True, **kw):
      # If a method for string substitutions of the text content, perform it.
      # Decode everything into unicode before the substitutions, in order to
      # avoid encoding errors.
      method_id = self.getTextContentSubstitutionMappingMethodId()
      if method_id:
        try:
          mapping = guarded_getattr(self, method_id)(**kw)
        except AttributeError:
          LOG('TextDocument', WARNING, 'could not get the substitution'
              ' mapping method %s from %r, so the content will not be'
              ' substituted.' % (method_id, self.getRelativeUrl()))
          return text

        is_str = isinstance(text, str)
        if is_str:
          text = text.decode('utf-8')

        unicode_mapping = {}
        for k, v in mapping.iteritems():
          if isinstance(v, str):
            v = v.decode('utf-8')
          elif not isinstance(v, unicode):
            v = str(v).decode('utf-8')
          unicode_mapping[k] = v

        if safe_substitute:
          text = Template(text).safe_substitute(unicode_mapping)
        else:
          text = Template(text).substitute(unicode_mapping)

        # If the original was a str, convert it back to str.
        if is_str:
          text = text.encode('utf-8')

      return text

    security.declareProtected(Permissions.View, 'asSubjectText')
    def asSubjectText(self, substitution_method_parameter_dict=None, safe_substitute=True, **kw):
      """
        Converts the subject of the document to a textual representation.
      """
      subject = TextDocument.inheritedAttribute('asSubjectText')(self, **kw)
      if substitution_method_parameter_dict is None:
        substitution_method_parameter_dict = {}
      return self._substituteTextContent(subject, safe_substitute=safe_substitute,
                                         **substitution_method_parameter_dict)

    def _convert(self, format, substitution_method_parameter_dict=None,
                safe_substitute=True, charset=None, text_content=None, **kw):
      """
        Convert text using portal_transforms or oood
      """
      src_mimetype = self.getContentType()
      if not format and src_mimetype == 'text/html':
        format = 'html' # Force safe_html
      if not format:
        # can return document without conversion
        return src_mimetype, self.getTextContent()
      portal = self.getPortalObject()
      mime_type = getToolByName(portal, 'mimetypes_registry').\
                                            lookupExtension('name.%s' % format)
      original_mime_type = mime_type = str(mime_type)
      if text_content is None:
        # check if document has set text_content and convert if necessary
        text_content = self.getTextContent()
      if text_content:
        kw['format'] = format
        if not self.hasConversion(**kw):
          portal_transforms = getToolByName(portal, 'portal_transforms')
          filename = self.getSourceReference(self.getTitleOrId())
          if mime_type == 'text/html':
            mime_type = 'text/x-html-safe'
          result = portal_transforms.convertToData(mime_type, text_content,
                                                   object=self, context=self,
                                                   filename=filename,
                                                   mimetype=src_mimetype,
                                                   encoding=charset)
          if result is None:
            raise ConversionError('TextDocument conversion error. '
                                  'portal_transforms failed to convert '\
                                  'to %s: %r' % (mime_type, self))
          self.setConversion(result, original_mime_type, **kw)
        else:
          mime_type, result = self.getConversion(**kw)
        if substitution_method_parameter_dict is None:
          substitution_method_parameter_dict = {}
        result = self._substituteTextContent(result, safe_substitute=safe_substitute,
                                             **substitution_method_parameter_dict)
        return original_mime_type, result
      else:
        # text_content is not set, return empty string instead of None
        return original_mime_type, ''

    def __call__(self):
      _setCacheHeaders(_ViewEmulator().__of__(self), {})
      return Document.__call__(self)

    security.declareProtected(Permissions.AccessContentsInformation, 'getContentBaseURL')
    def getContentBaseURL(self):
      """
        Returns the content base URL based on the actual content
        (in HTML)
      """
      if self.hasTextContent():
        html = self._asHTML()
        base_list = re.findall(self.base_parser, str(html))
        if base_list:
          return base_list[0]
      return Document.getContentBaseURL(self)

    security.declareProtected(Permissions.AccessContentsInformation, 'hasBaseData')
    def hasBaseData(self):
      """
        A TextDocument store its base_data in the "text_content" property.
        Having base_data is equivalent to having some text_content.
      """
      return self.hasTextContent()

    security.declareProtected(Permissions.ModifyPortalContent, 'setBaseData')
    def setBaseData(self, value):
      """Store base_data into text_content
      """
      self._setTextContent(value)

    security.declareProtected(Permissions.ModifyPortalContent, '_setBaseData')
    _setBaseData = setBaseData

    security.declareProtected(Permissions.ModifyPortalContent,
                                                            '_baseSetBaseData')
    _baseSetBaseData = _setBaseData

    security.declareProtected(Permissions.ModifyPortalContent,
                                                          'setBaseContentType')
    def setBaseContentType(self, value):
      """store value into content_type
      """
      self._setContentType(value)

    security.declareProtected(Permissions.ModifyPortalContent,
                                                         '_setBaseContentType')
    _setBaseContentType = setBaseContentType

    security.declareProtected(Permissions.ModifyPortalContent,
                                                     '_baseSetBaseContentType')
    _baseSetBaseContentType = _setBaseContentType

    security.declareProtected(Permissions.AccessContentsInformation,
                                                                 'getBaseData')
    def getBaseData(self, default=_MARKER):
      """
      """
      self._checkConversionFormatPermission(None, **kw)
      if default is _MARKER:
        return self.getTextContent()
      else:
        return self.getTextContent(default=default)

    security.declareProtected(Permissions.AccessContentsInformation,
                                                                 'hasBaseData')
    def hasBaseData(self):
      """
      """
      return self.hasTextContent()

    security.declareProtected(Permissions.AccessContentsInformation,
                                                              'getContentType')
    def getContentType(self, default=_MARKER):
      """Backward compatibility, read content_type
      from text_format property
      """
      if not self.hasContentType():
        # getProperty can not be used
        # because text_format is not registered in local_properties
        if default is _MARKER:
          return getattr(self, 'text_format', None)
        else:
          return getattr(self, 'text_format', default)
      else:
        if default is _MARKER:
          return self._baseGetContentType()
        else:
          return self._baseGetContentType(default)

    # base_convertable support
    security.declareProtected(Permissions.AccessContentsInformation,
                                                 'isSupportBaseDataConversion')
    def isSupportBaseDataConversion(self):
      """
      """
      return True

    def _convertToBaseFormat(self):
      """Conversion to base format for TextDocument consist
      to convert file content into utf-8
      """
      def guessCharsetAndConvert(document, text_content, content_type):
        """
        return encoded content_type and message if encoding
        is not utf-8
        """
        codec = document._guessEncoding(text_content, content_type)
        if codec is not None:
          try:
            text_content = text_content.decode(codec).encode('utf-8')
          except (UnicodeDecodeError, LookupError):
            message = 'Conversion to base format with codec %r fails' % codec
            # try again with another guesser based on file command
            codec = document._guessEncoding(text_content, 'text/plain')
            if codec is not None:
              try:
                text_content = text_content.decode(codec).encode('utf-8')
              except (UnicodeDecodeError, LookupError):
                message = 'Conversion to base format with codec %r fails'\
                                                                        % codec
              else:
                message = 'Conversion to base format with codec %r succeeds'\
                                                                        % codec
          else:
            message = 'Conversion to base format with codec %r succeeds'\
                                                                        % codec
        else:
          message = 'Conversion to base format without codec fails'
        return text_content, message

      content_type = self.getContentType() or DEFAULT_CONTENT_TYPE
      text_content = self.getData()
      if content_type == 'text/html':
        re_match = self.charset_parser.search(text_content)
        message = 'Conversion to base format succeeds'
        if re_match is not None:
          charset = re_match.group('charset')
          if charset.lower() != 'utf-8':
            try:
              # Use encoding in html document
              text_content = text_content.decode(charset).encode('utf-8')
            except (UnicodeDecodeError, LookupError):
              # Encoding read from document is wrong
              text_content, message = guessCharsetAndConvert(self,
                                                    text_content, content_type)
            else:
              message = 'Conversion to base format with charset %r succeeds'\
                                                                      % charset
              charset = 'utf-8' # Override charset if convertion succeeds
              # change charset value in html_document as well
              def subCharset(matchobj):
                keyword = matchobj.group('keyword')
                charset = matchobj.group('charset')
                if not (keyword or charset):
                  # no match, return same string
                  return matchobj.group(0)
                elif keyword:
                  # if keyword is present, replace charset just after
                  return keyword + 'utf-8'
              text_content = self.charset_parser.sub(subCharset, text_content)
        else:
          text_content, message = guessCharsetAndConvert(self,
                                                    text_content, content_type)
      else:
        # generaly text/plain
        try:
          # if succeeds, not need to change encoding
          # it's already utf-8
          text_content.decode('utf-8')
        except (UnicodeDecodeError, LookupError), error_message:
          text_content, message = guessCharsetAndConvert(self,
                                                    text_content, content_type)
        else:
          message = 'Conversion to base format succeeds'
      self._setBaseData(text_content)
      self._setBaseContentType(content_type)
      return message

    # Backward compatibility for replacement of text_format by content_type
    security.declareProtected(Permissions.AccessContentsInformation,
                                                               'getTextFormat')
    def getTextFormat(self, default=_MARKER):
      """
      """
      LOG('TextDocument', WARNING,
                'Usage of text_format is deprecated, use text_content instead')
      return self.getContentType(default)

    security.declareProtected(Permissions.ModifyPortalContent, 'setTextFormat')
    def setTextFormat(self, value):
      """
      """
      LOG('TextDocument', WARNING,
                'Usage of text_format is deprecated, use text_content instead')
      return self.setContentType(value)

    security.declareProtected(Permissions.ModifyPortalContent,
                                                              '_setTextFormat')
    def _setTextFormat(self, value):
      """
      """
      LOG('TextDocument', WARNING,
                'Usage of text_format is deprecated, use text_content instead')
      return self._setContentType(value)