##############################################################################
#
# Copyright (c) 2005 Nexedi SARL and Contributors. All Rights Reserved.
#                    Yoshinori Okuji <yo@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 Acquisition import Implicit

import time, os
from Products.ERP5Type.Utils import convertToUpperCase
from MethodObject import Method
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions

try:
  import pysvn

  class SubversionError(Exception):
    """The base exception class for the Subversion interface.
    """
    pass
    
  class SubversionInstallationError(SubversionError):
    """Raised when an installation is broken.
    """
    pass
    
  class SubversionTimeoutError(SubversionError):
    """Raised when a Subversion transaction is too long.
    """
    pass
  
  class SubversionLoginError(SubversionError):
    """Raised when an authentication is required.
    """
    def __init__(self, realm = None):
      self._realm = realm
  
    def getRealm(self):
      return self._realm
      
  class SubversionSSLTrustError(SubversionError):
    """Raised when a SSL certificate is not trusted.
    """
    def __init__(self, trust_dict = None):
      self._trust_dict = trust_dict
  
    def getTrustDict(self):
      return self._trust_dict
      
  
  class Callback:
    """The base class for callback functions.
    """
    def __init__(self, client):
      self.client = client
  
    def __call__(self, *args):
      pass
  
  class CancelCallback(Callback):
    def __call__(self):
      current_time = time.time()
      if current_time - self.client.creation_time > self.client.getTimeout():
        raise SubversionTimeoutError, 'too long transaction'
        #return True
      return False
  
  class GetLogMessageCallback(Callback):
    def __call__(self):
      message = self.client.getLogMessage()
      if message:
        return True, message
      return False, ''
  
  class GetLoginCallback(Callback):
    def __call__(self, realm, username, may_save):
      user, password = self.client.getLogin(realm)
      if user is None:
        raise SubversionLoginError(realm)
        #return False, '', '', False
      return True, user, password, False
  
  class NotifyCallback(Callback):
    def __call__(self, event_dict):
      # FIXME: should accumulate information for the user
      pass
  
  class SSLServerTrustPromptCallback(Callback):
    def __call__(self, trust_dict):
      trust, permanent = self.client.trustSSLServer(trust_dict)
      if not trust:
        raise SubversionSSLTrustError(trust_dict)
        #return False, 0, False
      # XXX SSL server certificate failure bits are not defined in pysvn.
      # 0x8 means that the CA is unknown.
      return True, 0x8, permanent

  # Wrap objects defined in pysvn so that skins have access to attributes in the ERP5 way.
  class Getter(Method):
    def __init__(self, key):
      self._key = key
  
    def __call__(self, instance):
      value = getattr(instance._obj, self._key)
      if type(value) == type(u''):
        value = value.encode('utf-8')
      #elif isinstance(value, pysvn.Entry):
      elif str(type(value)) == "<type 'entry'>":
        value = Entry(value)
      #elif isinstance(value, pysvn.Revision):
      elif str(type(value)) == "<type 'revision'>":
        value = Revision(value)
      return value

  def initializeAccessors(klass):
    klass.security = ClassSecurityInfo()
    klass.security.declareObjectPublic()
    for attr in klass.attribute_list:
      name = 'get' + convertToUpperCase(attr)
      print name
      setattr(klass, name, Getter(attr))
      klass.security.declarePublic(name)
    InitializeClass(klass)

  class ObjectWrapper(Implicit):
    attribute_list = ()
    
    def __init__(self, obj):
      self._obj = obj
  
  class Status(ObjectWrapper):
    # XXX Big Hack to fix a bug
    __allow_access_to_unprotected_subobjects__ = 1
    attribute_list = ('path', 'entry', 'is_versioned', 'is_locked', 'is_copied', 'is_switched', 'prop_status', 'text_status', 'repos_prop_status', 'repos_text_status')
  initializeAccessors(Status)
  
  class Entry(ObjectWrapper):
    attribute_list = ('checksum', 'commit_author', 'commit_revision', 'commit_time',
                      'conflict_new', 'conflict_old', 'conflict_work', 'copy_from_revision',
                      'copy_from_url', 'is_absent', 'is_copied', 'is_deleted', 'is_valid',
                      'kind', 'name', 'properties_time', 'property_reject_file', 'repos',
                      'revision', 'schedule', 'text_time', 'url', 'uuid')

  class Revision(ObjectWrapper):
    attribute_list = ('kind', 'date', 'number')
  initializeAccessors(Revision)

  
  class SubversionClient(Implicit):
    """This class wraps pysvn's Client class.
    """
    log_message = None
    timeout = 60 * 5
    
    def __init__(self, **kw):
      self.client = pysvn.Client()
      self.client.set_auth_cache(0)
      self.client.callback_cancel = CancelCallback(self)
      self.client.callback_get_log_message = GetLogMessageCallback(self)
      self.client.callback_get_login = GetLoginCallback(self)
      #self.client.callback_get_login = self.callback_get_Login
      self.client.callback_notify = NotifyCallback(self)
      self.client.callback_ssl_server_trust_prompt = SSLServerTrustPromptCallback(self)
      #self.client.callback_ssl_server_trust_prompt = self.callback_ssl_server_trust_prompt
      self.creation_time = time.time()
      self.__dict__.update(kw)

    def getLogMessage(self):
      return self.log_message
    
    def _getPreferences(self):
      self.working_path = self.getPortalObject().portal_preferences.getPreference('subversion_working_copy')
      if not self.working_path :
        raise "Error: Please set Subversion working path in preferences"
      self.svn_username = self.getPortalObject().portal_preferences.getPreference('preferred_subversion_user_name')
      os.chdir(self.working_path);

    def getTimeout(self):
      return self.timeout

#     def callback_get_Login( self, realm, username, may_save ):
#         #Retrieving saved username/password
#         username, password = self.login
#         if not username :
#           raise "Error: Couldn't retrieve saved username !"
#         if not password :
#           raise "Error: Couldn't retrieve saved password !"
#         return 1, username, password, True
        
    def trustSSLServer(self, trust_dict):
      return self.aq_parent._trustSSLServer(trust_dict)
    
#     def callback_ssl_server_trust_prompt( self, trust_data ):
#       # Always trusting
#       return True, trust_data['failures'], True
    
    def checkin(self, path, log_message, recurse):
      self._getPreferences()
      return self.client.checkin(path, log_message=log_message, recurse=recurse)

    def status(self, path, **kw):
      # Since plain Python classes are not convenient in Zope, convert the objects.
      return [Status(x) for x in self.client.status(path, **kw)]

  def newSubversionClient(container, **kw):
    return SubversionClient(**kw).__of__(container)
    
except ImportError:
  from zLOG import LOG, WARNING
  LOG('SubversionTool', WARNING,
      'could not import pysvn; until pysvn is installed properly, this tool will not work.')
  def newSubversionClient(container, **kw):
    raise SubversionInstallationError, 'pysvn is not installed'