import os, gettext, re, base64
import __builtin__
from logging import warning, debug, critical
from Qt4.Qt4Widgets import *
from lechat import *

from twisted.internet import reactor
from TDBProxy import TDBProxy

try:
    from hashlib import md5
except ImportError:
    from md5 import new as md5

class Ui(object):
    def __init__(self, parent = None):
        self.parent = parent
        self.widget = None
        self.values = {}
        self.modified = Event()
        self.canceled = Event()

    def show(self):
        if self.widget:
            self.widget.show()

    def hide(self):
        if self.widget:
            self.widget.hide()

    def setData(self, values):
        """
        Set widget values from DataTree.
        """
        self.widget.setData(values)

    def getData(self):
        """
        Actualise ui's object values from widget.
        
        Returns a dictionnary of Attribute objects keyed by attribute name.
        """
        return self.widget.getData()

class NullUi(Ui):
    def __init__(self, parent = None):
        Ui.__init__(self, parent)

    def show(self):
        pass

    def setData(self, data):
        pass

    def getData(self):
        pass
    
class UserUi(Ui):
    def __init__(self, parent = None):
        Ui.__init__(self, parent)

        for k in ['node',
                  'cn',
                  'sn',
                  'userPassword',
                  'enabled',
                  'mail',
                  'lechatUserMainDomain',
                  'lechatMailForward',
                  'lechatMailForwardOnly',
                  'lechatMailVacation',
                  'lechatMailVacationInfo']:
            self.values[k] = Attribute(k, visible = False)

        for k in ['node',
                  'cn',
                  'userPassword',
                  'enabled',
                  'mail',
                  'lechatMailForward',
                  'lechatMailVacation',
                  'lechatMailVacationInfo',
                  'lechatUserMainDomain',
                  'lechatMailForwardOnly' ]:
            self.values[k].visible = True

        self.values['lechatUserMainDomain'].filter_re = '/domains/.+'
        
        self.values['objectClass'] = Attribute('objectClass',
                                               ['lechatNode', 'lechatMailAccount'],
                                               True, False)
        
        self.widget = ObjectClassWidget(self)

class HostUi(Ui):
    def __init__(self, parent = None):
        Ui.__init__(self, parent)

        for k in ['node',
                  'hostname',
                  'services' ]:
            self.values[k] = Attribute(k) 

        self.values['objectClass'] = Attribute('objectClass',
                                               ['lechatNode'],
                                               True, False)

        self.widget = ObjectClassWidget(self)

class DomainUi(Ui):
    def __init__(self, parent = None):
        Ui.__init__(self, parent)

        for k in ['node'] :
            self.values[k] = Attribute(k) 

        self.values['objectClass'] = Attribute('objectClass',
                                               ['lechatNode'],
                                               True, False)
        self.widget = ObjectClassWidget(self)

class ConfigUi(Ui):
    def __init__(self, parent = None):
        Ui.__init__(self, parent)

        for k in ['node', 'ldapServer', 'ldapBaseDN'] :
            self.values[k] = Attribute(k) 

        self.widget = ObjectClassWidget(self)

class ApplicationUi(Ui):

    def __init__(self, argv):
        Ui.__init__(self, None)
        self.config = lechatConfiguration()
        self._gettext_init()
        self.widget = ApplicationWidget(argv)
        self.main_ui = MainUi(self, self.config)

    def _gettext_init(self):
        if os.name == 'nt':
            import locale

            lang = locale.getdefaultlocale()[0][:2]
            try:
                cur_lang = gettext.translation('lechat', localedir=self.config.locale_dir,
                                               languages=[lang])
                __builtin__._ = cur_lang.gettext
            except IOError:
                __builtin__._ = lambda text:text
        else:
            self.config.locale_dir = os.path.join(os.path.dirname(__file__), 'locale')
            gettext.install('lechat', self.config.locale_dir)    

    def quit(self):
        self.widget.quit()

class MainUi(Ui):
    """
    Main UI
    """
    def __init__(self, parent, config):
        Ui.__init__(self, parent)
        
        self.config = config

        #  the connection to the database
        self.db = None

        # 'entries' holds read tree from database
        self.entries = DataTree()

        # 'entries_changed' is raised on every entries modification
        self.entries_changed = Event()

        # 'validate_login' is raised as soon as login is tried
        self.validate_login = Event()

        # 'db_connected' is raised as soon as TreeDataBase is connected
        self.db_connected = Event()
        # this sadly has to be stacked first. TODO: db-connection state-machine
        #self.db_connected.addWatcher(self.dbConnected)
        
        # 'db_disconnected' is raised as soon as TreeDataBase is diconnected
        self.db_disconnected = Event()
        
        # 'db_changed' is raised on every TreeDataBase modification
        self.db_changed = Event()

        # 'db_changeset_changed' is raised on every changeset modification
        self.changeset_changed = Event()

        # pending database modifications
        self.changeset = ChangeSet()

        # currently edited entries
        self.edit_forms_ui = {}

        # changes view
        self.changeSetUi = ChangeSetUi(self)        

        # treedatabase browser view
        self.browser_ui = TDBBrowser(self)

        self.loginFormUi = LoginFormUi(self)

        self.uiFactories = [ (re.compile('/people/.+'), UserUi),
                             (re.compile('/people/'), NullUi),
                             (re.compile('/hosts/.+'), HostUi),
                             (re.compile('/hosts/'), NullUi),
                             (re.compile('/domains/.+'), DomainUi),
                             (re.compile('/domains/'), NullUi),
                             (re.compile('/config/'), ConfigUi) ]

    def dbConnected(self):
        self.db_disconnected.addWatcher(self.dbDisconnected)

        self.widget = MainWidget(self)
        
        self.db_changed.raiseEvent()
        self.entries_changed.raiseEvent()

    def dbDisconnected(self):
        self.db_disconnected.delWatcher(self.dbDisconnected)
        self.entries = DataTree()
        
    def getUiFactory(self, entry):
        for k, v in self.uiFactories:
            if k.match(entry):
                return v
        raise KeyError, 'no factory for %s' % entry
        
    def createEntry(self, branch):
        name = 'nouveau'
        i = 1
        while self.entries.has_key(branch + '/' + name + '/') or \
                  self.changeset.changes.has_key(branch + '/' + name + '/'):
            name = 'nouveau %d' % i
            i = i + 1

        self.entries[branch + '/' + name + '/'] = DataTree( {'node': Attribute('node', value = [name])} )

        self.changeset.add(branch + '/' + name + '/', OP_CREATE, name)

        self.editEntry(branch + '/' + name + '/')


    def editEntry(self, entry_path):

        if not self.edit_forms_ui.has_key(entry_path):
            ui_factory = self.getUiFactory(entry_path)
            self.edit_forms_ui[entry_path] = ui_factory(self)
            self.edit_forms_ui[entry_path].setData(self.entries[entry_path].content)
            self.edit_forms_ui[entry_path].modified.addWatcher( lambda : self.acceptEntryEdit(entry_path) )
            self.edit_forms_ui[entry_path].canceled.addWatcher( lambda : self.cancelEntryEdit(entry_path) )

        self.edit_forms_ui[entry_path].show()

    def deleteEntry(self, entry_path):
        #debug("delete %s" % entry_path)
        self.changeset.add(entry_path, OP_DELETE)
        self.changeset_changed.raiseEvent()
        
    def acceptEntryEdit(self, entry_path):
        #debug('accept %s' % entry_path)
        new_values = self.edit_forms_ui[entry_path].getData()
        old_values = {}
        for k, v in self.entries[entry_path].items():
            old_values[k] = v.value

        #print 'old_values: %s' % old_values
        #print 'new_values: %s' % new_values

        has_changes = False
        
        for k, v in new_values.items():
            if old_values.has_key(k) and \
               len(new_values[k]) == len(old_values[k]) and \
               not [ i for i in xrange( len(new_values[k]) )
                     if new_values[k][i] != old_values[k][i] ] :
                del new_values[k]
                del old_values[k]
        
        if new_values.has_key('node') and old_values.has_key('node'):
            self.changeset.add(entry_path, OP_RENAME,
                               new_values['node'][0],  old_values['node'][0])
            has_changes = True
            del new_values['node']
            del old_values['node']
            
        if new_values:
            #print 'path: %s, new values: %s' % (entry_path, new_values)
            self.changeset.add(entry_path, OP_MODIFY, new_values, old_values )
            has_changes = True

        if has_changes:
            self.changeset_changed.raiseEvent()
            
        del self.edit_forms_ui[entry_path]
        
    def cancelEntryEdit(self, entry_path):
        #debug('cancel %s' % entry_path)
        del self.edit_forms_ui[entry_path]

class TDBBrowser(Ui):
    def __init__(self, parent):
        Ui.__init__(self, parent)

        self.parent.db_connected.addWatcher(self.connected)
        self.parent.db_changed.addWatcher(self.updateFromDB)

        self.widget = TDBBrowserWidget(self)

    def __del__(self):
        self.parent.db_connected.delWatcher(self.connected)
        self.parent.db_changed.delWatcher(self.updateFromDB)
        
    def updateFromDB(self):
        cred = Credential(self.parent.config.login + ',' + self.parent.config.base_dn,
                          self.parent.config.password)

        deferred = self.parent.db.getTree(cred, '/', '(objectclass=lechatNode)')
        deferred.addCallbacks(self.updateDB_ok, self.updateDB_error)

    def updateDB_ok(self, result):
        self.parent.entries = result
        self.parent.entries_changed.raiseEvent()
    
    def updateDB_error(self, result):
        print 'updateDB_error'

    def connected(self):
        #debug('Getting users from %s' % self.parent.config.base_dn)
        self.parent.db_changed.raiseEvent()

class LoginFormUi(Ui):
    def __init__(self, parent):
        Ui.__init__(self, parent)
        
        self.parent.db_connected.addWatcher(self.connected)
        self.parent.validate_login.addWatcher(self.validate)
        self.widget = LoginFormWidget(self)

        config = parent.config

        try:
            password = base64.b64decode(config.password)
        except TypeError:
            password = config.password
            
        defaults = { 'server': config.server,
                     'port': config.port,
                     'base_dn': config.base_dn,
                     'login': config.login,
                     'password': password }
        
        self.setData(defaults)
        
        self.show()

    def __del__(self):
        self.parent.db_connected.delWatcher(self.connected)
        self.parent.validate_login.delWatcher(self.validate)
        self.parent.db_disconnected.delWatcher(self.disconnected)

    def validate(self):
        
        values = self.getData()

        self.parent.config.server = values['server']
        self.parent.config.port = values['port']
        self.parent.config.base_dn = values['base_dn']
        self.parent.config.login = values['login']
        
        try:
            base64.b64decode(values['password'])
        except TypeError:
            self.parent.config.password = base64.b64encode(values['password'])
        else:
            self.parent.config.password = values['password']

        #deferred = self.parent.db.getTree(cred, '/', '(objectclass=lechatNode)')
        #deferred.addCallbacks(self.updateDB_ok, self.updateDB_error)

        #  the connection to the database
        self.parent.db = TDBProxy(self.parent.config.server, self.parent.config.port)

        self.parent.dbConnected()

        self.parent.validate_login.delWatcher(self.validate)
        self.parent.db_connected.raiseEvent()
    
    def updateDB_ok(self, result):
        print 'login successfull'
    
    def updateDB_error(self, result):
        print 'updateDB_error'

    def connected(self):
        #debug("connected to ldap")

        self.parent.config.write()
        
        self.hide()
        self.parent.show()
        self.parent.db_connected.delWatcher(self.connected)
        self.parent.db_disconnected.addWatcher(self.disconnected)

    def disconnected(self):
        #debug("disconnected from ldap")
        self.parent.hide()
        self.show()
        self.parent.db_disconnected.delWatcher(self.disconnected)
        self.parent.db_connected.addWatcher(self.connected)

class ChangeSetUi(Ui):
    def __init__(self, parent):
        Ui.__init__(self, parent)
        
        self.parent.changeset_changed.addWatcher(self.update)

    def __del__(self):
        self.parent.changeset_changed.delWatcher(self.update)

    def update(self):
        #debug('update changeset')
        self.commit()
        self.parent.db_changed.raiseEvent()
        
    def commit(self):
        """
        Commits changeset to database.
        """
        cred = Credential(self.parent.config.login + ',' + self.parent.config.base_dn,
                          self.parent.config.password)

        print 'commit changeset %s' % self.parent.changeset
        deferred = self.parent.db.commit(cred, self.parent.changeset)
        deferred.addCallbacks(self.commited, self.error)

    def commited(self, result):
        self.parent.changeset.clear()
        self.parent.db_changed.raiseEvent()

    def error(self, result):
        critical(result)

    def clear(self):
        """
        Clears changeset.
        """
        self.parent.changeset.clear()

    def cancel(self, user, attribute = None):
        """
        Cancel pending modifications for given path.
        """
        self.parent.changeset.cancel(user, attribute)
        self.parent.changeset_changed.raiseEvent()

