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

from lechat import *

from TDBProxy import TDBProxy

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)

        localedir = os.path.join(os.path.dirname(__file__), 'locale')

        if os.name == 'nt':
            import locale
            lang = locale.getdefaultlocale()[0][:2]
            try:
                cur_lang = gettext.translation('lechat', localedir=localedir, \
                                               languages=[lang])
                cur_lang.install()
            except IOError:
                __builtin__._ = lambda text:text
        else:
            gettext.install('lechat', localedir)

        self.widget = ApplicationWidget(argv)

        self.main_ui = MainUi(self)

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

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

        #  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()

        # '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) ]

        self.widget = MainWidget(self)

    def dbConnected(self):
        self.db_connected.delWatcher(self.dbConnected)
        self.db_disconnected.addWatcher(self.dbDisconnected)
        #  the connection to the database
        self.db = TDBProxy(self.config.server)
        self.widget = MainWidget(self)
        self.db_changed.raiseEvent()
        self.entries_changed.raiseEvent()

    def dbDisconnected(self):
        self.db_disconnected.delWatcher(self.dbDisconnected)
        self.db_connected.addWatcher(self.dbConnected)
        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.password)
        try:
            self.parent.entries = self.parent.db.getTree(cred, '/', '(objectclass=lechatNode)')
        except Exception, message:
            critical(message)
        else:
            self.parent.entries_changed.raiseEvent()
        
    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.widget = LoginFormWidget(self)

        if os.name == 'nt':
            import _winreg

            try:
                _winreg.SetValue(_winreg.HKEY_CURRENT_USER, "Software\\lechat",
                                 _winreg.REG_SZ, "Default value")
                kh = _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, "Software\\lechat")
            except:
                kh = None

            if kh:
                try:
                    server = _winreg.QueryValueEx(kh, "server")[0]
                except WindowsError:
                    server = 'http://localhost:7080/'

                try:
                    base_dn = _winreg.QueryValueEx(kh, "base_dn")[0]
                except WindowsError:
                    base_dn = 'dc=abadcafe'

                try:
                    login = _winreg.QueryValueEx(kh, "login")[0]
                except WindowsError:
                    login = 'admin'
                
                try:
                    password = _winreg.QueryValueEx(kh, "password")[0]
                except WindowsError:
                    password = 'admin'
            else:
                server = 'http://localhost:7080/'
                base_dn = 'dc=abadcafe'
                login = 'admin'
                password = 'admin'
                
        else:
            try:
                from config import server, base_dn, login, password
            except ImportError:
                server = 'http://localhost:7080/'
                base_dn = 'dc=abadcafe'
                login = 'admin'
                password = 'admin'


        defaults = {'server': server,
                    'base_dn': base_dn,
                    'login': login,
                    'password': password}
        
        self.setData(defaults)
        
        self.show()

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

        if os.name == 'nt':
            import _winreg

            kh = _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, "Software\\lechat")

            values = self.getData()

            _winreg.SetValueEx(kh, "server", 0, _winreg.REG_SZ, str(values['server']))
            _winreg.SetValueEx(kh, "base_dn", 0, _winreg.REG_SZ, str(values['base_dn']))
            _winreg.SetValueEx(kh, "login", 0, _winreg.REG_SZ, str(values['login']))
            _winreg.SetValueEx(kh, "password", 0, _winreg.REG_SZ, str(values['password']))

        else:
            pass

    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.widget = ChangeSetWidget(self)
        
        self.parent.changeset_changed.addWatcher(self.update)
        

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

    def update(self):
        #debug('update changeset')
        self.widget.updateChangeSet()
        self.parent.db_changed.raiseEvent()
        
    def commit(self):
        """
        Commits changeset to database.
        """
        cred = Credential(self.parent.config.login, self.parent.config.password)
        try:
            self.parent.db.commit(cred, self.parent.changeset)
        except Exception, message:
            critical(message)
        else:
            self.parent.changeset.clear()
            self.parent.changeset_changed.raiseEvent()
            self.parent.db_changed.raiseEvent()

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

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


