define('spyl/pb/Launcher', [
  'jls/lang/Class',
  'jls/lang/Logger',
  'jls/gui/Element',
  'jls/gui/GuiUtilities',
  'jls/html/DomEvent',
  'jls/html/HtmlElement',
  'jls/util/Cookie',
  'jls/util/EventBroker',
  'jls/util/Formatter',
  'jls/util/Location',
  'jls/util/Resource',
  'jls/util/Sxe',
  'jls/util/SxeEnvelope',
  'jls/util/StringCodec',
  'jls/lang/ByteBuffer',
  'jls/util/XMLHttpRequest',
  'jls/security/MessageDigest',
  'spyl/pb/Comment',
  'spyl/pb/Context',
  'spyl/pb/Group',
  'spyl/pb/GroupPreview',
  'spyl/pb/Icon',
  'spyl/pb/Media',
  'spyl/pb/User',
  'spyl/pb/Repository'
], function (
  Class,
  Logger,
  Element,
  GuiUtilities,
  DomEvent,
  HtmlElement,
  Cookie,
  EventBroker,
  Formatter,
  Location,
  Resource,
  Sxe,
  SxeEnvelope,
  StringCodec,
  ByteBuffer,
  XMLHttpRequest,
  MessageDigest,
  Comment,
  Context,
  Group,
  GroupPreview,
  Icon,
  Media,
  User,
  Repository
) {

var Launcher;
/*
 * TODO
 * (Auto-)Refresh the comment board
 * Poll for new comments/medias
 * Start on new medias if any
 * Add tags
 */
Launcher = Class.create({
    initialize: function(theme) {
        this.theme = theme;
        this.broker = new EventBroker();
        this.sxe = new Sxe('script/default.php');
        this.medias = [];
        this.root = null;
        this.parent = GuiUtilities.getRoot();
        this.contextHistory = new Array();
        this.contextHistoryIndex = -1;
        this.resource = null;
        this.adminEMail = null;

        this.users = {};
        this.repositories = {};
        this.user = null;
        this.userId = Cookie.get('userId');
        this.password = Cookie.get('password');
        this.rememberMe = false;
        Logger.getInstance().info('Cookie says: User Id: ' + this.userId);
        
        this.connected = false;
        this.logon = null;
        this.menu = null;
        this.comment = null;

        this.broker.subscribeAll([
              'onOpenMenu',
              'onCloseMenu',
              'onLogoff',
              'onRegister',
              'onDisplayInfo',
              'onDisplayNotification',
              'onAuthenticated',
              'onAccessGranted',
              'onLogon',
              'onMediasLoaded',
              'onConnectOk',
              'onLostPassword',
              'onManageAccount',
              'onManageRepository',
              
              'onOpenGroup',
              'onOpenMedia',
              'onDisplayMedia',
              'onDisplayBoard',
              'onDisplayBoardFull',
              'onDisplayGroup',
              'onGoPrevious',
              'onGoNext',
              'onGoHome',
              'onGoUp',
              'onGoBackward',
              'onGoForward',
              'onComment',
              
              'onDisplayUsers',
              'onFeedUsers',
              'onDebug'
          ], this, this);
    },
    launch: function() {
        Logger.getInstance().trace('launch()');
        this.elements = Resource.getResourceAsJSON('spyl/pb/elements.' + this.theme + '.json');
        this.parent.setAttribute('class', this.theme);
        this.resource = Resource.load('spyl/pb/client');
        this.dateFormat = this.resource.get('date.format');
        this.commentLabel = this.resource.get('comment');
        this.commentsLabel = this.resource.get('comments');
        this.parent.setResource(this.resource);
        this.parent.setEventBroker(this.broker);
        this.createElements();
        HtmlElement.addEventListener(document, 'keydown', (function(e) {
            this.onKey.call(this, new DomEvent(e, this));
        }).bind(this));

        if (this.userId && this.password) {
            this.broker.publish('onLogon');
        }
        // check url
        var hash = Location.getHash();
        if (hash) {
            Location.setHash();
        }
        var query = Location.parseQuery(hash);
        if (query != null) {
            if (query.logLevel) {
                Logger.getInstance().setLogLevel(query.logLevel);
            }
            if (query.operation) {
                this.onQuery(query);
            }
        }
        Logger.getInstance().trace('done');
    },
    onKey: function(event) {
        var keyCode = event.keyCode();
        //Logger.getInstance().debug('onKey(): -->' + keyCode + '<--');
        if ((this.currentContext == null) || this.comment) {
            return;
        }
        switch (keyCode) {
        case 38: // up
            switch (this.currentContext.viewName) {
            case 'media':
                this.broker.publish('onDisplayBoard', event);
                break;
            case 'board':
                this.broker.publish('onDisplayGroup', event);
                break;
            case 'group':
                event.message.preventDefault();
                if (this.currentContext.group != this.root) {
                    this.changeContext(new Context('group', this.root, 0));
                }
                break;
            }
            break;
        case 40: // down
            switch (this.currentContext.viewName) {
            /*case 'media':
                event.preventDefault();
                break;*/
            case 'board':
                this.broker.publish('onDisplayMedia', event);
                break;
            case 'group':
                this.broker.publish('onDisplayBoard', event);
                break;
            }
            break;
        }
        if (this.currentContext.viewName != 'media') {
            return;
        }
        switch (keyCode) {
        case 37: // left
            this.broker.publish('onGoBackward', event);
            break;
        case 39: // right
            this.broker.publish('onGoForward', event);
            break;
        }
    },
    onQuery: function(query) {
        var envelope = new SxeEnvelope();
        envelope.addRequest({name: query.operation, attributes: query});
        this.sxe.send(envelope, (function(response) {
            var envelope = response.getEnvelope();
            //Logger.getInstance().debug('response: -->' + envelope.toString() + '<--');
            var result = envelope.getResult(0);
            var msg = null;
            if (result.getName() == 'failure') {
                msg = this.resource.getf('operation.failure', query.operation, result.getText());
            } else {
                switch (query.operation) {
                case 'activateUser':
                    if (result.getName() == 'success') {
                        msg = this.resource.get('user.activated');
                    }
                    break;
                }
            }
            this.broker.publish('onDisplayInfo', msg || this.resource.get('error'));
        }).bind(this));
    },
    createElements: function() {
        Logger.getInstance().trace('createElements()');
        this.viewport = Element.create(this.elements.viewport, this.parent);
        this.toolbar = Element.create(this.elements.toolbar, this.parent);
        this.header = Element.create(this.elements.header, this.parent);
        var element = Element.create(this.elements.notification, this.parent);
        this.notification = element.getById('container');
        this.logon = Element.create(this.elements.logon, this.header);
        this.logon.getById('linkMailTo').setAttribute('href', 'mailto:' + this.adminEMail);
        this.options = Element.create(this.elements.options, this.header);
        this.options.getById('linkMailTo').setAttribute('href', 'mailto:' + this.adminEMail);
        this.options.getById('linkHelp').setAttribute('href', 'help.' + _native.core.properties['user.language'] + '.html');
        // fetch elements
        this.groupIcon = this.toolbar.getById('groupIcon');
        this.boardIcon = this.toolbar.getById('boardIcon');
        this.mediaIcon = this.toolbar.getById('mediaIcon');
        this.mediaToolbar = this.toolbar.getById('mediaToolbar');
        this.repositoryToolbar = this.toolbar.getById('repositoryToolbar');
        this.connectLink = this.header.getById('connectLink');
    },
    formatDate: function(date) {
        return Formatter.format(this.dateFormat, date);
    },
    onOpenMenu: function(event) {
        Logger.getInstance().trace('onOpenMenu');
        event.message.preventDefault();
        if (this.menu != null) {
            this.onCloseMenu();
            return;
        }
        if (this.connected) {
            this.menu = this.options;
        } else {
            if (this.userId) {
                this.logon.getById('userId').setText(this.userId);
                this.logon.getById('password').setText('');
            }
            this.menu = this.logon;
        }
        this.menu.getStyle().setProperty('display', 'block');
    },
    onCloseMenu: function(event) {
        if (this.menu != null) {
            this.menu.getStyle().setProperty('display', 'none');
            this.menu = null;
        }
    },
    onLogoff: function(event) {
        event.message.preventDefault();
        Logger.getInstance().trace('logoff');
        this.userId = '';
        this.password = '';
        this.user = null;
        this.rememberMe = false;
        this.connected = false;
        this.connectLink.setTextContent('#logon');
        Cookie.clear('userId');
        Cookie.clear('password');
        this.onCloseMenu();
    },
    onConnectOk: function(event) {
        var userIdEdit = this.logon.getById('userId');
        var passwordEdit = this.logon.getById('password');
        this.userId = userIdEdit.getText();
        this.password = Launcher.sha1(passwordEdit.getText());
        this.rememberMe = true; // TODO use a check box
        this.onCloseMenu();
        this.broker.publish('onLogon');
    },
    onRegister: function(event) {
        Logger.getInstance().trace('onRegister');
        event.message.preventDefault();
        var div = Element.create(this.elements.register, this.parent);
        div.getById('ok').observe('click', (function (e) {
            var userId = div.getById('userId').getText();
            var password = Launcher.sha1(div.getById('password').getText());
            var email = div.getById('email').getText();
            var envelope = new SxeEnvelope();
            envelope.addRequest({name: 'createUser', attributes: {login: userId, password: password, email: email}});
            this.sxe.send(envelope, (function(response) {
                var envelope = response.getEnvelope();
                //Logger.getInstance().debug('response: -->' + envelope.toString() + '<--');
                var result = envelope.getResult(0);
                if (result.getName() == 'success') {
                    Logger.getInstance().trace('User created');
                    div.destroy();
                    this.broker.publish('onDisplayNotification', this.resource.get('user.create.success'));
                } else {
                    var msg = this.resource.get('user.create.failure');
                    if (result.getName() == 'failure') {
                        msg = this.resource.getf('error.message', result.getText());
                    }
                    this.broker.publish('onDisplayInfo', msg);
                }
            }).bind(this));
        }).bind(this));
        div.getById('cancel').observe('click', function (e) {
            div.destroy();
        });
    },
    onLostPassword: function(event) {
        Logger.getInstance().trace('onLostPassword()');
        event.message.preventDefault();
        var div = Element.create(this.elements.lostPassword, this.parent);
        div.getById('ok').observe('click', (function (e) {
            var password = Launcher.sha1(div.getById('password').getText());
            var email = div.getById('email').getText();
            var envelope = new SxeEnvelope();
            envelope.addRequest({name: 'lostUserPassword', attributes: {email: email, password: password}});
            this.sxe.send(envelope, (function(response) {
                var envelope = response.getEnvelope();
                //Logger.getInstance().debug('response: -->' + envelope.toString() + '<--');
                var result = envelope.getResult(0);
                if (result.getName() == 'success') {
                    Logger.getInstance().trace('lostUserPassword succeed');
                    div.destroy();
                    this.broker.publish('onDisplayNotification', this.resource.get('user.lostPassword.success'));
                } else {
                    var msg = this.resource.get('error');
                    if (result.getName() == 'failure') {
                        msg = this.resource.getf('error.message', result.getText());
                    }
                    this.broker.publish('onDisplayInfo', msg);
                }
            }).bind(this));
        }).bind(this));
        div.getById('cancel').observe('click', function (e) {
            div.destroy();
        });
    },
    onDebug: function(event) {
        Logger.getInstance().trace('onDebug()');
        if (event.message instanceof DomEvent) {
            event.message.preventDefault();
            this.onCloseMenu();
        }
        if (Logger.getInstance().getLogLevel() == Logger.TRACE) {
            Logger.getInstance().setLogLevel(Logger.INFO);
        } else {
            Logger.getInstance().setLogLevel(Logger.TRACE);
        }
        this.broker.publish('onDisplayNotification', 'Log level set to ' + Logger.getInstance().getLogLevel());
    },
    onManageRepository: function(event) {
        Logger.getInstance().trace('onManageRepository()');
        if (event.message instanceof DomEvent) {
            event.message.preventDefault();
            this.onCloseMenu();
        }
        var broker = new EventBroker();
        var userRepositories = this.repositories;
        var allRepositories, allRepositoryMap;
        var launcher = this;
        var div = Element.create(this.elements.repositoryGrid, this.parent);
        div.setEventBroker(broker);
        var dataGrid = div.getById('dataGrid');
        var dgModel = dataGrid.getModel();
        dgModel.isCellEditable = function(row, column) {
            var rowData = this.getRow(row);
            if (column < 4) {
                return rowData.userId == launcher.userId;
            }
            if (column > 4) {
                return rowData.id in launcher.repositories;
            }
            return true;
        };
        var waitRepository = new Repository(0, '...', -1, '...');
        waitRepository.isSubscribed = false;
        dgModel.addRow(waitRepository);
        
        if ('repository' in this.user.rights) {
            // enable create/delete buttons
        }
        /*
         * Fetch repositories
         */
        var envelope = new SxeEnvelope(launcher.userId, launcher.password);
        envelope.addRequest({name: 'listRepository', attributes: {select: 'all'}});
        launcher.sxe.send(envelope, function(response) {
            var envelope = response.getEnvelope();
            var result = envelope.getResult(0);
            if (result.getName() == 'repositoryListResult') {
                dgModel.removeAll();
                allRepositories = [];
                for (var i = 0; i < result.getChildCount(); i++) {
                    var repository = Repository.createFromXML(result.getChild(i));
                    allRepositories.push(repository);
                    repository = repository.clone();
                    if (repository.id in launcher.repositories) {
                        var ur = launcher.repositories[repository.id];
                        repository.isSubscribed = true;
                        repository.hasCommentRight = Launcher.hasRight(ur.rights, 'comment');
                        repository.hasWriteRight = Launcher.hasRight(ur.rights, 'comment');
                    } else {
                        repository.isSubscribed = false;
                        repository.hasCommentRight = false;
                        repository.hasWriteRight = false;
                    }
                    dgModel.addRow(repository);
                }
                allRepositoryMap = {};
                for (var i = allRepositories.length - 1; i >= 0; i--) {
                    var repository = allRepositories[i];
                    allRepositoryMap[repository.id] = repository;
                }
            } else {
                div.destroy();
                var msg = launcher.resource.get('error');
                if (result.getName() == 'failure') {
                    msg = launcher.resource.getf('error.message', result.getText());
                }
                launcher.broker.publish('onDisplayInfo', msg);
            }
        });
        broker.subscribe('create', function (event) {
            dgModel.addRow(new Repository(-1, 'Add a title', launcher.userId, '', true, false));
        });
        broker.subscribe('delete', function (event) {
            var selectedRow = dataGrid.getSelectedRow();
            Logger.getInstance().trace('delete, selectedRow: ' + selectedRow);
            if ((selectedRow >= 0) && (selectedRow < dgModel.getRowCount())) {
                dgModel.removeRow(selectedRow);
            }
        });
        broker.subscribe('apply', function (event) {
            var envelope = new SxeEnvelope(launcher.userId, launcher.password);
            var repMap = {};
            for (var i = dgModel.getRowCount() - 1; i >= 0; i--) {
                var row = dgModel.getRow(i);
                if (row.id == -1) {
                    envelope.addRequest({name: 'createRepository', attributes: {title: row.title, description: row.description,
                        requiresUserAccess: row.requiresUserAccess, isPublic: row.isPublic}});
                } else if (row.id in allRepositoryMap) {
                    var repository = allRepositoryMap[row.id];
                    if (! row.equals(repository)) {
                        envelope.addRequest({name: 'modifyRepository', attributes: {id: row.id, title: row.title, description: row.description,
                            requiresUserAccess: row.requiresUserAccess, isPublic: row.isPublic}});
                    }
                    if (row.isSubscribed != (repository.id in launcher.repositories)) {
                        if (row.isSubscribed) {
                            envelope.addRequest({name: 'addRepositoryUser', attributes: {repositoryId: row.id, userId: launcher.user.id}});
                        } else {
                            envelope.addRequest({name: 'removeRepositoryUser', attributes: {repositoryId: row.id, userId: launcher.user.id}});
                        }
                    }
                } else {
                    
                }
                repMap[row.id] = row;
            }
            for (var i = allRepositories.length - 1; i >= 0; i--) {
                var repository = allRepositories[i];
                if (! (repository.id in repMap)) {
                    envelope.addRequest({name: 'deleteRepository', attributes: {id: repository.id}});
                }
            }
            if (envelope.getRequestCount() == 0) {
                return;
            }
            Logger.getInstance().warn('envelope [' + envelope.getRequestCount() + ']: ' + envelope.toString());
            launcher.sxe.send(envelope, function(response) {
                div.destroy();
                var resEnv = response.getEnvelope();
                Logger.getInstance().warn('response envelope [' + resEnv.getResultCount() + ']: ' + resEnv.toString());
                for (var i = resEnv.getResultCount() - 1; i >= 0; i--) {
                    var request = envelope.getRequest(i);
                    var result = resEnv.getResult(i);
                    if (result.getName() == 'failure') {
                        var msg = launcher.resource.getf('error.message', result.getText());
                        launcher.broker.publish('onDisplayInfo', msg);
                        break;
                    }
                    var repositoryId;
                    var msg = null;
                    switch (request.getName()) {
                    case 'createRepository':
                    case 'modifyRepository':
                        var newRepository = Repository.createFromXML(result.getChild(0));
                        launcher.repositories[newRepository.id] = newRepository;
                        msg = launcher.resource.getf(request.getName() == 'createRepository' ? 'repository.created' : 'repository.modified', newRepository.title);
                        break;
                    case 'deleteRepository':
                        repositoryId = request.getAttribute('id');
                        msg = launcher.resource.getf('repository.deleted', allRepositoryMap[repositoryId].title);
                        delete launcher.repositories[repositoryId];
                        break;
                    case 'addRepositoryUser':
                        repositoryId = request.getAttribute('repositoryId');
                        launcher.repositories[repositoryId] = allRepositoryMap[repositoryId];
                        msg = launcher.resource.getf('repository.subscribed', allRepositoryMap[repositoryId].title);
                        break;
                    case 'removeRepositoryUser':
                        repositoryId = request.getAttribute('repositoryId');
                        delete launcher.repositories[repositoryId];
                        msg = launcher.resource.getf('repository.unsubscribed', allRepositoryMap[repositoryId].title);
                        break;
                    }
                    if (msg != null) {
                        launcher.broker.publish('onDisplayNotification', msg);
                    }
                }
                //launcher.broker.publish('onDisplayNotification', launcher.resource.get('repository.done'));
            });
        });
        broker.subscribe('close', function () {
            div.destroy();
        });
    },
    onManageAccount: function(event) {
        Logger.getInstance().trace('onManageAccount()');
        event.message.preventDefault();
        this.onCloseMenu();
        var user = this.user;
        var div = Element.create(this.elements.manageAccount, this.parent);
        div.getById('login').setAttribute('text', user.login);
        div.getById('email').setAttribute('text', user.email);
        
        var cbAlert = div.getById('cbAlert');
        var alertMat = [0, 1, 16];
        cbAlert.addString(this.resource.get('user.alert.none'));
        var currentIndex = user.alertThreshold;
        for (var i = 1; i < alertMat.length; i++) {
            cbAlert.addString(this.resource.getf(i > 1 ? 'user.alert.counts' : 'user.alert.count', alertMat[i]));
            if (user.alertThreshold == alertMat[i]) {
                currentIndex = i;
            }
        }
        cbAlert.setCurrentSelectionIndex(currentIndex);
        div.getById('modify').observe('click', (function (e) {
            var login = div.getById('login').getText();
            var oldPassword = div.getById('oldPassword').getText();
            if (oldPassword) {
                oldPassword = Launcher.sha1(oldPassword);
            }
            var newPassword = div.getById('newPassword').getText();
            if (newPassword) {
                newPassword = Launcher.sha1(newPassword);
            }
            var email = div.getById('email').getText();
            var alertThreshold = cbAlert.getCurrentSelectionIndex();
            alertThreshold = alertMat[alertThreshold];
            var modifyUserAttr = {};
            if ((login) && (login != user.login)) {
                modifyUserAttr.login = login;
            }
            if ((oldPassword) && (newPassword) && (newPassword != user.password)) {
                modifyUserAttr.password = newPassword;
            }
            if ((email) && (email != user.email)) {
                modifyUserAttr.email = email;
            }
            if (alertThreshold != user.alertThreshold) {
                modifyUserAttr.alertThreshold = alertThreshold;
            }
            var envelope = new SxeEnvelope(this.userId, oldPassword || this.password);
            envelope.addRequest({name: 'modifyUser', attributes: modifyUserAttr});
            //Logger.getInstance().trace('request: -->' + envelope.toString() + '<--');
            this.sxe.send(envelope, (function(response) {
                var envelope = response.getEnvelope();
                //Logger.getInstance().debug('response: -->' + envelope.toString() + '<--');
                var result = envelope.getResult(0);
                if (result.getName() == 'ModifyUserResult') {
                    // update user
                    this.user = User.createFromXML(result.getChild(0));
                    Logger.getInstance().trace('User modified');
                    div.destroy();
                    this.broker.publish('onDisplayNotification', this.resource.get('user.modify.success'));
                } else {
                    var msg = this.resource.get('error');
                    if (result.getName() == 'failure') {
                        msg = this.resource.getf('error.message', result.getText());
                    }
                    this.broker.publish('onDisplayInfo', msg);
                }
            }).bind(this));
        }).bind(this));
        div.getById('cancel').observe('click', function (e) {
            div.destroy();
        });
    },
    displayInfo: function(element) {
        Logger.getInstance().trace('displayInfo()');
        var div = Element.create(this.elements.info, this.parent);
        var container = div.getById('container');
        container.addChild(element);
        div.getById('ok').observe('click', function () {
            div.destroy();
        });
    },
    onDisplayInfo: function(event) {
        Logger.getInstance().trace('onDisplayInfo');
        this.displayInfo({classname: 'jls/gui/Label', attributes: {text: event.message}});
    },
    displayNotification: function(element, seconds) {
        Logger.getInstance().trace('displayNotification()');
        var div = new HtmlElement({attributes: {htmlTagName: 'div'}, children: [element]}, this.notification);
        // TODO Manage setTimeout() in jls
        window.setTimeout(function () {
            div.destroy();
        }, seconds || 3000);
        return div;
    },
    onDisplayNotification: function(event) {
        Logger.getInstance().trace('onDisplayNotification');
        this.displayNotification({classname: 'jls/gui/Label', attributes: {text: event.message}});
    },
    onFeedUsers: function(event) {
        Logger.getInstance().trace('onFeedUsers');
        event.message.preventDefault();
        var div = Element.create(this.elements.feedConfirm, this.parent);
        div.getById('ok').observe('click', (function () {
            div.destroy();
            this.onFeedUsersConfirmed();
        }).bind(this));
        div.getById('cancel').observe('click', function () {
            div.destroy();
        });
    },
    onFeedUsersConfirmed: function() {
        Logger.getInstance().trace('onFeedUsersConfirmed');
        var envelope = new SxeEnvelope(this.userId, this.password);
        envelope.addRequest({name: 'feedUser'});
        this.sxe.send(envelope, (function(response) {
            var envelope = response.getEnvelope();
            //Logger.getInstance().debug('response: -->' + envelope.toString() + '<--');
            var result = envelope.getResult(0);
            if (result.getName() == 'userFeedResult') {
                var emailCount = parseInt(result.getAttribute('emailCount'), 10);
                var failureCount = parseInt(result.getAttribute('failureCount'), 10);
                var msg = this.resource.getf('user.feed.result', emailCount, failureCount);
                this.broker.publish('onDisplayNotification', msg);
            } else {
                var msg = this.resource.get('error');
                if (result.getName() == 'failure') {
                    msg = this.resource.getf('error.message', result.getText());
                }
                this.broker.publish('onDisplayInfo', msg);
            }
        }).bind(this));
    },
    onDisplayUsers: function(event) {
        event.message.preventDefault();
        this.onCloseMenu();
        var users = [];
        for (var id in this.users) {
            users.push(this.users[id]);
        }
        users.sort(function(a, b) {
                if (a.lastAuthenticationDate < b.lastAuthenticationDate)
                    return 1;
                if (a.lastAuthenticationDate > b.lastAuthenticationDate)
                    return -1;
                return 0;
            });
        Logger.getInstance().trace('onDisplayUsers ' + users.length + ' user(s)');
        // TODO elements
        var element = {classname: 'jls/html/Table', attributes: {fixedSize: 5, cellStyle: {padding: '5px', border: '1px dotted black'}}, style: {borderCollapse: 'collapse', display: 'inline-table'}, children: [
            {classname: 'jls/gui/Label', attributes: {text: '#user.id'}},
            {classname: 'jls/gui/Label', attributes: {text: '#user.name'}},
            {classname: 'jls/gui/Label', attributes: {text: '#user.email'}},
            {classname: 'jls/gui/Label', attributes: {text: '#user.creationDate'}},
            {classname: 'jls/gui/Label', attributes: {text: '#user.lastLogonDate'}}
        ]};
        for (var i = 0; i < users.length; i++) {
            var user = users[i];
            element.children.push({classname: 'jls/gui/Label', attributes: {text: user.id}});
            element.children.push({classname: 'jls/gui/Label', attributes: {text: user.login}});
            element.children.push({classname: 'jls/gui/Label', attributes: {text: user.email}});
            element.children.push({classname: 'jls/gui/Label', attributes: {text: this.formatDate(user.creationDate)}});
            element.children.push({classname: 'jls/gui/Label', attributes: {text: this.formatDate(user.lastAuthenticationDate)}});
        }
        this.displayInfo(element);
    },
    onLogon: function() {
        Logger.getInstance().trace('onLogon');
        var envelope = new SxeEnvelope();
        envelope.setAuthentication(this.userId, this.password);
        envelope.addRequest({name: 'authenticateUser'});
        envelope.addRequest({name: 'listUsers'});
        envelope.addRequest({name: 'listRepository', attributes: {select: 'subscribed'}});
        this.sxe.send(envelope, this.broker.callback('onAuthenticated'));
    },
    onAuthenticated: function(event) {
        Logger.getInstance().trace('onAuthenticated');
        var envelope = event.message.getEnvelope();
        //Logger.getInstance().debug('envelope: -->' + envelope.toString() + '<--');
        var result = envelope.getResult(0);
        if (result.getName() != 'AuthenticateUserResult') {
            Logger.getInstance().trace('Authentication fails');
            var msg = this.resource.get('logon.failure');
            if (result.getName() == 'failure') {
                msg = this.resource.getf('error.message', result.getText());
            }
            this.broker.publish('onDisplayInfo', msg);
            return;
        }
        Logger.getInstance().trace('AuthenticateUser succeed');
        if (this.rememberMe) {
            Logger.getInstance().trace('Place credentials in cookie');
            var nextYear = new Date();
            nextYear.setFullYear(nextYear.getFullYear() + 1);
            Cookie.set('userId', this.userId, {expires: nextYear});
            Cookie.set('password', this.password, {expires: nextYear});
        }
        this.connected = true;
        this.connectLink.setTextContent(this.userId);
        Logger.getInstance().trace('connected');

        this.users = {};
        var userListResult = envelope.getResult(1);
        //Logger.getInstance().trace('Users: --> ' + userListResult.toString('UTF-8') + '<--');
        if (userListResult.getName() == 'userListResult') {
            for (var i = 0; i < userListResult.getChildCount(); i++) {
                var user = User.createFromXML(userListResult.getChild(i));
                this.users[user.id] = user;
                Logger.getInstance().trace('User id ' + user.id + ' added');
            }
        }
        this.repositories = {};
        this.user = User.createFromXML(result.getChild(0));
        if (! ('read' in this.user.rights)) {
            this.broker.publish('onDisplayInfo', this.resource.get('logon.badRights'));
        }
    	var container = this.viewport.openGroup();
        this.options.getById('adminOptions').getStyle().setProperty('display', 'admin' in this.user.rights ? '' : 'none');
        var repositoryListResult = envelope.getResult(2);
        if (repositoryListResult.getName() == 'repositoryListResult') {
            if (repositoryListResult.getChildCount() > 0) {
                new HtmlElement({attributes: {htmlTagName: 'img', src: 'style/progress.gif'}}, container);
                for (var i = 0; i < repositoryListResult.getChildCount(); i++) {
                    var repository = Repository.createFromXML(repositoryListResult.getChild(i));
                    this.repositories[repository.id] = repository;
                    Logger.getInstance().trace('Repository id ' + repository.id + ' added');
                }
                var launcher = this;
                var accessCallback = function() {
                    if (this.succeed()) {
                        launcher.broker.publish('onAccessGranted');
                    } else {
                        launcher.broker.publish('onDisplayNotification', 'No access for "' + this._url + '" :-(');
                    }
                };
                Logger.getInstance().info('openning repository access');
                new XMLHttpRequest('repository/access.php').setAuthentication(this.userId, this.password).setMethod('post').send(null, accessCallback);
                
            } else {
            	Element.create({classname: 'jls/gui/Label', attributes: {text: '#repository.none'}}, container);
                this.broker.publish('onAccessGranted');
            }
        }
    },
    onAccessGranted: function() {
        Logger.getInstance().trace('onAccessGranted');
        var envelope = new SxeEnvelope();
        envelope.setAuthentication(this.userId, this.password);
        envelope.addRequest({name: 'listMedias'});
        envelope.addRequest({name: 'listCommentedMedias', attributes: {limit: 40}});
        this.sxe.send(envelope, this.broker.callback('onMediasLoaded'));
    },
    computeRootView : function(medias, commentedMedias, lastAuthenticationDate) {
        Logger.getInstance().info('computeRootView() ' + medias.length + ' media(s)');
        var root = new Group.View(this.resource.get('board.home'));
        if (medias.length == 0) {
            return root;
        }
        medias.sort(Media.compareMedias);
        // new medias
        if (lastAuthenticationDate != 0) {
            var mediasByPubDate = medias.concat([]);
            mediasByPubDate.sort(Media.compareMediasByPubDate);
            var recents = new Group.Medias(this.resource.get('board.recent'));
            var last = Math.max(0, mediasByPubDate.length - Launcher.MAX_MEDIA * 4);
            for (var i = mediasByPubDate.length - 1; (i >= last) && (mediasByPubDate[i]._pubDate > lastAuthenticationDate); i--) {
                recents.addItem(mediasByPubDate[i]);
            }
            recents.sort(Media.compareMedias);
            if (recents.size() > 0) {
                root.addItem(recents);
            }
        }
        // by date
        var rootByDate = new Group.List(this.resource.get('board.byDate'));
        var year = -1;
        var yearGroup = null;
        var monthFullNames = Formatter.getResource(_native.core.properties['user.language']).monthFullNames;
        var month = -1;
        var monthGroup = null;
        for (var i = 0; i < medias.length; i++) {
            var media = medias[i];
            var yearMedia = media.getDate().getFullYear();
            if (year != yearMedia) {
                yearGroup = new Group.List(yearMedia);
                year = yearMedia;
                rootByDate.addItem(yearGroup);
                month = -1;
                monthGroup = null;
            }
            var monthMedia = media.getDate().getMonth();
            if (month != monthMedia) {
                // create group
                monthGroup = new Group.Medias(monthFullNames[monthMedia]);
                month = monthMedia;
                yearGroup.addItem(monthGroup);
            }
            monthGroup.addItem(media);
        }
        root.addItem(rootByDate);
        // Commented medias
        var commentMap = {};
        if (commentedMedias) {
            for (var i = 0; i < commentedMedias.length; i++) {
                var cm = commentedMedias[i];
                commentMap[cm.mediaId] = cm.lastDate;
            }
        }
        // Films
        var movies = new Group.Medias(this.resource.get('board.movies'));
        var comments = new Group.RevertedMedias(this.resource.get('board.commented'));
        for (var i = 0; i < medias.length; i++) {
            var m = medias[i];
            if (m instanceof Media.Movie) {
                movies.addItem(m);
            }
            if (commentMap[m.getId()]) {
                comments.addItem(m);
            }
        }
        if (movies.size() > 0) {
            root.addItem(movies);
        }
        if (comments.size() > 0) {
            Logger.getInstance().info('sort comments, first: ' + comments.getItem(0).getId() + ', date: ' + commentMap[comments.getItem(0).getId()]);
            comments.sort(function(a, b) {
                var aDate = commentMap[a.getId()];
                var bDate = commentMap[b.getId()];
                if (aDate < bDate)
                    return -1;
                if (aDate > bDate)
                    return 1;
                return 0;
            });
            Logger.getInstance().info('sorted comments, first: ' + comments.getItem(0).getId() + ', date: ' + commentMap[comments.getItem(0).getId()]);
            root.addItem(comments);
        }
        // Random
        var rand = new Group.Medias(this.resource.get('board.random'));
        for (var i = 0; i < Launcher.MAX_MEDIA; i++) {
            var m = medias[Math.floor(Math.random() * medias.length)];
            rand.addItem(m);
        }
        root.addItem(rand);
        return root;
    },
    onMediasLoaded: function(event) {
        Logger.getInstance().trace('onMediasLoaded()');
        var envelope = event.message.getEnvelope();
        var result = envelope.getResult(0);
        if (result.getName() != 'mediaListResult') {
            return;
        }
        var mediaNodes = result.getChildren();
        this.medias = [];
        for (var i = 0; i < mediaNodes.length; i++) {
            this.medias.push(Media.createFromXML(mediaNodes[i]));
        }
        result = envelope.getResult(1);
        if (result.getName() != 'commentListResult') {
            return;
        }
        var commentedMedias = [];
        var commentNodes = result.getChildren();
        for (var i = 0; i < commentNodes.length; i++) {
            var node = commentNodes[i];
            var mediaId = parseInt(node.getAttribute('mediaId'), 10);
            var lastDate = new Date(parseInt(node.getAttribute('lastDate'), 10) * 1000);
            commentedMedias.push({
                mediaId: mediaId,
                lastDate: lastDate
            });
        }
        this.root = this.computeRootView(this.medias, commentedMedias, this.user.lastAuthenticationDate);
        this.changeContext(new Context('group', this.root, 0));
        this.toolbar.getStyle().setProperty('display', '');
    },
    onDisplayMedia: function(event) {
        event.message.preventDefault();
        this.changeContext(this.currentContext.createView('media'));
    },
    onDisplayBoard: function(event) {
        event.message.preventDefault();
        this.changeContext(this.currentContext.createView('board'));
    },
    onDisplayBoardFull: function(event) {
        event.message.preventDefault();
        // TODO Add a sticky tag full on the group and just display the board
        this.changeContext(this.currentContext.createView('boardFull'));
    },
    onDisplayGroup: function(event) {
        event.message.preventDefault();
        var group = this.currentContext.group;
        if (group instanceof Group.Medias) {
            group = group.getParent();
        }
        this.changeContext(new Context('group', group, 0));
    },
    displayMedia: function(context) {
        Logger.getInstance().trace('displayMedia()');
        var media = context.getMedia();
        this.mediaToolbar.getStyle().setProperty('display', '');
        this.repositoryToolbar.getStyle().setProperty('display', 'none');
        var mediaContainer = this.viewport.openMedia();
        media.createElement({group: context.group, index: context.mediaIndex, attributes: {'class': 'media'}}, mediaContainer);
        if (media.getCommentCount() == 0) {
            return;
        }
        if (media.getCommentCount() != media.getComments().length) {
        	var commentContainer = this.viewport.openComment();
            new HtmlElement({attributes: {htmlTagName: 'img', src: 'style/progress.gif'}}, commentContainer);
            var envelope = new SxeEnvelope(this.userId, this.password);
            envelope.addRequest({name: 'listComment', attributes: {mediaId: media.getId()}});
            this.sxe.send(envelope, (function(response) {
                var envelope = response.getEnvelope();
                //Logger.getInstance().debug('response: -->' + envelope.toString() + '<--');
                var result = envelope.getResult(0);
                if (result.getName() == 'commentListResult') {
                	media.clearComments();
                    for (var i = 0; i < result.getChildCount(); i++) {
                        var comment = Comment.createFromXML(result.getChild(i));
                        media.addComment(comment);
                        Logger.getInstance().trace('Comment id ' + comment.getId() + ' added');
                    }
                }
                this.displayMediaComments(context);
            }).bind(this));
        } else {
            this.displayMediaComments(context);
        }
    },
    displayMediaComments: function(context) {
    	var container = this.viewport.openComment();
        var media = context.getMedia();
        for (var i = 0; i < media.getComments().length; i++) {
            var comment = media.getComments()[i];
            var commentElements = [
                {classname: 'jls/html/HtmlElement', attributes: {htmlTagName: 'div', 'class': 'commentHeader',
                    textContent: this.formatDate(comment.getDate()) + ', ' + this.users[comment.getUserId()].login}}
            ];
            var lines = comment.getContent().split('\n');
            for (var j = 0; j < lines.length; j++) {
                commentElements.push({classname: 'jls/html/HtmlElement', attributes: {htmlTagName: 'div', textContent: lines[j]}});
            }
            new HtmlElement({attributes: {htmlTagName: 'div', 'class': 'comment'}, children: commentElements}, container);
        }
    },
    createBreadcrumb: function(group, parent) {
        var groups = [];
        while (group) {
            groups.push(group);
            group = group.getParent();
        }
        if (groups.length == 1) {
            return;
        }
        var p = new HtmlElement({attributes: {htmlTagName: 'p'}, style: {textAlign: 'left'}}, parent);
        for (var i = groups.length - 1; i >= 0; i--) {
            if (i > 0) {
                var e = new HtmlElement({attributes: {htmlTagName: 'a', href: '#', textContent: groups[i].getName()}, events: {click: 'onOpenGroup'}}, p);
                e._group = groups[i];
                e.getGroup = GroupPreview.prototype.getGroup;
                new HtmlElement({attributes: {htmlTagName: 'span', htmlContent: '&nbsp;&raquo;&nbsp;'}}, p);
            } else {
                new HtmlElement({attributes: {htmlTagName: 'span', textContent: groups[i].getName()}}, p);
            }
        }
    },
    displayBoard: function(context, full) {
        Logger.getInstance().trace('displayBoard()');
        this.mediaToolbar.getStyle().setProperty('display', 'none');
        this.repositoryToolbar.getStyle().setProperty('display', 'none');
    	var container = this.viewport.openBoard();
        this.createBreadcrumb(context.group, container);
        var table = Element.create(this.elements.boardTable, container);
        var mediaCount = context.group.getMediaCount();
        if ((! (full || (context.group instanceof Group.Medias))) && (mediaCount > Launcher.MAX_MEDIA)) {
            //new HtmlElement({attributes: {htmlTagName: 'img', src: 'style/list-add.png'}, style: {margin: '30px', cursor: 'pointer'}, events: {click: 'onDisplayBoardFull'}}, container);
            new HtmlElement({attributes: {htmlTagName: 'a', href: '#', textContent: this.resource.getf('seeAllMedias', mediaCount)},
                style: {margin: '30px'}, events: {click: 'onDisplayBoardFull'}}, container);
            mediaCount = Launcher.MAX_MEDIA;
        }
        for (var i = 0; i < mediaCount; i++) {
            var media = context.group.getMedia(i);
            Logger.getInstance().trace('createMiniature(' + i + ')');
            media.createMiniature({group: context.group, index: i, attributes: {'class': 'media'}, events: {click: 'onOpenMedia'}}, table);
        }
    },
    displayGroup: function(context) {
        Logger.getInstance().trace('displayGroup()');
        this.mediaToolbar.getStyle().setProperty('display', 'none');
        this.repositoryToolbar.getStyle().setProperty('display', context.group.getParent() ? 'none' : '');
    	var container = this.viewport.openGroup();
        this.createBreadcrumb(context.group, container);
        var table = Element.create(this.elements.groupTable, container);
        var size = context.group.size();
        for (var i = 0; i < size; i++) {
            var item = context.group.getItem(i);
            var groupCell = Element.create(this.elements.groupCell, table);
            groupCell.setGroup(item);
        }
    },
    displayContext: function(context) {
        Logger.getInstance().trace('displayContext(' + context.viewName + ')');
        this.mediaIcon.setAttribute('class', 'icon');
        this.boardIcon.setAttribute('class', 'icon');
        this.groupIcon.setAttribute('class', 'icon');
        switch (context.viewName) {
        case 'media':
            this.mediaIcon.setAttribute('class', 'iconSelected');
            this.displayMedia(context);
            break;
        case 'board':
        case 'boardFull':
            this.boardIcon.setAttribute('class', 'iconSelected');
            this.displayBoard(context, context.viewName == 'boardFull');
            break;
        case 'group':
            this.groupIcon.setAttribute('class', 'iconSelected');
            this.displayGroup(context);
            break;
        }
    },
    changeContext: function(context) {
        Logger.getInstance().trace('changeContext(' + context.viewName + ')');
        var onlyMedia = false;
        if ((this.currentContext != null) &&
            (this.currentContext.viewName == context.viewName) &&
            (this.currentContext.group == context.group))
        {
            if (this.currentContext.mediaIndex == context.mediaIndex) {
                return;
            } else {
                onlyMedia = true;
            }
        }
        // Reset navigation context
        this.comment = null;
        
        // Save scroll position
        var scrollPos = this.viewport._htmlElement.scrollTop;
        if (this.currentContext != null) {
            switch (this.currentContext.viewName) {
            case 'board':
                this.currentContext.group._scrollBoardHistory = scrollPos;
                break;
            case 'group':
                this.currentContext.group._scrollGroupHistory = scrollPos;
                break;
            }
        }
        Logger.getInstance().trace('Saving scroll position: ' + scrollPos);
        
        this.currentContext = context;
        
        // History
        if (this.contextHistoryIndex != this.contextHistory.length - 1) {
            this.contextHistory.length = this.contextHistoryIndex + 1;
        }
        this.contextHistoryIndex++;
        this.contextHistory[this.contextHistoryIndex] = this.currentContext;
        if (onlyMedia) {
            this.displayMedia(this.currentContext);
        } else {
            this.displayContext(this.currentContext);
        }

        // Restore scroll position
        var scrollPos = -1;
        switch (this.currentContext.viewName) {
        case 'board':
            scrollPos = 0;
            if ('_scrollBoardHistory' in this.currentContext.group) {
                scrollPos = this.currentContext.group._scrollBoardHistory;
            }
            break;
        case 'group':
            scrollPos = 0;
            if ('_scrollGroupHistory' in this.currentContext.group) {
                scrollPos = this.currentContext.group._scrollGroupHistory;
            }
            break;
        }
        if (scrollPos >= 0) {
            Logger.getInstance().trace('Restore scroll position: ' + scrollPos);
            this.viewport._htmlElement.scrollTop = scrollPos;
        }
    },
    onGoNext: function(event) {
        event.message.preventDefault();
        if (this.contextHistoryIndex < this.contextHistory.length - 1) {
            this.contextHistoryIndex++;
            this.currentContext = this.contextHistory[this.contextHistoryIndex];
            this.displayContext(this.currentContext);
        } else {
            alert('no more history ' + this.contextHistoryIndex + '/' + this.contextHistory.length);
        }
    },
    onGoPrevious: function(event) {
        event.message.preventDefault();
        if (this.contextHistoryIndex > 0) {
            this.contextHistoryIndex--;
            this.currentContext = this.contextHistory[this.contextHistoryIndex];
            this.displayContext(this.currentContext);
        } else {
            alert('no more history ' + this.contextHistoryIndex + '/' + this.contextHistory.length);
        }
    },
    onGoHome: function(event) {
        event.message.preventDefault();
        this.changeContext(new Context('group', this.root, 0));
    },
    openGroup: function(grp) {
        this.changeContext(new Context((grp instanceof Group.Medias) ? 'board' : 'group', grp, 0));
    },
    onOpenGroup: function(event) {
        event.message.preventDefault();
        this.openGroup(event.message.element.getGroup());
    },
    onOpenMedia: function(event) {
        event.message.preventDefault();
        var grp = event.message.element.getGroup();
        var idx = event.message.element.getIndex();
        //var media = event.message.element.getMedia();
        this.changeContext(new Context('media', grp, idx));
    },
    onComment: function(event) {
        event.message.preventDefault();
        if (this.comment) {
            return;
        }
    	var container = this.viewport.openComment();
        var div = Element.create(this.elements.addComment, container);
        div.getById('ok').observe('click', (function (e) {
            var media = this.currentContext.getMedia();
            var content = div.getById('newComment').getHtmlElement().value;
            Logger.getInstance().debug('content: -->' + content + '<--');
            var envelope = new SxeEnvelope(this.userId, this.password);
            envelope.addRequest({name: 'addComment', attributes: {mediaId: media.getId(), content: content}});
            this.sxe.send(envelope, (function(response) {
                var envelope = response.getEnvelope();
                //Logger.getInstance().debug('response: -->' + envelope.toString() + '<--');
                var result = envelope.getResult(0);
                if (result.getName() == 'success') {
                    Logger.getInstance().trace('Comment added');
                    media.newComment(this.user.id, content);
                    div.destroy();
                    this.comment = null;
                    this.displayMediaComments(this.currentContext);
                    this.root.getItemByName(this.resource.get('board.commented')).addItem(media);
                } else {
                    var msg = this.resource.get('error');
                    if (result.getName() == 'failure') {
                        msg = this.resource.getf('error.message', result.getText());
                    }
                    this.broker.publish('onDisplayInfo', msg);
                }
            }).bind(this));
        }).bind(this));
        div.getById('cancel').observe('click', (function (e) {
            div.destroy();
            this.comment = null;
        }).bind(this));
        this.comment = true;
        div.getById('newComment').getHtmlElement().focus();
    },
    onGoBackward: function(event) {
        event.message.preventDefault();
        this.changeContext(this.currentContext.createMedia(-1));
    },
    onGoUp: function(event) {
        event.message.preventDefault();
        this.openGroup(this.currentContext.group);
    },
    onGoForward: function(event) {
        event.message.preventDefault();
        this.changeContext(this.currentContext.createMedia(1));
    }
});

Object.extend(Launcher,
{
    _instance: null,
    MAX_MEDIA: 16,
    scrollHistory : {},
    hasRight: function(rights, right) {
        if (rights && right) {
            var ra = rights.split(',');
            for (var i = 0; i < ra.length; i++) {
                if (ra[i] == right) {
                    return true;
                }
            }
        }
        return false;
    },
    sha1: function(value) {
        var md = MessageDigest.getInstance('SHA1');
        md.updateString(value, 'UTF-8');
        var buffer = md.digest();
        return StringCodec.hexEncode(buffer);
    },
    getInstance: function() {
        if (Launcher._instance == null) {
        	Launcher._instance = new Launcher('default');
            //Launcher._instance = new Launcher('mobile');
        }
        return Launcher._instance;
    }
});


return Launcher;
});
