/**
 *  Copyright © 2003-2025, Luis Andrés Lange <https://javacomm.net>
 *
 *  This Source Code Form is subject to the terms of the Mozilla Public
 *  License, v. 2.0. If a copy of the MPL was not distributed with this
 *  file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 *  ----------------------------------------------------------------------------
 *
 *  Exhibit B - "Incompatible With Secondary Licenses" Notice
 *
 *  This Source Code Form is "Incompatible With Secondary Licenses",
 *  as defined by the Mozilla Public License, v. 2.0.
 *
 *  In short:
 *  - This file may be used, modified, and distributed under MPL 2.0 only.
 *  - It may NOT be relicensed under GPL, LGPL, AGPL, or any other Secondary License.
 *
 *  Rationale:
 *  - Ensures that the code remains MPL-2.0.
 *  - Avoids legal conflicts with GPL-licensed libraries (e.g., VideoLAN).
 *  - Maximizes usability for commercial and security-critical applications.
 *
 */
package net.javacomm.client.base;

import static net.javacomm.protocol.HEADER.CONFIRM;
import static net.javacomm.protocol.HEADER.REQUEST;
import static net.javacomm.protocol.HEADER.RESPONSE;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.pump.plaf.CircularProgressBarUI;
import jakarta.websocket.DeploymentException;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation.Builder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Form;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.core.Response.StatusType;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.ValidationException;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.BindException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.UnresolvedAddressException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.security.PublicKey;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.crypto.SecretKey;
import javax.imageio.ImageIO;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer.Info;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.Action;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JDesktopPane;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.SwingWorker.StateValue;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.WindowConstants;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.nexuswob.gui.MediaFileChooser;
import org.nexuswob.gui.swing.JApplication;
import org.nexuswob.util.Util;
import org.nexuswob.util.WatchService;
import org.xml.sax.SAXException;
import net.javacomm.client.administer.AdministratorFrame;
import net.javacomm.client.administer.JRegistryFrame;
import net.javacomm.client.administer.JUpdateProfilFrame;
import net.javacomm.client.administer.PasswortAnfordernButton;
import net.javacomm.client.administer.RegistryButton;
import net.javacomm.client.administer.RemoveRoomfilterWorker;
import net.javacomm.client.administer.StringException;
import net.javacomm.client.administer.WriteRoomfilterWorker;
import net.javacomm.client.base.JLogon.STATUS;
import net.javacomm.client.chat.JBesprechungsraum;
import net.javacomm.client.chat.JChatFrame;
import net.javacomm.client.chat.JChatPane.Botschaft;
import net.javacomm.client.chat.JForum;
import net.javacomm.client.chat.JGruppenraum;
import net.javacomm.client.chat.JPausenraum;
import net.javacomm.client.chat.JPrivateChat;
import net.javacomm.client.chat.JTicker.Speicherort;
import net.javacomm.client.chat.MessageException;
import net.javacomm.client.config.schema.Config;
import net.javacomm.client.config.schema.Directories;
import net.javacomm.client.config.schema.Directory;
import net.javacomm.client.config.schema.FileTransferService;
import net.javacomm.client.config.schema.ISO6391;
import net.javacomm.client.config.schema.Land;
import net.javacomm.client.config.schema.Livestream;
import net.javacomm.client.config.schema.Livestreams;
import net.javacomm.client.config.schema.Login;
import net.javacomm.client.config.schema.Mediaplayer;
import net.javacomm.client.config.schema.Sorte;
import net.javacomm.client.config.schema.Sprache;
import net.javacomm.client.environment.Configfile;
import net.javacomm.client.environment.Environment;
import net.javacomm.client.environment.GUI;
import net.javacomm.client.environment.JerseyClientFactory;
import net.javacomm.client.filetransfer.Channel;
import net.javacomm.client.filetransfer.ChannelEvent;
import net.javacomm.client.filetransfer.ChannelException;
import net.javacomm.client.filetransfer.ChannelListener;
import net.javacomm.client.filetransfer.Channels;
import net.javacomm.client.filetransfer.FTServer;
import net.javacomm.client.filetransfer.JFTServiceFrame;
import net.javacomm.client.filetransfer.JSchwarzeListeDialog;
import net.javacomm.client.filetransfer.JServerKonfigurierenFrame;
import net.javacomm.client.filetransfer.PortException;
import net.javacomm.client.filetransfer.Slot;
import net.javacomm.client.filetransfer.Transport;
import net.javacomm.client.gui.JSliderVolumePanel;
import net.javacomm.client.info.Desktop;
import net.javacomm.client.info.JInfoPane;
import net.javacomm.client.launcher.JLauncher;
import net.javacomm.client.mediaplayer.DvdException;
import net.javacomm.client.mediaplayer.FilterException;
import net.javacomm.client.mediaplayer.JPlayerInternalFrame;
import net.javacomm.client.mediaplayer.MediaException;
import net.javacomm.client.mediaplayer.NoAudiofileException;
import net.javacomm.client.mediaplayer.NoDirectoryException;
import net.javacomm.client.mediaplayer.NullPathException;
import net.javacomm.client.mediaplayer.PLAYERMODE;
import net.javacomm.client.mediaplayer.PlayerFilter;
import net.javacomm.client.mediaplayer.ShuffleException;
import net.javacomm.client.phone.JAbnehmenFrame;
import net.javacomm.client.phone.JAnrufenFrame;
import net.javacomm.client.phone.JTelefonEinrichtenFrame;
import net.javacomm.client.phone.MicProducer;
import net.javacomm.client.phone.MicronameException;
import net.javacomm.client.phone.MikrofonException;
import net.javacomm.client.phone.MixerNotFoundException;
import net.javacomm.client.phone.SoundClip;
import net.javacomm.client.phone.TelefonUtil;
import net.javacomm.client.phone.Telefonformat;
import net.javacomm.client.phone.TelefonformatException;
import net.javacomm.client.phone.VolumeException;
import net.javacomm.client.resource.Resource;
import net.javacomm.client.screencaster.EinwilligungInternalFrame;
import net.javacomm.client.screencaster.ImageCaster;
import net.javacomm.client.screencaster.ProjektorInternalFrame;
import net.javacomm.client.screencaster.TeilenInternalFrame;
import net.javacomm.client.streams.StreamFrame;
import net.javacomm.client.telko.ConferenceSpeaker;
import net.javacomm.client.telko.JTelefonkonferenzErstellenFrame;
import net.javacomm.client.telko.JTelefonkonferenzOrganisierenFrame;
import net.javacomm.client.telko.JTelefonkonferenzTeilnehmenFrame;
import net.javacomm.client.telko.KonferenznameException;
import net.javacomm.client.telko.MicConferenceConsumer;
import net.javacomm.client.telko.TelkoProjektorInternalFrame;
import net.javacomm.client.websocket.WebsocketClient;
import net.javacomm.client.websocket.WebsocketEvent;
import net.javacomm.client.websocket.WebsocketEventAdapter;
import net.javacomm.multilingual.Babelfish;
import net.javacomm.multilingual.MultilingualButton;
import net.javacomm.multilingual.MultilingualCheckBoxMenuItem;
import net.javacomm.multilingual.MultilingualInternalFrame;
import net.javacomm.multilingual.MultilingualLabel;
import net.javacomm.multilingual.MultilingualMenu;
import net.javacomm.multilingual.MultilingualMenuItem;
import net.javacomm.multilingual.MultilingualString;
import net.javacomm.multilingual.schema.ISO639;
import net.javacomm.multilingual.schema.KEY;
import net.javacomm.protocol.Agent;
import net.javacomm.protocol.BEAMEROFF;
import net.javacomm.protocol.Benutzerstatus;
import net.javacomm.protocol.CALLPRIVATECHAT;
import net.javacomm.protocol.CALLREMOTEUSER;
import net.javacomm.protocol.CANDIDATETOPICMEMBER;
import net.javacomm.protocol.CBlock;
import net.javacomm.protocol.CBlockProvider;
import net.javacomm.protocol.CHANGETOPICMEMBER;
import net.javacomm.protocol.CHATMESSAGE;
import net.javacomm.protocol.CHATONLINELIST;
import net.javacomm.protocol.CHATUSERLIST;
import net.javacomm.protocol.CONFERENCE;
import net.javacomm.protocol.CONFERENCEAUDIO;
import net.javacomm.protocol.CONFERENCEMUTE;
import net.javacomm.protocol.CONFERENCEVIDEO;
import net.javacomm.protocol.CREATETEMPROOM;
import net.javacomm.protocol.ChatUser;
import net.javacomm.protocol.Command;
import net.javacomm.protocol.DELETEROOM;
import net.javacomm.protocol.DELETEUPLOADFILES;
import net.javacomm.protocol.DIAL;
import net.javacomm.protocol.DOWNLOAD;
import net.javacomm.protocol.ENTERROOM;
import net.javacomm.protocol.Entry;
import net.javacomm.protocol.FILETYPES;
import net.javacomm.protocol.HEADER;
import net.javacomm.protocol.HISTORYMESSAGE;
import net.javacomm.protocol.IMAGE;
import net.javacomm.protocol.INCOMINGCALL;
import net.javacomm.protocol.KonferenzraumUser;
import net.javacomm.protocol.LEAVEPRIVATECHAT;
import net.javacomm.protocol.LEAVEROOM;
import net.javacomm.protocol.Language;
import net.javacomm.protocol.Lifetime;
import net.javacomm.protocol.MESSAGE;
import net.javacomm.protocol.MICROERROR;
import net.javacomm.protocol.NEWIMAGE;
import net.javacomm.protocol.NEWMOUSEPOINTER;
import net.javacomm.protocol.ONCALL;
import net.javacomm.protocol.ONHOOK;
import net.javacomm.protocol.PRIVATECHATFILE;
import net.javacomm.protocol.PRIVATEMESSAGE;
import net.javacomm.protocol.PROYECTORCLOSING;
import net.javacomm.protocol.Protocol;
import net.javacomm.protocol.READROOMS;
import net.javacomm.protocol.READTOPICROOMOWNER;
import net.javacomm.protocol.ROOMLIST;
import net.javacomm.protocol.RecordInterface;
import net.javacomm.protocol.Room;
import net.javacomm.protocol.Roomtype;
import net.javacomm.protocol.SEARCHFILES;
import net.javacomm.protocol.SIGNIN;
import net.javacomm.protocol.STOPSENDINGVIDEO;
import net.javacomm.protocol.TOPICMEMBER;
import net.javacomm.protocol.Token;
import net.javacomm.protocol.UPDATEFILETYPES;
import net.javacomm.protocol.UPDATEPHONE;
import net.javacomm.protocol.UPDATEPORT;
import net.javacomm.protocol.UPDATEUSER;
import net.javacomm.protocol.UPLOADFILES;
import net.javacomm.protocol.USERONLINELIST;
import net.javacomm.protocol.USRLOGIN;
import net.javacomm.protocol.Uploadfile;
import net.javacomm.protocol.UserOnline;
import net.javacomm.protocol.Userdata;
import net.javacomm.protocol.crypto.AES_Bitlength;
import net.javacomm.protocol.crypto.Crypto;
import net.javacomm.restserver.MessageBodyArrayList;
import net.javacomm.restserver.MessageBodyCONFERENCE;
import net.javacomm.restserver.MessageBodyHISTORYMESSAGE;
import net.javacomm.restserver.MessageBodyOutdated;
import net.javacomm.restserver.MessageBodySEARCHFILES;
import net.javacomm.restserver.MessageBodySIGNIN;
import net.javacomm.restserver.MessageBodyTransferFiletransferConfig;
import net.javacomm.restserver.MessageBodyTransferKonferenzraum;
import net.javacomm.restserver.MessageBodyTransferOrganizeConferenceCall;
import net.javacomm.restserver.MessageBodyTransferUser;
import net.javacomm.restserver.Wrapper;
import net.javacomm.share.Constants;
import net.javacomm.transfer.Helado;
import net.javacomm.transfer.Sprachguete;
import net.javacomm.transfer.Starttype;
import net.javacomm.transfer.TransferBenutzerkonto;
import net.javacomm.transfer.TransferConfig;
import net.javacomm.transfer.TransferFiletransferConfig;
import net.javacomm.transfer.TransferKonferenzraum;
import net.javacomm.transfer.TransferOrganizeConferenceCall;
import net.javacomm.transfer.TransferOutdated;
import net.javacomm.transfer.TransferTelefon;
import net.javacomm.transfer.TransferTooltip;
import net.javacomm.transfer.TransferUser;
import net.javacomm.window.manager.Control;
import net.javacomm.window.manager.Frames;
import net.javacomm.window.manager.WM;
import net.javacomm.window.manager.WMResource;
import net.javacomm.window.manager.WindowAnrufen;
import net.javacomm.window.manager.WindowAnrufenEvent;
import net.javacomm.window.manager.WindowAnrufenListener;
import net.javacomm.window.manager.WindowManagerInternalFrame;
import io.nayuki.flac.app.DecodeFlacToWav;
import uk.co.caprica.vlcj.factory.discovery.NativeDiscovery;
import uk.co.caprica.vlcj.player.base.TitleDescription;



public class JChat extends JApplication {

  /**
   *
   *
   *
   * @author llange
   *
   */
  class WebsocketAction extends WebsocketEventAdapter {

    @Override
    public void onClose(WebsocketEvent event) {
      micTerminate(); // direkt hier
      speakerTerminate(); // direkt hier
      EventQueue.invokeLater(JChat.this::lineBroken);
    }



    @Override
    public void onError(WebsocketEvent event) {

    }



    @Override
    public void onMessage(WebsocketEvent event) {
      executorChat.execute(() -> incoming(event.getMessage()));
    }



    @Override
    public void onOpen(WebsocketEvent event) {
      executorChat.execute(() -> {
        if (jLogon == null) return; // Der Fehler tritt durch die Testroutine auf, weil login
        // mit jLOgin noch
        // gebrückt wird
        login(jLogon.getMail(), jLogon.getPassword(), jLogon.getMail());
      });
    }

  }

  private static final long serialVersionUID = -4936627732170220984L;
  private final static Logger log = LogManager.getLogger(JChat.class);
  public final static Sprachguete CONFERENECE_MEDIUM = Sprachguete.MEDIUM;
  public final static int UPLOAD_DOWNLAOD_BUFFERSIZE = 65535;

  private final static Dimension MINIMUM_SCREENSIZE = new Dimension(1330, 768);
  private final static Dimension PREFRRED_DIMENSION = new Dimension(1360, 792);
  public static final int CHAT_LAYER = 8;
  public static final int VIDEO_LAYER = 7;
  public static final int PLAYER_LAYER = 9;
  private final static String USER = "user";
  private final static String PASSWORD = "password";
  private final static String DOMAIN = "domain";
  private final static String LANGUAGE = "language";

  // autoupdate
  final static Path zipPortable = Paths.get("../", "windows.zip");
  private AtomicBoolean isOccupied = new AtomicBoolean(false);

  private Desktop desktop = new Desktop();
  private JLogon jLogon;
  private WindowAnrufen windowAnrufen = new WindowAnrufen();
  private GridBagLayout gridbag = new GridBagLayout();
  private GridBagConstraints con = new GridBagConstraints();
  private JMenuBar menubar = new JMenuBar();
  private MultilingualMenu menuBildschirm = new MultilingualMenu(KEY.BILDSCHIRM);
  private MultilingualMenuItem itemBildschirmEmpfangen = new MultilingualMenuItem(KEY.BILDSCHIRM_EMPFANGEN);
  private MultilingualMenu menuStreams = new MultilingualMenu(KEY.STREAMS);
  private JMenuItem itemLiveStreams = new JMenuItem("Live Streams");

  private JMenuItem itemIcons = new JMenuItem("Icons by Icons8");

  private MultilingualMenuItem itemTeilenframe = new MultilingualMenuItem(KEY.BILDSCHIRM_UEBERTRAGEN);
  private MultilingualMenuItem itemVerwalten = new MultilingualMenuItem(KEY.ADMINISTRATOR_VERWALTEN);
  private MultilingualMenu menuJavacomm = new MultilingualMenu(KEY.JAVACOMM);
  private MultilingualMenu menuKonto = new MultilingualMenu(KEY.KONTO);
  private MultilingualMenu menuEis = new MultilingualMenu(KEY.EIS);
  private MultilingualCheckBoxMenuItem itemMokka = new MultilingualCheckBoxMenuItem(KEY.MOKKA);
  private MultilingualCheckBoxMenuItem itemVanille = new MultilingualCheckBoxMenuItem(KEY.VANILLE);
  private MultilingualCheckBoxMenuItem itemJoghurt = new MultilingualCheckBoxMenuItem(KEY.JOGHURT);
  private MultilingualCheckBoxMenuItem itemBlaubeere = new MultilingualCheckBoxMenuItem(KEY.BLAUBEERE);
  private MultilingualCheckBoxMenuItem itemErdbeere = new MultilingualCheckBoxMenuItem(KEY.ERDBEERE);
  private MultilingualCheckBoxMenuItem itemZitrone = new MultilingualCheckBoxMenuItem(KEY.ZITRONE);
  private MultilingualMenu menuOnlinetransfer = new MultilingualMenu(KEY.ONLINETRANSFER);
  private MultilingualMenuItem itemServerKonfigurieren = new MultilingualMenuItem(KEY.SERVER_KONFIGURIEREN);
  private MultilingualMenuItem itemHochladenRunterladen = new MultilingualMenuItem(
      KEY.HOCHLADEN_HERUNTERLADEN
  );
  private MultilingualMenu menuChat = new MultilingualMenu(KEY.CHAT);
  private MultilingualMenuItem itemSchwarzeListe = new MultilingualMenuItem(KEY.SCHWARZE_LISTE);
  private MultilingualMenuItem itemBearbeiten = new MultilingualMenuItem(KEY.BERABEITEN);
  private MultilingualMenuItem itemAufWiedersehen = new MultilingualMenuItem(KEY.AUF_WIEDERSEHEN);
  private MultilingualMenuItem itemRegistrieren = new MultilingualMenuItem(KEY.REGISTRIEREN);
  private MultilingualMenuItem itemAnmelden = new MultilingualMenuItem(KEY.ANMELDEN);
  private MultilingualMenuItem itemNeuesPasswortAnfordern = new MultilingualMenuItem(
      KEY.NEUES_PASSWORT_ANFORDERN
  );
  private MultilingualMenuItem itemLaunch = new MultilingualMenuItem(KEY.ALLE_RAEUME_ANZEIGEN);
  private MultilingualMenuItem itemForum = new MultilingualMenuItem(KEY.FORUM);
  private MultilingualMenuItem itemBesprechungsraum = new MultilingualMenuItem(
      KEY.BESPRECHUNGSRAEUME_VERWALTEN
  );
  private MultilingualMenuItem itemGruppenraum = new MultilingualMenuItem(KEY.GRUPPENRAUM_EINRICHTEN);
  private MultilingualMenuItem itemPausenraum = new MultilingualMenuItem(KEY.PAUSENRAEUME_VERWALTEN);

  private MultilingualCheckBoxMenuItem itemChatanfragenAblehnen = new MultilingualCheckBoxMenuItem(
      KEY.CHATANFRAGEN_ABLEHNEN
  );
  private MultilingualMenuItem itemPrivateTalk = new MultilingualMenuItem(KEY.PRIVATGESPRAECH);
  private ButtonGroup groupEis = new ButtonGroup();

  private MultilingualMenu menuMediaPlayer = new MultilingualMenu(KEY.MEDIENWIEDERGABE);
  private MultilingualMenuItem itemTitel = new MultilingualMenuItem(KEY.TITEL);
  private MultilingualMenu menuGaming = new MultilingualMenu(KEY.GAMING);
  private MultilingualMenu menuAdministrator = new MultilingualMenu(KEY.ADMINISTRATOR);
  private MultilingualMenu menuPhone = new MultilingualMenu(KEY.TELEFON);
  private MultilingualMenu menuInfo = new MultilingualMenu(KEY.INFO);

  private MultilingualMenuItem itemTelefonEinrichten = new MultilingualMenuItem(KEY.TELEFON_EINRICHTEN);
  private MultilingualMenuItem itemAnrufen = new MultilingualMenuItem(KEY.FRAME_ANRUFEN);
  private MultilingualMenuItem itemTelkoErstellen = new MultilingualMenuItem(
      KEY.FRAME_TELEFONKONFERENZ_ERSTELLEN
  );
  private MultilingualMenuItem itemTelkoOrganisieren = new MultilingualMenuItem(KEY.STRING_ORGANISIEREN);
  private MultilingualMenuItem itemTelkoTeilnehmen = new MultilingualMenuItem(KEY.STRING_TEILNEHMEN);
  private MultilingualMenu menuHintergrundbild = new MultilingualMenu(KEY.TAPETE);
  private MultilingualMenuItem itemTapeteAnkleben = new MultilingualMenuItem(KEY.TAPETE_ANKLEBEN);
  private MultilingualMenuItem itemTapeteAbreissen = new MultilingualMenuItem(KEY.TAPETE_ABREISSEN);
  private MultilingualMenuItem itemLizenz = new MultilingualMenuItem(KEY.LIZENZ);
  private MultilingualCheckBoxMenuItem itemSpielliste = new MultilingualCheckBoxMenuItem(KEY.SPIELLISTE);
  private MultilingualMenu menuMusikkiste = new MultilingualMenu(KEY.MUSIKKISTE);
  private MultilingualMenuItem itemDVD = new MultilingualMenuItem(KEY.DVD);
  private MultilingualMenu menuInhaltsverzeichnis = new MultilingualMenu(KEY.INHALSVERZEICHNIS);
  private MultilingualMenu menuFilmsprache = new MultilingualMenu(KEY.FILMSPRACHE);
  private MultilingualCheckBoxMenuItem itemDeutsch = new MultilingualCheckBoxMenuItem(
      Mnemonics.DEUTSCH.toString()
  );
  private MultilingualCheckBoxMenuItem itemEnglisch = new MultilingualCheckBoxMenuItem(
      Mnemonics.ENGLISCH.toString()
  );
  private MultilingualCheckBoxMenuItem itemFranzoesisch = new MultilingualCheckBoxMenuItem(
      Mnemonics.FRANZOESISCH.toString()
  );
  private MultilingualCheckBoxMenuItem itemSpanisch = new MultilingualCheckBoxMenuItem(
      Mnemonics.SPANISCH.toString()
  );
  private MultilingualCheckBoxMenuItem itemItalienisch = new MultilingualCheckBoxMenuItem(
      Mnemonics.ITALIENISCH.toString()
  );
  private MultilingualCheckBoxMenuItem itemTuerkisch = new MultilingualCheckBoxMenuItem(
      Mnemonics.TUERKISCH.toString()
  );
  private MultilingualCheckBoxMenuItem itemPortugiesisch = new MultilingualCheckBoxMenuItem(
      Mnemonics.PORTUGUES.toString()
  );
  private MultilingualCheckBoxMenuItem itemRussisch = new MultilingualCheckBoxMenuItem(
      Mnemonics.RUSSISCH.toString()
  );
  private ButtonGroup sprachgroup = new ButtonGroup();
  private MultilingualMenuItem itemKisteLeeren = new MultilingualMenuItem(KEY.MUSIKKISTE_LEEREN);
  private MultilingualCheckBoxMenuItem randomItem;
  private MultilingualCheckBoxMenuItem telefonateAblehnen = new MultilingualCheckBoxMenuItem(
      KEY.TELEFONATE_ABLEHNEN
  );
  private MultilingualCheckBoxMenuItem itemVollbildmodus = new MultilingualCheckBoxMenuItem(
      KEY.VOLLBILDMODUS
  );
  private MultilingualCheckBoxMenuItem itemFensterImHintergrund = new MultilingualCheckBoxMenuItem(
      KEY.FENSTER_IM_HINTERGRUND_HALTEN
  );
  private MultilingualMenu menuFlag = new MultilingualMenu(KEY.SPRACHE);
  private JCheckBoxMenuItem spracheDeutsch = new JCheckBoxMenuItem(Mnemonics.DEUTSCH.toString());
  private JCheckBoxMenuItem spracheEnglisch = new JCheckBoxMenuItem(Mnemonics.ENGLISCH.toString());
  private JCheckBoxMenuItem spracheSpanisch = new JCheckBoxMenuItem(Mnemonics.SPANISCH.toString());
  private ButtonGroup buttonGroupSprache = new ButtonGroup();
  private JAnrufenFrame callerframe;
  private JAbnehmenFrame empfaengerFrame;
  private boolean restrictionOn;
  private JRegistryFrame registryFrame;
  private JChatFrame chatFrame;
  private JTelefonkonferenzErstellenFrame telefonErstellenFrame;
  private JTelefonEinrichtenFrame telefonFrame;
  private JTelefonkonferenzTeilnehmenFrame telefonTeilnehmenFrame;
  private ProjektorInternalFrame projektorframe;
  private TelkoProjektorInternalFrame telkoprojektorframe;
  private JUpdateProfilFrame updateProfil;
  private JLauncher launcher;
  private JBesprechungsraum besprechungsraum;
  private JPausenraum pausenraum;
  private JGruppenraum gruppenraum;
  private JForum forum;
  private URL url;
  private JFTServiceFrame ftsFrame;
  private MediaFileChooser mediaplayerChooser;
  private MediaFileChooser shuffleChooser;
  private MediaFileChooser dvdChooser;
  private MediaFileChooser anlageChooser;
  private Class<? extends JChat> resource;
  private Vector<JChatFrame> chatVector = new Vector<>(4, 4);
  private Vector<JPlayerInternalFrame> playerVector = new Vector<>(2, 2);
  private Vector<WM> frameVector = new Vector<>(4, 4); // enthält alle Frames
  private JInfoPane infoPane;
  private MediaFileChooser wallpaperChooser;
  private JPrivateChat personChat;

  private Config root;
  private Configfile configfile;
  private volatile Userdata userdata;

  private ArrayList<UserOnline> userOnlinelist = new ArrayList<>();
  private ActionListener itemOneListener;
  private TextBuffer textbuffer;
  private ActionListener restrictionListener;
  private volatile WebsocketClient wsClient = new WebsocketClient();
  private WebsocketAction websocketAction = new WebsocketAction();
  private ExecutorService executorChat;
  private ExecutorService executorFileTransferServer;
  private ExecutorService executorVideolan;
  private ExecutorService executorJersey;

  private ScheduledExecutorService executorWatcher;
  private WatchService watcher = new WatchService();
  private Future<Void> futureVideolan;
  private ArrayList<ConferenceSpeaker> conferenceSpeakerlist = new ArrayList<>();

  private AtomicReference<String> receiverPseudoid = new AtomicReference<>();

  private JPlayerInternalFrame internalplayer;

  private TeilenInternalFrame teilenframe;

  private EinwilligungInternalFrame einwilligungFrame;
  private JSchwarzeListeDialog schwarzeliste;
  private FTServer ftserver;
  private Channels channels = new Channels(); // istAWT
  private String myIP;
  private List<Path> playlist;
  private int playervolume = JSliderVolumePanel.NORMAL;
  private ActionListener actionBallerina;
  private AdministratorFrame administratorFrame;
  private TransferFiletransferConfig transferConfig;
  private Sprachguete voiceQuality;
  private String telefonLautsprecher;
  private String telefonMikrofon;
  private String onhookConferencekey;
  private boolean onrekorder;
  private Helado helado;

  private StreamFrame iptvframe;
  private JServerKonfigurierenFrame serverKonfigurierenFrame;
  private PropertyChangeListener createRoomListener;
  private List<String> senderliste;
  private ArrayBlockingQueue<byte[]> micQueue;
  private MicProducer micProducer;

  private MicConferenceConsumer micConsumer;

  private boolean outdated = false;
  private int iptvvolume = 68;
  private boolean chattooltip;
  // language bisher ohne Bedeutung, steht nur in der Datenbank,
  // der tatsächliche Eintrag wird aus der config.xml ermittelt
  // dies muss geändert werden,
  // aus der config.xml nur verwenden, wenn es keine Datenbankverbindung gibt
  // Language ist aus Protocol
  private Language language = Language.de;
  private boolean unzipped = false;
  private boolean macheKeinUpdate = true;
  private ArrayList<Babelfish> babelfish = new ArrayList<>();
  private ISO6391 rootLanguage;
  private MultilingualLabel veralteteProgrammversion = new MultilingualLabel(
      KEY.LABEL_VERALTETE_PROGRAMMVERSION
  );
  private MultilingualString checkVersion = new MultilingualString(KEY.STRING_VERSIONSPRUEFUNG);
  private MultilingualString mikrofonFehlt = new MultilingualString(KEY.STRING_EIN_MIKROFON_FEHLT);
  private MultilingualString telefonierenIstNichtMoeglich = new MultilingualString(
      KEY.STRING_TELEFONIEREN_IST_NICHT_MOEGLICH
  );
  private MultilingualString lautsprecher = new MultilingualString(KEY.STRING_EIN_LAUTSPRECHER_IST);
  private MultilingualString mikrofonAuswaehlen = new MultilingualString(KEY.STRING_BISHER_WURDE_KEIN);
  private MultilingualString versioncheck = new MultilingualString(KEY.STRING_VERSIONSPRUEFUNG);
  private MultilingualLabel labelAktualisieren = new MultilingualLabel(
      KEY.LABEL_ICH_NEHME_EINE_AKTUALISIERUNG
  );
  private MultilingualString ja = new MultilingualString(KEY.STRING_JA);
  private MultilingualString nein = new MultilingualString(KEY.STRING_NEIN);
  private MultilingualString programmende = new MultilingualString(KEY.STRING_PROGRAMMENDE);
  private MultilingualString sollDieLaufendeAktualisierung = new MultilingualString(
      KEY.STRING_SOLL_DIE_LAUFENDE
  );
  private MultilingualString dieVersionWurdeErfolgreich = new MultilingualString(
      KEY.STRING_DIE_VERSION_WURDE
  );
  private MultilingualString programmaktualisierung = new MultilingualString(
      KEY.STRING_PROGRAMMAKTUALISIERUNG
  );
  private MultilingualString lautsprecherEinrichten = new MultilingualString(
      KEY.STRING_LAUTSPRECHER_EINRICHTEN
  );
  private MultilingualString keinMixer = new MultilingualString(KEY.STRING_KEIN_MIXER);
  private MultilingualString telefonformatFalsch = new MultilingualString(KEY.STRING_TELFONFORMAT_FALSCH);
  private MultilingualString mikrofonnameIstFalsch = new MultilingualString(
      KEY.STRING_DER_MIKROFONNAME_IST_UNBEKANNT
  );
  private MultilingualString mikrofonEinrichten = new MultilingualString(KEY.STRING_MIKROFON_EINRICHTEN);
  private MultilingualString hatEinMikrofonproblem = new MultilingualString(
      KEY.STRING_HAT_EIN_MIKROFONPROBLEM
  );
  private MultilingualString serverpublic = new MultilingualString(KEY.STRING_DER_SERVER_MUSS);
  private MultilingualString keineAbspielbarenLieder = new MultilingualString(
      KEY.STRING_KEINE_ABSPIELBAREN_LIEDER
  );
  private MultilingualString zufuallsmodus = new MultilingualString(
      KEY.STRING_DER_ZUFALLSMODUS_WIRD_ABGESCHALTET
  );
  private MultilingualString alleMusikstuecke = new MultilingualString(KEY.STRING_ALLE_MUSIKSTUECKE);
  private MultilingualString musikboxLeer = new MultilingualString(KEY.STRING_DIE_MUSIKKISTE_IST_LEER);
  private MultilingualString wiedergabelisten = new MultilingualString(KEY.STRING_ALLE_WIEDERGABELISTEN);
  private MultilingualString profilGespeichert = new MultilingualString(KEY.SERVER_PROFIL_GESPEICHERT);
  private MultilingualString profilNichtGespeichert = new MultilingualString(
      KEY.SERVER_PROFIL_NICHT_GESPEICHERT
  );
  private MultilingualString kontodaten = new MultilingualString(KEY.SERVER_KONTODATEN);
  private MultilingualString invalidEmail = new MultilingualString(KEY.SERVER_EMAIL_INVALID);
  private MultilingualString passwortAnfordern = new MultilingualString(KEY.STRING_PASSWORT_ANFORDERN);
  private MultilingualString gruppenraumEinrichten = new MultilingualString(KEY.GRUPPENRAUM_EINRICHTEN);
  private MultilingualString freundeslisteAngepasst = new MultilingualString(
      KEY.STRING_FREUNDESLISTE_ERWEITERT
  );
  private MultilingualString mitgliederlisteAngepasst = new MultilingualString(
      KEY.STRING_MITGLIEDERLISTE_WURDE_ANGEPASST
  );
  private MultilingualString raumname = new MultilingualString(KEY.STRING_RAUM);
  private MultilingualString serverZuKurzerRaumname = new MultilingualString(KEY.SERVER_ZU_KURZER_RAUMNAME);
  private MultilingualString serverLehntChatanfragenAb = new MultilingualString(
      KEY.SERVER_LEHNT_CHATANFRAGEN_AB
  );
  private MultilingualString serverDerChatraumIstVorhanden = new MultilingualString(
      KEY.SERVER_DER_CHATRAUM_IST_VORHANDEN
  );
  private MultilingualString serverChatnameNichtErlaubt = new MultilingualString(
      KEY.SERVER_DER_CHATNAME_IST_NICHT_ERLAUBT
  );
  private MultilingualString serverChatraumAngelegt = new MultilingualString(
      KEY.SERVER_DER_CHATRAUM_WURDE_ANGELEGT
  );
  private MultilingualString nimmtKeineAnrufeEntgegen = new MultilingualString(
      KEY.STRING_NIMMT_KEINE_ANRUFE_ENTGEGEN
  );
  private MultilingualString istAmTelefonieren = new MultilingualString(KEY.STRING_IST_AM_TELEFONIEREN);
  private MultilingualString istInEinerKonferenz = new MultilingualString(KEY.STRING_IST_IN_EINER_KONFERENZ);
  private MultilingualString forwardingImage = new MultilingualString(KEY.SERVER_FORWARDING_IMAGE);
  private MultilingualString istNichtErreichbar = new MultilingualString(KEY.STRING_IST_NICHT_ERREICHBAR);
  private MultilingualString telefonieren = new MultilingualString(KEY.STRING_TELEFONIEREN);
  private MultilingualString alleFormate = new MultilingualString(KEY.STRING_ALLE_FORMATE);

  private MultilingualString derKanalIstBelegt = new MultilingualString(KEY.STRING_DER_KANAL_IST_BELEGT);
  private MultilingualString alleKanaeleSindBelegt = new MultilingualString(
      KEY.STRING_ALLE_KANAELE_SIND_BELEGT
  );
  private MultilingualString titelMarkieren = new MultilingualString(KEY.STRING_TITEL_MARKIEREN);
  private MultilingualString istNichtMehrOnline = new MultilingualString(KEY.STRING_IST_NICHT_MEHR_ONLINE);
  private MultilingualString hatAufgelegt = new MultilingualString(KEY.STRING_HAT_AUFGELEGT);
  private MultilingualString einsameSeelen = new MultilingualString(KEY.STRING_EINSAME_SEELEN);

  private MultilingualString downloadAttachment = new MultilingualString(KEY.STRING_DOWNLOAD);
  private MultilingualString uploadAttachment = new MultilingualString(KEY.STRING_UPLOAD);
  private MultilingualString downloadAbgeschlossen = new MultilingualString(
      KEY.STRING_DOWNLOAD_ABGESCHLOSSEN
  );
  private MultilingualString medienformatWirdNichtUnterstuetzt = new MultilingualString(
      KEY.STRING_DAS_MEDIENFORMAT_WIRD_NICHT
  );
  private MultilingualString medienwiedergabe = new MultilingualString(KEY.STRING_MEDIENWIEDERGABE);
  private MultilingualString projektorAusschalten = new MultilingualString(
      KEY.SERVER_MUSS_NOCH_SEINEN_PROJEKTOR_AUSSCHALTEN
  );
  private MultilingualString lehntUebertragungenAb = new MultilingualString(
      KEY.SERVER_LEHNT_UEBERTRAGUNGEN_AB
  );
  private MultilingualString empfaengerIstVerschwunden = new MultilingualString(
      KEY.SERVER_DER_EMPFAENGER_IST_VERSCHWUNDEN
  );
  private MultilingualString videoRunning = new MultilingualString(KEY.SERVER_VIDEO_RUNNING);
  private MultilingualString configSaved = new MultilingualString(
      KEY.STRING_KONFIGURATIONSEINSTELLUNGEN_SPEICHERN
  );
  private MultilingualString configNotSaved = new MultilingualString(
      KEY.STRING_KONFIGURATIONSEINSTELLUNGEN_NICHT_SPEICHERN
  );
  private MultilingualString saveFailed = new MultilingualString(KEY.STRING_SPEICHERN_FEHLGESCHALGEN);
  private MultilingualString testmailWurdeGesendet = new MultilingualString(KEY.STRING_EINE_TESTMAIL_WURDE);
  private MultilingualString checkadminMail = new MultilingualString(KEY.STRING_UEBERPRUEFE_DAS);
  private MultilingualString testmailFail = new MultilingualString(KEY.STRING_DIE_TESTMAIL_KONNTE);
  private MultilingualString testmailNichtGesendet = new MultilingualString(
      KEY.STRING_TESTMAIL_NICHT_GESENDET
  );
  private MultilingualString alleVerbotenenSpitznamen = new MultilingualString(
      KEY.STRING_ALLE_VERBOTENEN_SPITZNAMEN
  );
  private MultilingualString dieSpitznamenKonntenNicht = new MultilingualString(
      KEY.STRING_DIE_SPITZNAMEN_KONNTEN_NICHT
  );
  private MultilingualString alleProgrammversionen = new MultilingualString(KEY.STRING_ALLE_VERALTETEN);
  private MultilingualString dieProgrammversionen = new MultilingualString(KEY.STRING_DIE_PROGRAMMVERSIONEN);
  private MultilingualString besprechungsRauemeVerwalten = new MultilingualString(
      KEY.FRAME_BESPRECHUNGSRAEUME
  );
  private MultilingualString chatAbgelehnt = new MultilingualString(KEY.STRING_CHATTEN_ABGELEHNT);
  private MultilingualString confirm = new MultilingualString(KEY.BUTTON_CONFIRM);
  private MultilingualString cancel = new MultilingualString(KEY.BUTTON_ABBRECHEN);
  private MultilingualString beschreibung = new MultilingualString(KEY.STRING_BESCHREIBUNG);
  private MultilingualString leitungGebrochen = new MultilingualString(KEY.SERVER_LEITUNG_UNTERBROCHEN);
  private MultilingualString verbindungsproblem = new MultilingualString(KEY.SERVER_VERBINDUNGSPROBLEM);
  private JTelefonkonferenzOrganisierenFrame telefonkonferenzframe;
  private String chatDownloadDir;
  private String chatUploadDir;
  private long drift;
  private JavacommVirtualThreadFactory networkFactory = new JavacommVirtualThreadFactory("v-network");
  private MultilingualString alleSpeicheroptionen = new MultilingualString(KEY.LABEL_ALLE_SPEICHEROPTIONEN);
  private MultilingualString logischGeloescht = new MultilingualString(KEY.STRING_DAS_BENUTZERKONTO);
  private MultilingualString logischAktiviert = new MultilingualString(KEY.STRING_BENUTZERKONTO_AKTIVIERT);
  private MultilingualString open = new MultilingualString(KEY.STRING_OPEN);
  private MultilingualString multilingualReport = new MultilingualString(KEY.FRAME_REPORT);
  private MultilingualString multilingualSchemapruefung = new MultilingualString(KEY.FRAME_SCHEMAPRUEFUNG);
  private MultilingualString mussGeprueftWerden = new MultilingualString(KEY.STRING_DIE_DATEI);
  private MultilingualString hinweis = new MultilingualString(KEY.STRING_HINWEIS);
  private MultilingualString istNichtVorhanden = new MultilingualString(KEY.STRING_IST_NICHT_VORHANDEN);
  private MultilingualString linebroken = new MultilingualString(KEY.STRING_SERVERVERBINDUNG);
  private MultilingualString chatIsRunning = new MultilingualString(KEY.SERVER_DER_CHAT_LAEUFT_BEREITS);
  private MultilingualString userIstOffline = new MultilingualString(KEY.SERVER_USER_IST_OFFLINE);
  private MultilingualString dieTestmailKonnte = new MultilingualString(KEY.SERVER_DIE_TESTMAIL_KONNTE);
  private MultilingualString hatKeineBerechtigung = new MultilingualString(KEY.STRING_HAT_KEINE_BERECHTIGUNG);
  private MultilingualString drittePerson = new MultilingualString(KEY.SERVER_DRITTE_PERSON);
  private MultilingualString dvdTitel = new MultilingualString(KEY.STRING_DVD_TITEL);
  private MultilingualString raumfilterGespeichert = new MultilingualString(
      KEY.SERVER_RAUMFILTER_GESPEICHERT
  );
  private MultilingualString raumfilterNichtGeloescht = new MultilingualString(
      KEY.SERVER_RAUMFILTER_NOT_DELETED
  );
  private MultilingualString raumfilterGeloescht = new MultilingualString(KEY.SERVER_RAUMFILTER_DELETED);

  private JCheckBoxMenuItem titel[];
  private AtomicReference<String> cancelDownload = new AtomicReference<>("");
  private AtomicReference<String> cancelUpload = new AtomicReference<>("");
  /**
   * dieser AES Schlüssel wird bei jedem USRLOGIN erzeugt
   */
  private SecretKey secretAES;
  private PublicKey rsa;

  // Administrator Menu einblenden
  private boolean firstInvoke = true;
  private volatile MausWrapper mausWrapper;
  private BufferedImage duke;
  private Optional<BufferedImage> pictureWithoutMouse = Optional.empty(); // wird von NEWIMAGE und
                                                                          // NEWMOUSEPOINTER beschrieben
  private AutoupdateWorker autoupdater;

  private final CBlockProvider standardBlock = (pseudoPassword, drift) -> {
    CBlock cb = new CBlock();
    cb.setPseudoPassword(pseudoPassword);
    cb.setTimestamp(drift + System.currentTimeMillis());
    return cb;
  };

  public JChat() throws JAXBException, IOException {
    resource = getClass();

    babelfish.add(menuJavacomm);
    babelfish.add(menuKonto);
    babelfish.add(menuEis);
    babelfish.add(itemMokka);
    babelfish.add(itemVanille);
    babelfish.add(itemJoghurt);
    babelfish.add(itemBlaubeere);
    babelfish.add(itemErdbeere);
    babelfish.add(itemZitrone);
    babelfish.add(menuOnlinetransfer);
    babelfish.add(itemServerKonfigurieren);
    babelfish.add(itemHochladenRunterladen);
    babelfish.add(menuChat);
    babelfish.add(itemSchwarzeListe);
    babelfish.add(itemBearbeiten);
    babelfish.add(itemAufWiedersehen);
    babelfish.add(itemRegistrieren);
    babelfish.add(itemAnmelden);
    babelfish.add(itemNeuesPasswortAnfordern);
    babelfish.add(itemLaunch);
    babelfish.add(itemForum);
    babelfish.add(itemBesprechungsraum);
    babelfish.add(itemGruppenraum);
    babelfish.add(itemPausenraum);
    babelfish.add(itemChatanfragenAblehnen);
    babelfish.add(itemPrivateTalk);
    babelfish.add(menuAdministrator);
    babelfish.add(menuMediaPlayer);
    babelfish.add(menuPhone);
    babelfish.add(menuInfo);
    babelfish.add(menuBildschirm);
    babelfish.add(menuStreams);
    babelfish.add(itemBildschirmEmpfangen);
    babelfish.add(itemTeilenframe);
    babelfish.add(telefonateAblehnen);
    babelfish.add(itemTelefonEinrichten);
    babelfish.add(itemAnrufen);
    babelfish.add(itemLizenz);
    babelfish.add(itemVerwalten);
    babelfish.add(itemTitel);
    babelfish.add(itemSpielliste);
    babelfish.add(menuMusikkiste);
    babelfish.add(itemKisteLeeren);
    babelfish.add(itemDVD);
    babelfish.add(menuInhaltsverzeichnis);
    babelfish.add(menuFilmsprache);
    babelfish.add(itemVollbildmodus);
    babelfish.add(itemFensterImHintergrund);
    babelfish.add(menuFlag);
    babelfish.add(menuHintergrundbild);
    babelfish.add(itemTapeteAnkleben);
    babelfish.add(itemTapeteAbreissen);
    babelfish.add(veralteteProgrammversion);
    babelfish.add(checkVersion);
    babelfish.add(mikrofonFehlt);
    babelfish.add(telefonierenIstNichtMoeglich);
    babelfish.add(lautsprecher);
    babelfish.add(mikrofonAuswaehlen);
    babelfish.add(versioncheck);
    babelfish.add(labelAktualisieren);
    babelfish.add(ja);
    babelfish.add(nein);
    babelfish.add(programmende);
    babelfish.add(sollDieLaufendeAktualisierung);
    babelfish.add(dieVersionWurdeErfolgreich);
    babelfish.add(programmaktualisierung);
    babelfish.add(lautsprecherEinrichten);
    babelfish.add(keinMixer);
    babelfish.add(telefonformatFalsch);
    babelfish.add(mikrofonnameIstFalsch);
    babelfish.add(mikrofonEinrichten);
    babelfish.add(hatEinMikrofonproblem);
    babelfish.add(serverpublic);
    babelfish.add(keineAbspielbarenLieder);
    babelfish.add(zufuallsmodus);
    babelfish.add(alleMusikstuecke);
    babelfish.add(musikboxLeer);
    babelfish.add(wiedergabelisten);
    babelfish.add(profilGespeichert);
    babelfish.add(profilNichtGespeichert);
    babelfish.add(kontodaten);
    babelfish.add(invalidEmail);
    babelfish.add(passwortAnfordern);
    babelfish.add(mitgliederlisteAngepasst);
    babelfish.add(gruppenraumEinrichten);
    babelfish.add(freundeslisteAngepasst);
    babelfish.add(serverDerChatraumIstVorhanden);
    babelfish.add(raumname);
    babelfish.add(serverZuKurzerRaumname);
    babelfish.add(serverChatnameNichtErlaubt);
    babelfish.add(serverChatraumAngelegt);
    babelfish.add(nimmtKeineAnrufeEntgegen);
    babelfish.add(istAmTelefonieren);
    babelfish.add(istNichtErreichbar);
    babelfish.add(istNichtMehrOnline);
    babelfish.add(hatAufgelegt);
    babelfish.add(telefonieren);
    babelfish.add(alleFormate);
    babelfish.add(derKanalIstBelegt);
    babelfish.add(alleKanaeleSindBelegt);
    babelfish.add(titelMarkieren);
    babelfish.add(GUI.blaubeere);
    babelfish.add(GUI.mokka);
    babelfish.add(GUI.joghurt);
    babelfish.add(GUI.erdbeere);
    babelfish.add(GUI.vanille);
    babelfish.add(downloadAttachment);
    babelfish.add(medienformatWirdNichtUnterstuetzt);
    babelfish.add(medienwiedergabe);
    babelfish.add(empfaengerIstVerschwunden);
    babelfish.add(lehntUebertragungenAb);
    babelfish.add(configSaved);
    babelfish.add(configNotSaved);
    babelfish.add(saveFailed);
    babelfish.add(testmailWurdeGesendet);
    babelfish.add(checkadminMail);
    babelfish.add(testmailFail);
    babelfish.add(alleVerbotenenSpitznamen);
    babelfish.add(dieSpitznamenKonntenNicht);
    babelfish.add(alleProgrammversionen);
    babelfish.add(dieProgrammversionen);
    babelfish.add(besprechungsRauemeVerwalten);
    babelfish.add(itemTelkoOrganisieren);
    babelfish.add(itemTelkoErstellen);
    babelfish.add(itemTelkoTeilnehmen);
    babelfish.add(serverLehntChatanfragenAb);
    babelfish.add(chatAbgelehnt);
    babelfish.add(confirm);
    babelfish.add(cancel);
    babelfish.add(beschreibung);
    babelfish.add(leitungGebrochen);
    babelfish.add(verbindungsproblem);
    babelfish.add(alleSpeicheroptionen);
    babelfish.add(multilingualReport);
    babelfish.add(multilingualSchemapruefung);
    babelfish.add(mussGeprueftWerden);
    babelfish.add(hinweis);
    babelfish.add(logischGeloescht);
    babelfish.add(logischAktiviert);
    babelfish.add(uploadAttachment);
    babelfish.add(downloadAbgeschlossen);
    babelfish.add(open);
    babelfish.add(menuGaming);
    babelfish.add(istInEinerKonferenz);
    babelfish.add(istNichtVorhanden);
    babelfish.add(linebroken);
    babelfish.add(einsameSeelen);
    babelfish.add(chatIsRunning);
    babelfish.add(dieTestmailKonnte);
    babelfish.add(dieTestmailKonnte);
    babelfish.add(videoRunning);
    babelfish.add(drittePerson);
    babelfish.add(forwardingImage);
    babelfish.add(userIstOffline);
    babelfish.add(dvdTitel);
    babelfish.add(raumfilterGespeichert);
    babelfish.add(raumfilterNichtGeloescht);
    babelfish.add(raumfilterGeloescht);

    secretAES = Crypto.createAES(AES_Bitlength.AES_128);

    executorJersey = Executors.newCachedThreadPool(
        runnable -> new Thread(Thread.currentThread().getThreadGroup(), runnable, "jersey")
    );

    executorWatcher = Executors.newSingleThreadScheduledExecutor(
        runnable -> new Thread(Thread.currentThread().getThreadGroup(), runnable, "watcher")
    );
    // 15, weil die Task erst nach dem Login gestartet werden soll
//    futureWatcher = executorWatcher.scheduleAtFixedRate(watcher.task(), 3, 12, TimeUnit.SECONDS);
    watcher.addWatchListener(event -> {
      int count = event.getList().size();
      String[] pathname = new String[count];
      List<Path> path = event.getList();
      for (int index = 0; index < count; index++) {
        pathname[index] = path.get(index).toAbsolutePath().toString();
      }
      transmit(pathname);
    });

    executorVideolan = Executors.newSingleThreadExecutor(
        callable -> new Thread(Thread.currentThread().getThreadGroup(), callable, "loader")
    );
    executorFileTransferServer = Executors.newSingleThreadExecutor();
    executorChat = Executors.newThreadPerTaskExecutor(networkFactory);

    wsClient.addWebsocketListener(websocketAction);

    Environment env = Environment.getInstance();
    senderliste = env.createIptvlist(Constants.IPTV);
    String etcdir = env.getEtcdir();
    Path dest = Paths.get(etcdir, Environment.CONFIGFILE);
    try {
      configfile = new Configfile(dest);
      root = configfile.read();
    }
    catch (JAXBException | IOException | SAXException e) {
      log.error(e.getMessage(), e.getCause());
      log.info("Der Fehler wird behoben.");
      log.info("Ich versuche die " + Environment.CONFIGFILE + " neu anzulegen.");
      boolean created;
      try {
        Files.deleteIfExists(env.getConfigfile());
        created = env.createDefaultConfigfile();
        if (created) {
          root = configfile.read();
          log.info("Der Programmstart wird fortgesetzt.");
        }
      }
      catch (SAXException e1) {
        log.error(e1.getMessage(), e1.getCause());
      }
    }
    if (root.getWallpaper() == null) {
      Path file = Paths.get(env.getWallpaperdir().toAbsolutePath().toString(), Environment.DEFAULT_WALLPAPER);

      if (Files.isRegularFile(file)) {
        net.javacomm.client.config.schema.Wallpaper paper = new net.javacomm.client.config.schema.Wallpaper();
        try {
          url = file.toUri().toURL();
          log.info("wallpaper -----> " + url);
          paper.setPathToFile(url.getFile());
          root.setWallpaper(paper);
          desktop.setPicture(url);
        }
        catch (MalformedURLException e) {
          log.error(e.getMessage(), e.getCause());
        }
      }
    }
    else {
      log.info(root.getWallpaper().getPathToFile());
    }

    if (root.getEis() == null) root.setEis(Sorte.MOKKA);

    initMediaplayer(root);
    initDvd(root);
    initStreams(root);

    if (GUI.properties != null) {
      Login login = new Login();
      login.setEmail(GUI.properties.getProperty(JChat.USER));
      login.setPassword(GUI.properties.getProperty(JChat.PASSWORD));
      root.setDomain(GUI.properties.getProperty(JChat.DOMAIN));
      String languageShort = GUI.properties.getProperty(JChat.LANGUAGE);
      root.setLanguage(ISO6391.fromValue(languageShort));
      root.setLogin(login);
    }
    ImageIO.setUseCache(false);
    try {
      duke = ImageIO.read(getClass().getResource(Resource.DUKEMAUS));
    }
    catch (IOException e) {
      log.warn("Mauscursor konnte nicht gelesen werden");
    }

  }



  /**
   * Die Administrator GUI wird aufgebaut.
   */
  void admin() {
    // con.gridx = 0;
    // con.gridy = 1;
    con.weightx = 0.0;
    con.anchor = GridBagConstraints.EAST;
    gridbag.setConstraints(menuAdministrator, con);

    menubar.add(menuAdministrator);
    menuAdministrator.add(itemVerwalten);
    itemVerwalten.setIcon(new ImageIcon(resource.getResource(Resource.ADMINISTRATOR)));
    itemVerwalten.addActionListener(event -> {

      itemVerwalten.setEnabled(false);
      administratorFrame = new AdministratorFrame();
      administratorFrame.setLanguage(ISO639.fromValue(rootLanguage.value()));
      administratorFrame.setFrameTitleId(menuAdministrator.getText());

      administratorFrame.addAdministratorFrameListener(propertyEvent -> {
        SaveKontoaktivitaetenWorker saveKontoaktivitaetenWorker;

        Control control = (Control) propertyEvent.getNewValue();

        final CBlock cblock = standardBlock.create(userdata.getPPassword(), drift);

        switch(control) {
          case UPDATE:
            Thread.startVirtualThread(
                () -> saveLogicallyDelete(
                    userdata.getPseudoid(), cblock, administratorFrame.getBenutzerkonto()
                )
            );
            break;
          case UPDATE_ODX:
            break;
          case CLOSE:
            babelfish.remove(administratorFrame);
            itemVerwalten.setEnabled(true);
            removeWM(administratorFrame);
            frameVector.remove(administratorFrame);
            administratorFrame.removeAllListener();
            break;
          case TESTMAIL:

            try {
              administratorFrame.getAdminPostfach();
              administratorFrame.getSmtpHost();
              administratorFrame.getSmptUser();
              administratorFrame.getSmtpPort();
              administratorFrame.getSmtpPassword();

              testmail(
                  userdata.getPseudoid(), cblock, root.getDomain(), administratorFrame.getSmtpHost(),
                  administratorFrame.getSmptUser(), administratorFrame.getSmtpPort(),
                  new String(administratorFrame.getSmtpPassword()), administratorFrame.getAdminPostfach()
              );
            }
            catch (StringException e) {

            }

            break;
          case SAVE:
            Thread.startVirtualThread(
                () -> saveKonfigurationsdaten(
                    userdata.getPseudoid(), cblock, administratorFrame.getKonfigurationsdaten()
                )
            );

          case SAVE_BENUTZERANTRAEGE:
            if (administratorFrame.getBenutzerantraege().size() >= 1) {
              saveKontoaktivitaetenWorker = new SaveKontoaktivitaetenWorker(
                  userdata.getPseudoid(), cblock, administratorFrame.getBenutzerantraege()
              );
              saveKontoaktivitaetenWorker.addPropertyChangeListener(activity -> {
                if (activity.getNewValue() == SwingWorker.StateValue.DONE) {
                  try {
                    Boolean success = saveKontoaktivitaetenWorker.get(); // blockiert nicht, da DONE
                    if (success != null && success) {
                      // saveKontoaktivitaetenWorker erfolgreich → benutzerkontenWorker starten
                      JOptionPane.showMessageDialog(
                          JChat.this, "Die neuen Benutzeranträge wurden bearbeitet und gespeichert.",
                          menuAdministrator.getText(), JOptionPane.INFORMATION_MESSAGE
                      );

                      // cblock darf nicht ein zweites Mal verwendet werden, wegen 404
                      // Replay-Angriff
                      final CBlock cblock1 = standardBlock.create(userdata.getPPassword(), drift);
                      Thread.startVirtualThread(() -> readUserRegistration(userdata.getPseudoid(), cblock1));
                    }
                    else {
                      log.warn("Speichern der Benutzeranträge fehlgeschlagen, kein erneutes Einlesen.");
                      JOptionPane.showMessageDialog(
                          JChat.this, "Die Benutzeranträge wurden nicht gespeichert.",
                          menuAdministrator.getText() + " - " + saveFailed.toString(),
                          JOptionPane.ERROR_MESSAGE
                      );
                    }
                  }
                  catch (InterruptedException | ExecutionException e) {
                    log.warn(e.getMessage(), e);
                  }
                }
              });
              saveKontoaktivitaetenWorker.execute();
//              log.info("wurde der Kontoaktivitätenworker gestartet?");
            }
            break;
          case DEREGISTER_RAUMFILTER:
            RemoveRoomfilterWorker removeRoomfilterWorker = new RemoveRoomfilterWorker(
                userdata.getPseudoid(), cblock, administratorFrame.deregisterFilter()
            );
            removeRoomfilterWorker.addPropertyChangeListener(removeRoomfilterEvent -> {
              if (!removeRoomfilterEvent.getPropertyName().equals("state")) return;
              if (removeRoomfilterEvent.getNewValue() != StateValue.DONE) return;

              boolean result;
              try {
                result = removeRoomfilterWorker.get();
                StringBuilder YYY = new StringBuilder();
                administratorFrame.deregisterFilter().forEach(roomfilter -> {
                  YYY.append("<b>");
                  YYY.append(roomfilter);
                  YYY.append("</b>");
                  YYY.append("<br>");
                });
                YYY.delete(YYY.length() - "<br>".length(), YYY.length());
                String anzeigeNichtGeloescht = raumfilterNichtGeloescht.toString().replace("XXX", YYY);
                if (!result) {
                  JOptionPane.showMessageDialog(
                      JChat.this, anzeigeNichtGeloescht, menuAdministrator.getText(),
                      JOptionPane.ERROR_MESSAGE
                  );
                  return;
                }
                else {
                  String anzeige = raumfilterGeloescht.toString().replace("XXX", YYY);
                  JOptionPane.showMessageDialog(
                      JChat.this, anzeige, menuAdministrator.getText(), JOptionPane.INFORMATION_MESSAGE
                  );
                }
              }
              catch (InterruptedException | ExecutionException e) {
                log.info(e.getMessage(), e);
              }
              // aktuelle Liste laden
              // cblock3 verhindert einen ungewollten Replay-Angriff
              final CBlock cblock3 = standardBlock.create(userdata.getPPassword(), drift);
              ReadRoomfilterWorker readRoomfilter = new ReadRoomfilterWorker(userdata.getPseudoid(), cblock3);
              readRoomfilter.execute();
              try {
                administratorFrame.setRaumfilterliste(readRoomfilter.get());
              }
              catch (InterruptedException | ExecutionException e) {
                log.error(e.getMessage(), e);
                JOptionPane.showMessageDialog(
                    JChat.this, "Die Raumfilter konnten nicht gelesen werden.", menuAdministrator.getText(),
                    JOptionPane.ERROR_MESSAGE
                );
              }

            });
            removeRoomfilterWorker.execute();

            break;
          case REGISTER_RAUMFILTER:
            String ifSucces = raumfilterGespeichert.toString()
                .replace("XXX", administratorFrame.registerFilter());

            WriteRoomfilterWorker writeroomWorker = new WriteRoomfilterWorker(
                userdata.getPseudoid(), cblock, administratorFrame.registerFilter()
            );
            writeroomWorker.addPropertyChangeListener(writeRoomEvent -> {
              if (!writeRoomEvent.getPropertyName().equals(WriteRoomfilterWorker.HTTP)) return;
              StatusType statustype = (StatusType) writeRoomEvent.getOldValue();
              switch(statustype) {
                case Status.OK:

                  JOptionPane.showMessageDialog(
                      JChat.this, ifSucces, menuAdministrator.getText(), JOptionPane.INFORMATION_MESSAGE
                  );
                  break;
                case Status.BAD_REQUEST:
                  JOptionPane.showMessageDialog(
                      JChat.this, writeRoomEvent.getNewValue(), menuAdministrator.getText(),
                      JOptionPane.ERROR_MESSAGE
                  );
                  return;
                case Status.FORBIDDEN:
                  return;
                case Status.INTERNAL_SERVER_ERROR:
                  return;
                default:
                  throw new UnsupportedOperationException(statustype.toString());
              }

              // aktuelle Liste laden
              // cblock2 verhindert einen ungewollten Replay-Angriff
              final CBlock cblock2 = standardBlock.create(userdata.getPPassword(), drift);

//              log.info("pseudoid {}, cblock {}", userdata.getPseudoid(), cblock2.toString());
              ReadRoomfilterWorker readRoomfilter = new ReadRoomfilterWorker(userdata.getPseudoid(), cblock2);
              readRoomfilter.addPropertyChangeListener(roomfilterEvent -> {
                if (!roomfilterEvent.getPropertyName().equals("state")) return;
                if (roomfilterEvent.getNewValue() != SwingWorker.StateValue.DONE) return;
                try {
                  administratorFrame.setRaumfilterliste(readRoomfilter.get());
                }
                catch (InterruptedException | ExecutionException e) {
                  log.error(e.getMessage(), e);
                  JOptionPane.showMessageDialog(
                      JChat.this, "Die Raumfilter konnten nicht gelesen werden.", menuAdministrator.getText(),
                      JOptionPane.ERROR_MESSAGE
                  );
                }
              });
              readRoomfilter.execute();

            });
            writeroomWorker.execute();

            break;
          case SAVE_PROGRAMMVERSIONEN:
            Thread.startVirtualThread(
                () -> deleteProgrammversionen(
                    userdata.getPseudoid(), cblock, administratorFrame.getProgrammversionen()
                )
            );

            break;
          case SAVE_NICKNAMES:
            Thread.startVirtualThread(
                () -> deleteNicknames(userdata.getPseudoid(), cblock, administratorFrame.getNicknames())
            );
            break;
          default:
            break;
        }

      });
      Rectangle localRectangle = getBounds();
      administratorFrame.setLocation(
          (localRectangle.width - AdministratorFrame.WIDTH) / 2,
          (localRectangle.height - AdministratorFrame.HEIGHT) / 4
      );

      desktop.add(administratorFrame, JLayeredPane.DEFAULT_LAYER, -1);
      administratorFrame.show();
      babelfish.add(administratorFrame);
      frameVector.add(administratorFrame);
      insertWM(administratorFrame, administratorFrame.getFrameTitleId());

      CBlock cblock = standardBlock.create(userdata.getPPassword(), drift);

      AdministratorWorker adminWorker = new AdministratorWorker(userdata.getPseudoid(), cblock);
      adminWorker.addPropertyChangeListener(adminEvent -> {
        if (!adminEvent.getPropertyName().equals(AdministratorWorker.SUCCESS)) return;

        switch(adminEvent.getNewValue()) {
          case Control.ACCOUNTS:
            try {
              administratorFrame.setBenutzerkonten(adminWorker.getAccounts());
            }
            catch (Exception e) {
              log.warn("Die Benutzerkonten konnten nicht gelesen werden.");
              JOptionPane.showMessageDialog(
                  JChat.this, "Die Benutzerkonten konnten nicht gelesen werden.", menuAdministrator.getText(),
                  JOptionPane.ERROR_MESSAGE
              );
            }
            break;
          case Control.DOMAIN:
            try {
              administratorFrame.setKonfigurationsdaten(adminWorker.getDomain());
            }
            catch (Exception e) {
              log.warn("Die Konfigurationsdaten für die Domäne konnten nicht gelesen werden.");
              JOptionPane.showMessageDialog(
                  JChat.this, "Die Konfigurationsdaten für die Domäne konnten nicht gelesen werden.",
                  menuAdministrator.getText(), JOptionPane.ERROR_MESSAGE
              );
            }
            break;
          case Control.NICKNAMES:
            try {
              administratorFrame.setNicknames(adminWorker.getNicknames());
            }
            catch (Exception e) {
              log.warn("Die Nicknames konnten nicht gelesen werden.");
              JOptionPane.showMessageDialog(
                  JChat.this, "Die Nicknames konnten nicht gelesen werden.", menuAdministrator.getText(),
                  JOptionPane.ERROR_MESSAGE
              );
            }
            break;
          case Control.PROGRAMMVERSIONEN:
            try {
              administratorFrame.setVeralteteProgrammversionen(adminWorker.getProgrammversionen());
            }
            catch (Exception e) {
              log.warn("Die Programmversionen konnten nicht gelesen werden.");
              JOptionPane.showMessageDialog(
                  JChat.this, "Die Programmversionen konnten nicht gelesen werden.",
                  menuAdministrator.getText(), JOptionPane.ERROR_MESSAGE
              );
            }
            break;
          case Control.ROOMFILTER:
            try {
              administratorFrame.setRaumfilterliste(adminWorker.getRoomfilter());
            }
            catch (Exception e) {
              log.warn(e.getMessage(), e);
              JOptionPane.showMessageDialog(
                  JChat.this, "Die Raumfilter konnten nicht gelesen werden.", menuAdministrator.getText(),
                  JOptionPane.ERROR_MESSAGE
              );
            }
            break;
          case Control.USER_REGISTRATION:
            try {
              administratorFrame.setBenutzerantraege(adminWorker.getUserRegistration());
            }
            catch (Exception e) {
              log.warn("Die Benutzeranträge konnten nicht gelesen werden.");
              JOptionPane.showMessageDialog(
                  JChat.this, "Die Benutzeranträge konnten nicht gelesen werden.",
                  menuAdministrator.getText(), JOptionPane.ERROR_MESSAGE
              );
            }
            break;
          default:
            break;
        }

      });
      adminWorker.execute();
    });

  }



  /**
   * Das Login Fenster hat sich geschlossen. Der Anwender hat auf Anmelden oder
   * Abbrechen geklickt.
   * 
   * @param logon
   *              diese GUI Komponente
   * 
   * @return der Zustand nach der Anmeldung
   */
  STATUS afterJLogon(STATUS state) {
    STATUS status = state;
    switch(status) {
      case PROVISIONAL:
        connect(jLogon.getUrl());
        break;
      case DISMISSED:
        itemAnmelden.setEnabled(true);
        itemRegistrieren.setEnabled(true);
        break;
      case ELAPSED:
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        processWindowEvent(new WindowEvent(JChat.this, WindowEvent.WINDOW_CLOSING));
        break;
      case GUEST:
        // nur bestimmte Knöpfe aktivieren
        // zur Registruerung
        itemAnmelden.setEnabled(true);
        itemRegistrieren.setEnabled(true);
        status = registryFrame(KEY.BUTTON_ABBRECHEN);
        if (status == STATUS.PROVISIONAL) {
          connect(jLogon.getUrl());
        }
        break;
      default:
        throw new RuntimeException("status is null");
    }
    return status;
  }



  /**
   * Alle Haken werden aus den Spiellisten entfernt.
   *
   */
  public void alleHakenAusSpiellisteEntfernen() {
    Component[] menuComponents = menuMusikkiste.getMenuComponents();
    for (Component tmp : menuComponents) {
      if (tmp instanceof MultilingualCheckBoxMenuItem item) {
        item.setSelected(false);
      }
    }
  }



  /**
   * Ein Frame öffnet sich, nachdem ein Telefonster bereits eingeblendet ist.
   * Telefonfenster teilt allen seine Baumansicht und Fenstertitel mit.
   *
   * @param source
   *               dieser Frame/Fenster hat sich gerade geöfnet
   */
  private void anrufenEvent(WindowAnrufenListener source) {
    WindowAnrufenEvent windowAnrufenEvent = new WindowAnrufenEvent(source);
    windowAnrufenEvent.setNickname(windowAnrufen.getTriggerNickname());
    windowAnrufenEvent.setCode(ISO639.fromValue(rootLanguage.value()));
    windowAnrufen.trigger(windowAnrufenEvent);
  }



  /**
   * Besprechungsräume können über das Hauptmenu oder die Chatliste angelegt
   * werden.
   * 
   * @param pseudoid
   *                 dieser Anwender
   * @param nickname
   *                 dieser Nickname
   */
  void besprechungsraumEinrichten(String pseudoid, String nickname) {
    besprechungsraum = new JBesprechungsraum();
    besprechungsraum.setFrameIcon(new ImageIcon(resource.getResource(Resource.BESPRECUNGSRAUM_35x26)));
    besprechungsraum.setClosable(true);
    besprechungsraum.setSize(new Dimension(JBesprechungsraum.WIDTH, JBesprechungsraum.HEIGHT));
    besprechungsraum.setResizable(true);
    besprechungsraum.setIconifiable(false);
    Rectangle topicRectangle = getBounds();
    besprechungsraum.setLocation(
        (topicRectangle.width - JBesprechungsraum.WIDTH) / 2,
        (topicRectangle.height - JBesprechungsraum.HEIGHT) / 4
    );
    desktop.add(besprechungsraum, JLayeredPane.MODAL_LAYER, 4);
    babelfish.add(besprechungsraum);
    besprechungsraum.setLanguage(ISO639.fromValue(rootLanguage.value()));
    besprechungsraum.show();
    besprechungsraum.addCreateroomListener(createRoomListener);
    // Anfrgae
    READTOPICROOMOWNER topicroom = new READTOPICROOMOWNER();
    topicroom.setCommand(Command.READTOPICROOMOWNER);
    topicroom.setHeader(REQUEST);
    topicroom.setDataset(Protocol.DATASET);
    topicroom.setOwner(nickname);
    topicroom.setPseudoid(pseudoid);
    executorChat.execute(() -> wsClient.sendMessage(topicroom));
  }



  /**
   * Der Caller oder Anrufer reagiert auf Benutzereingaben.
   *
   * @param receiverNickname
   *                         diese Person soll angerufen werden
   * 
   * @return callback auf Benutzereingaben vom Anrufer
   */
  public PropertyChangeListener callerListener() {
    return event -> {
      Control value = (Control) event.getNewValue();
      switch(value) {
        case TELEFON_WAEHLEN:
          SoundClip.stopOffhooktone();
          try {
            SoundClip.playDialtone();
          }
          catch (IOException | UnsupportedAudioFileException | LineUnavailableException e1) {
            log.error(e1.getMessage(), e1.getCause());
          }
          DIAL dial = new DIAL();
          dial.setCommand(Command.DIAL);
          dial.setHeader(REQUEST);
          dial.setDataset(Protocol.DATASET);
          dial.setCallerNickname(userdata.getNickname());
          dial.setCallerPseudoid(userdata.getPseudoid());
          dial.setCallerVoice(voiceQuality.toString());
          dial.setReceiverNickname(callerframe.getNickname());
          executorChat.execute(() -> wsClient.sendMessage(dial));
          break;
        case CLOSE:
          closeCallerWindowPhone();
          callerframe.removeAllListener(); // Internetverbindung verloren
          removeWM(callerframe);
          frameVector.remove(callerframe);
          babelfish.remove(callerframe);
          itemAnrufen.setEnabled(true);
          itemTelkoTeilnehmen.setEnabled(true);
          windowAnrufen.removeWindowAnrufenListener(callerframe);

          break;
        case TELEFON_AUFLEGEN:
          closeCallerWindowPhone(); // hier passiert der Fehler
          ONHOOK onhook = new ONHOOK();
          onhook.setCommand(Command.ONHOOK);
          onhook.setHeader(REQUEST);
          onhook.setDataset(Protocol.DATASET);
          // Caller und Receiver vertauschen, weil die Anfrage vom Caller kommt
          onhook.setCallerPseudoid(userdata.getPseudoid());
          onhook.setReceiverPseudoid(receiverPseudoid.get());
          onhook.setTo(true);
          onhook.setSignal(true);

          onhook.setConferencekey(onhookConferencekey);

          // Auf jeden Fall ONHOOK
          executorChat.execute(() -> wsClient.sendMessage(onhook));

          break;
        case TELEFON_ABNEHMEN:
          SoundClip.stopBusytone();
          callerframe.hoererAbnehmen();
          try {
            SoundClip.playOffhooktone();
          }
          catch (IOException | UnsupportedAudioFileException | LineUnavailableException e) {
            log.error(e.getMessage(), e.getCause());
          }
          // Caller
          UPDATEPHONE phone = new UPDATEPHONE();
          phone.setCommand(Command.UPDATEPHONE);
          phone.setHeader(REQUEST);
          phone.setDataset(Protocol.DATASET);
          phone.setPseudoid(userdata.getPseudoid());
          phone.setOnBusy(true);
          executorChat.execute(() -> wsClient.sendMessage(phone));
          break;
        case SELECTED:
          selectFrame(event.getPropertyName());
          break;
        case TELEFON_VOLUME:
          conferenceSpeakerlist.forEach(speaker -> speaker.setVolume(callerframe.getVolume()));
          break;
        default:
          log.info(value.name() + " wird nicht benötigt");
          break;
      }
    };
  }



  /**
   * Der Anwender initiiert einen Private Chat.
   * 
   * @param event
   *              dieses Event löste den Private Chat aus
   */
  private void callPrivatechat(PropertyChangeEvent event) {
    if (event.getPropertyName().equals(userdata.getNickname())) {
      JOptionPane.showMessageDialog(
          JChat.this, "<html>" + "<head/>" + "<body >" + einsameSeelen.toString() + "</body>" + "</html>",
          "1:1 Chat", JOptionPane.INFORMATION_MESSAGE
      );
      return;
    }
    callPrivateChat(userdata.getPseudoid(), userdata.getNickname(), event.getPropertyName());
  }



  /**
   * Ein Private Chat wird eingeleitet.
   * 
   * @param userid
   *                       dieser Initiator
   * @param localNickname
   *                       der Nickname vom Initiator
   * @param remoteNickname
   *                       der Empfänger
   */
  private void callPrivateChat(String pseudoid, String localNickname, String remoteNickname) {
    // Connection to myself is not allowed!"
    // Call to myself is not allowed
    CALLPRIVATECHAT call = new CALLPRIVATECHAT();
    call.setCommand(Command.CALLPRIVATECHAT);
    call.setHeader(REQUEST);
    call.setDataset(Protocol.DATASET);
    call.setPseudoid(pseudoid);
    call.setLocalNickname(localNickname);
    call.setRemoteNickname(remoteNickname);
    log.info("-----> " + call.toString());
    executorChat.execute(() -> wsClient.sendMessage(call));
  }



  /**
   * Einer Nachricht wird eine Anlage angehängt.
   *
   * @param frame
   *              aus diesem Chatraum wird gesendet
   */
  private void chooseAttachment(JInternalFrame frame) {
    if (!(frame instanceof JInternalFrame)) return;
    JChatFrame chatframe = (JChatFrame) frame;
    anlageChooser = new MediaFileChooser();
    anlageChooser.setPreferredSize(GUI.FILE_CHOOSER_SIZE);
    anlageChooser.setCurrentDirectory(chatUploadDir == null ? null : new File(chatUploadDir));
    anlageChooser.setDialogTitle(open.toString());

    FileNameExtensionFilter chessbaseFilter = new FileNameExtensionFilter("Chessbase (*.pgn)", "pgn");
    FileNameExtensionFilter sevenZip = new FileNameExtensionFilter("7Zip (*.7z)", "7z");
    FileNameExtensionFilter adobe = new FileNameExtensionFilter("Adobe PDF (*.pdf)", "pdf");

    FileNameExtensionFilter quicktime = new FileNameExtensionFilter(
        "QuickTime (*.aac), (*.mov), (*.mp3), (*.mp4)", "aac", "mov", "mp3", "mp4"
    );
    FileNameExtensionFilter pdx = new FileNameExtensionFilter("Packaged ODX (*.pdx)", "pdx");
    FileNameExtensionFilter jar = new FileNameExtensionFilter("Java Binary (*.jar)", "jar");
    FileNameExtensionFilter java = new FileNameExtensionFilter("Java Source (*.java)", "java");
    FileNameExtensionFilter jpeg = new FileNameExtensionFilter(
        "Joint Photographic (*.jpg), (*.jpeg)", "jpg", "jpeg", "JPG", "JPEG"
    );
    FileNameExtensionFilter matroska = new FileNameExtensionFilter("Matroska (*.mkv)", "mkv");
    FileNameExtensionFilter office = new FileNameExtensionFilter(
        "Office (*.doc), (*.docx), (*.xlsx), (*.odt), (*.ods)", "doc", "docx", "xlsx", "odt", "ods"
    );

    FileNameExtensionFilter png = new FileNameExtensionFilter("Portable Network Graphics (*.png)", "png");
    FileNameExtensionFilter webm = new FileNameExtensionFilter("WebM (*.webm)", "webm");
    FileNameExtensionFilter xml = new FileNameExtensionFilter("eXtensible Markup Language (*.xml)", "xml");
    FileNameExtensionFilter zip = new FileNameExtensionFilter("WinZip (*.zip)", "zip");

    anlageChooser.addChoosableFileFilter(sevenZip);
    anlageChooser.addChoosableFileFilter(adobe);
    anlageChooser.addChoosableFileFilter(chessbaseFilter);
    anlageChooser.addChoosableFileFilter(jar);
    anlageChooser.addChoosableFileFilter(java);
    anlageChooser.addChoosableFileFilter(jpeg);
    anlageChooser.addChoosableFileFilter(matroska);
    anlageChooser.addChoosableFileFilter(office);
    anlageChooser.addChoosableFileFilter(pdx);
    anlageChooser.addChoosableFileFilter(png);
    anlageChooser.addChoosableFileFilter(quicktime);
    anlageChooser.addChoosableFileFilter(webm);
    anlageChooser.addChoosableFileFilter(xml);
    anlageChooser.addChoosableFileFilter(zip);

    if (chatUploadDir != null) {
      if (chatUploadDir.endsWith("pgn")) anlageChooser.setFileFilter(chessbaseFilter);
      else if (chatUploadDir.endsWith("7z")) anlageChooser.setFileFilter(sevenZip);
      else if (chatUploadDir.endsWith("aac")) anlageChooser.setFileFilter(quicktime);
      else if (chatUploadDir.endsWith("doc")) anlageChooser.setFileFilter(office);
      else if (chatUploadDir.endsWith("docx")) anlageChooser.setFileFilter(office);
      else if (chatUploadDir.endsWith("jar")) anlageChooser.setFileFilter(jar);
      else if (chatUploadDir.endsWith("jpg")) anlageChooser.setFileFilter(jpeg);
      else if (chatUploadDir.endsWith("jpeg")) anlageChooser.setFileFilter(jpeg);
      else if (chatUploadDir.endsWith("mkv")) anlageChooser.setFileFilter(matroska);
      else if (chatUploadDir.endsWith("mov")) anlageChooser.setFileFilter(quicktime);
      else if (chatUploadDir.endsWith("mp4")) anlageChooser.setFileFilter(quicktime);
      else if (chatUploadDir.endsWith("mp3")) anlageChooser.setFileFilter(quicktime);
      else if (chatUploadDir.endsWith("odt")) anlageChooser.setFileFilter(office);
      else if (chatUploadDir.endsWith("ods")) anlageChooser.setFileFilter(office);
      else if (chatUploadDir.endsWith("pdf")) anlageChooser.setFileFilter(adobe);
      else if (chatUploadDir.endsWith("pdx")) anlageChooser.setFileFilter(pdx);
      else if (chatUploadDir.endsWith("png")) anlageChooser.setFileFilter(png);
      else if (chatUploadDir.endsWith("webm")) anlageChooser.setFileFilter(webm);
      else if (chatUploadDir.endsWith("xlsx")) anlageChooser.setFileFilter(office);
      else if (chatUploadDir.endsWith("xml")) anlageChooser.setFileFilter(xml);
      else if (chatUploadDir.endsWith("zip")) anlageChooser.setFileFilter(zip);
    }

    anlageChooser.viewTypeDetails();
    anlageChooser.sort();
    if (JFileChooser.APPROVE_OPTION != anlageChooser.showOpenDialog(JChat.this)) {
      return;
    }
    String filename = anlageChooser.getSelectedFile().getAbsolutePath();
    chatUploadDir = filename;
    chatframe.setAnlage(filename);
  }



  /**
   * Diese Methode wird vom Caller aufgerufen und ist Teil der 1:1 Telefonie. Der
   * Anrufer schließt das Fenster.
   *
   */
  void closeCallerWindowPhone() {

    if (micProducer != null) micProducer.terminate(); // wurde nie gestartet
    speakerTerminate();

    SoundClip.stopOffhooktone();
    SoundClip.stopDialtone();

    userdata.setVolume(callerframe.getVolume());
    Thread.startVirtualThread(() -> saveTelefonvolume(userdata.getPseudoid(), userdata.getVolume()));

    // CAller
    UPDATEPHONE phone = new UPDATEPHONE();
    phone.setCommand(Command.UPDATEPHONE);
    phone.setHeader(REQUEST);
    phone.setDataset(Protocol.DATASET);
    phone.setPseudoid(userdata.getPseudoid());
    phone.setOnBusy(false); // wieder anrufbar
    executorChat.execute(() -> wsClient.sendMessage(phone));
  }



  /**
   * Der Anwender hebt den Hörer ab oder legt ihn auf. Die Methode wird von der
   * Telefonkonferenz aufgerufen.
   *
   *
   * @param offhook
   *                {@code true}, er hebt den Hörer ab
   */
  void conference(boolean offhook) {
    if (telefonTeilnehmenFrame == null || telefonTeilnehmenFrame.getKonferenzname() == null) return;

    CONFERENCE conference = new CONFERENCE();
    conference.setCommand(Command.CONFERENCE);
    conference.setHeader(HEADER.REQUEST);
    conference.setDataset(Protocol.DATASET);
    conference.setKonferenzraum(telefonTeilnehmenFrame.getKonferenzname());
    conference.setConferenceKey(telefonTeilnehmenFrame.getConferenceKey());
    conference.setPseudoid(userdata.getPseudoid());
    conference.setSession(userdata.getWebsocketSession());
    conference.setOffhook(offhook);
    executorChat.execute(() -> wsClient.sendMessage(conference));
  }



  /**
   * Baue eine Verbindung zum Javacommserver auf.
   *
   * @param domain
   *               an diese Domäne wird gesendet
   */
  public void connect(String domain) {
    root.setDomain(domain);

    String websocketUrl = Environment.getInstance().getWebsocketUrl(domain);
    log.info(websocketUrl);
    try {
      wsClient.connectToServer(websocketUrl);
      // falls eine Verbindung möglich ist, geht es bei onOpen weiter
    }
    catch (DeploymentException e) {
      // Alles im AWT
      log.info(e.getMessage());
      log.error("Ich konnte keine Verbindung zum Server herstellen.");
      log.error("Der Server <" + websocketUrl + "> ist nicht empfangsbereit oder nicht erreichbar.");
      JOptionPane.showMessageDialog(
          JChat.this,
          "<html>" + "<b>Es konnte keine Verbindung mit der Domäne<br><br><span style=\"color:red\">"
              + websocketUrl
              + "</span><br><br>hergestellt werden.<br>- Zeitüberschreitung beim Verbindungsaufbau."
              + "</b>"
              + "</html>",
          "Verbindungsaufbau abgebrochen", JOptionPane.ERROR_MESSAGE
      );
      itemAnmelden.setEnabled(true);
      itemRegistrieren.setEnabled(true);
      return;
    }
    catch (UnresolvedAddressException e) {
      log.fatal(e);
      JOptionPane.showMessageDialog(
          JChat.this,
          "<html>" + "Es konnte keine Verbindung zum Server hergestellt werden.<br><br>"
              + websocketUrl
              + "</html>",
          "Verbindungsaufbau abgebrochen", JOptionPane.ERROR_MESSAGE
      );
      itemAnmelden.setEnabled(true);
      itemRegistrieren.setEnabled(true);
    }
    catch (IOException | URISyntaxException e) {
      log.error(e.getMessage(), e.getCause());
    }
  }



  /**
   * Der Lautsprecher wird für die Telefonkonferenz aktiviert.
   * 
   * @param volume
   *                         diese Lautstärke
   * @param websocketSession
   *                         für diesen Absender wird eine Leitung geöffnet
   * @throws TelefonformatException
   *                                  das Telefonformat ist ungültig
   * @throws MixerNotFoundException
   *                                  kein aktiver Lautsprecher gefunden
   * @throws LineUnavailableException
   *                                  keine Lautsprecherleitung ist verfügbar
   */
  ConferenceSpeaker createConferenceSpeaker(int volume, String websocketSession)
      throws TelefonformatException, MixerNotFoundException, LineUnavailableException {

    if (0 > volume || volume > 102) throw new VolumeException("Volume: " + volume);

    ConferenceSpeaker conferenceEmpfaenger = new ConferenceSpeaker(
        telefonLautsprecher, TelefonUtil.toTelefonformat(Sprachguete.MEDIUM), websocketSession
    );
    conferenceEmpfaenger.setVolume(volume);
    conferenceEmpfaenger.setTargetLatencyMs(100); // 100 ms Latenz
    return conferenceEmpfaenger;
  }



  /**
   * Über diesen Listener kann das Versenden von Nachrichen über einen Private
   * Chat verfolgt werden.
   * 
   * @param remoteNickname
   *                       ist der Nickname des Nachrichtenempfängers
   * @param internalFrame
   *                       der eingeblendete Chatraum ist ein Private Chatraum
   * 
   * @return zurückgegeben wird ein Listener
   */
  PropertyChangeListener createPrivatechatListener(String remoteNickname, JChatFrame internalFrame) {
    return event -> {
      Object source = event.getSource();
      Control rc = (Control) event.getNewValue();
      switch(rc) {
        case DOWNLOAD:
          downloadRaumPrivate(root.getDomain(), event, internalFrame);
          break;
        case ANLAGE:
          chooseAttachment(internalFrame);
          break;
        case MESSAGE:
          Botschaft botschaft = (Botschaft) event.getOldValue();
          if (botschaft.isAttachment()) {
            PRIVATECHATFILE chatfile = new PRIVATECHATFILE();
            chatfile.setCommand(Command.PRIVATECHATFILE);
            chatfile.setHeader(REQUEST);
            chatfile.setDataset(Protocol.DATASET);
            chatfile.setAbsoluteFilename(botschaft.getAbsoluteFilename());
            chatfile.setFilename(botschaft.getFilename());
            chatfile.setFilesize(botschaft.getFilesize());
            chatfile.setMessage(event.getPropertyName());
            chatfile.setRemotePseudoid(chatFrame.getRemotePseudoid());
            chatfile.setPseudoid(userdata.getPseudoid());
            chatfile.setRemoteNickname(remoteNickname); // ist der Raumname in einem

            log.info("-----> {}", chatfile.toString());

            // Privatechat
            executorChat.execute(() -> wsClient.sendMessage(chatfile));
            break;
          }

          PRIVATEMESSAGE privatemessage = new PRIVATEMESSAGE();
          privatemessage.setCommand(Command.PRIVATEMESSAGE);
          privatemessage.setHeader(REQUEST);
          privatemessage.setDataset(Protocol.DATASET);
          privatemessage.setPseudoid(userdata.getPseudoid());
          for (JChatFrame frame : chatVector) {
            if (frame.getFrameTitleId().equals(remoteNickname)) {
              privatemessage.setRemotePseudoid(frame.getRemotePseudoid());
              break;
            }
          }
          privatemessage.setMessage(event.getPropertyName());
          log.info("-----> {}", privatemessage.toString());
          executorChat.execute(() -> wsClient.sendMessage(privatemessage));
          break;
        case SELECTED:
          selectFrame(event.getPropertyName());
          break;
        case QUIT:
          // private chat schliessen
          // leaveprivatechat senden
          int countFrames = chatVector.size();
          for (int n = 0; n < countFrames; n++) {
            JChatFrame chatframe = chatVector.elementAt(n);
            if (source.equals(chatframe)) {
              chatVector.removeElementAt(n);
              frameVector.remove(chatframe);
              LEAVEPRIVATECHAT leaveChat = new LEAVEPRIVATECHAT();
              leaveChat.setCommand(Command.LEAVEPRIVATECHAT);
              leaveChat.setHeader(REQUEST);
              leaveChat.setDataset(Protocol.DATASET);
              leaveChat.setRemotePseudoid(chatframe.getRemotePseudoid());
              executorChat.execute(() -> wsClient.sendMessage(leaveChat));
              break;
            }
          }
          babelfish.remove(source);
          removeWM((JChatFrame) source);
          windowAnrufen.removeWindowAnrufenListener((JChatFrame) source);
          break;
        default:
          log.info(event.getNewValue());
          break;
      }

    };
  }



  public void deleteNicknames(String pseudoid, CBlock cblock, List<String> nicknames) {
    if (!saveNicknames(pseudoid, cblock, nicknames)) {
      EventQueue.invokeLater(
          () -> JOptionPane.showMessageDialog(
              JChat.this, dieSpitznamenKonntenNicht.toString(), menuAdministrator.getText(),
              JOptionPane.ERROR_MESSAGE
          )
      );
      return;
    }
    boolean result[] = new boolean[] {false};
    try(Client client = ClientBuilder.newClient()) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/administrator/delete/nicknames");

      WebTarget webtarget = client.target(url.toString());
      Form form = new Form();
      form.param("pseudoid", pseudoid).param("cblock", cblock.toString())
          .param("nicknames", Wrapper.toString(nicknames));
      result[0] = webtarget.request(MediaType.TEXT_PLAIN).post(Entity.form(form), Boolean.class);
    }
    catch (Exception e) {
      log.error(e.getMessage());
    }
    EventQueue.invokeLater(() -> {
      if (result[0]) {
        JOptionPane.showMessageDialog(
            JChat.this, alleVerbotenenSpitznamen.toString(), menuAdministrator.getText(),
            JOptionPane.INFORMATION_MESSAGE
        );
      }
      else {
        JOptionPane.showMessageDialog(
            JChat.this, dieSpitznamenKonntenNicht.toString(), menuAdministrator.getText(),
            JOptionPane.ERROR_MESSAGE
        );
      }
    });
  }



  public void deleteProgrammversionen(String pseudoid, CBlock cblock, List<String> versionen) {

    Thread.startVirtualThread(() -> {
      if (!saveProgrammversionen(pseudoid, cblock, versionen)) {
        EventQueue.invokeLater(() -> {
          JOptionPane.showMessageDialog(
              JChat.this, dieProgrammversionen.toString(), menuAdministrator.getText(),
              JOptionPane.ERROR_MESSAGE
          );
          return;
        });
      }
      boolean result[] = new boolean[1];
      ClientConfig config = new ClientConfig();
      config.property(ClientProperties.CONNECT_TIMEOUT, 2000);

      try(Client client = ClientBuilder.newClient(config)) {
        StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
            .append("/javacommserver/administrator/delete/outdated");
        WebTarget target = client.target(url.toString());
        Form form = new Form();
        form.param("pseudoid", pseudoid).param("cblock", cblock.toString())
            .param("outdated", Wrapper.toString(versionen));
        result[0] = target.request(MediaType.TEXT_PLAIN).post(Entity.form(form), Boolean.class);
      }
      catch (Exception e) {
        log.error(e.getMessage());
        result[0] = false;
      }

      EventQueue.invokeLater(() -> {
        if (result[0]) {
          JOptionPane.showMessageDialog(
              JChat.this, alleProgrammversionen.toString(), menuAdministrator.getText(),
              JOptionPane.INFORMATION_MESSAGE
          );
        }
        else {
          JOptionPane.showMessageDialog(
              JChat.this, dieProgrammversionen.toString(), menuAdministrator.getText(),
              JOptionPane.ERROR_MESSAGE
          );
        }

      });

    });

  }



  /**
   * Lösche alle Dateieinträge auf dem Server.
   *
   *
   */
  void deleteServerFiles() {
    DELETEUPLOADFILES deletefiles = new DELETEUPLOADFILES();
    deletefiles.setCommand(Command.DELETEUPLOADFILES);
    deletefiles.setHeader(REQUEST);
    deletefiles.setDataset(Protocol.DATASET);
    deletefiles.setIp(myIP);
    deletefiles.setPseudoid(userdata.getPseudoid());
    executorChat.execute(() -> wsClient.sendMessage(deletefiles));
  }



  /**
   * Speichere eine Datei ab.
   * 
   * @param domain
   *                    an diese Domain wird gesendet
   * @param ort
   *                    enthält alle Informationen für Datei speichern
   * @param progressbar
   *                    Anzeigepanel
   * @param filesize
   *                    die Anlagegröße in Bytes
   * @param chunks
   *                    alle Chunknummern
   * @return {@code true}, diese Datei konnte gespeichert werden
   */
  public boolean downloadChatfileSync(String domain, Speicherort ort, JProgressBar progressbar,
      final long filesize, List<Long> chunks) {
    boolean done = false;

    StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
        .append("/javacommserver/chat/anlage");

    byte[] buffer = new byte[UPLOAD_DOWNLAOD_BUFFERSIZE];
    int read = 0;
    long count = 0;

    Path destinationParent = Paths.get(ort.getLocation()).getParent();

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);

    try(Client client = ClientBuilder.newClient(config)) {
      Path destinationFile = Files.createTempFile(destinationParent, null, null);
      destinationFile.toFile().deleteOnExit();

      // Tempfile erzeugen
      for (Long chunk : chunks) {
        WebTarget webtarget = client.target(url.toString()).queryParam("chunk", chunk);
        try(
          InputStream response = webtarget.request(MediaType.APPLICATION_OCTET_STREAM)
              .header("number", ort.getNumber()).get(InputStream.class);
          BufferedOutputStream outbuffer = new BufferedOutputStream(
              Files.newOutputStream(destinationFile, StandardOpenOption.APPEND), UPLOAD_DOWNLAOD_BUFFERSIZE
          );
        ) {
          while ((read = response.read(buffer)) != -1) {
            if (cancelDownload.get().equals(ort.getDateiname())) {
              cancelDownload.set("");
              Files.deleteIfExists(destinationFile);
              return false;
            }
            outbuffer.write(buffer, 0, read);
            count += read;
            final long help = count;
            EventQueue.invokeLater(() -> {
              int neu = (int) (help * 100l / filesize);
              progressbar.setValue(neu);
            });
          }
          done = true;
        }
        catch (IOException e) {
          log.warn(e.getMessage(), e);
        }
      }
      Path neuername = destinationParent.resolve(ort.getDateiname());
      Files.move(destinationFile, neuername, StandardCopyOption.REPLACE_EXISTING);
    }
    catch (IOException e) {
      log.warn(e.getMessage(), e);
    }
    return done;
  }



  /**
   * Speichere eine Datei ab.
   * 
   * @param domain
   *                    an diese Adresse wird gesendet
   * @param ort
   *                    enthält alle Informationen für Datei speichern
   * @param progressbar
   *                    Anzeigepanel
   * @param filesize
   *                    die Anlagegröße in Bytes
   * @param chunks
   *                    alle Chunknummern
   * @return {@code true}, diese Datei konnte gespeichert werden
   */
  public boolean downloadPrivateChatfileSync(String domain, Speicherort ort, JProgressBar progressbar,
      final long filesize, List<Long> chunks) {
    boolean done = false;
    final StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
        .append("/javacommserver/chat/anlage/private");

    byte[] buffer = new byte[UPLOAD_DOWNLAOD_BUFFERSIZE];
    int read = 0;
    long count = 0;

    Path destinationParent = Paths.get(ort.getLocation()).getParent();
    try(Client client = ClientBuilder.newClient()) {
      Path tempFile = Files.createTempFile(destinationParent, null, null);
      tempFile.toFile().deleteOnExit();
      // Tempfile erzeugen
      for (Long chunk : chunks) {
        WebTarget webtarget = client.target(url.toString()).queryParam("chunk", chunk);
        try(
          InputStream response = webtarget.request(MediaType.APPLICATION_OCTET_STREAM)
              .header("number", ort.getNumber()).get(InputStream.class);
          BufferedOutputStream outbuffer = new BufferedOutputStream(
              Files.newOutputStream(tempFile, StandardOpenOption.APPEND), UPLOAD_DOWNLAOD_BUFFERSIZE
          );
        ) {
          while ((read = response.read(buffer)) != -1) {
            if (cancelDownload.get().equals(ort.getDateiname())) {
              cancelDownload.set("");
              Files.deleteIfExists(tempFile);
              return false;
            }
            outbuffer.write(buffer, 0, read);
            count += read;
            final long help = count;
            EventQueue.invokeLater(() -> {
              int neu = (int) (help * 100l / filesize);
              progressbar.setValue(neu);
            });
          }
          done = true;
        }
        catch (IOException e) {
          log.warn(e.getMessage(), e);
        }
      }
      Path neuername = destinationParent.resolve(ort.getDateiname());
      Files.move(tempFile, neuername, StandardCopyOption.REPLACE_EXISTING);
    }
    catch (IOException e) {
      log.warn(e.getMessage(), e);
    }
    return done;
  }



  /**
   * Eine Datei wird aus dem Chat heruntergeladen und gespeichert. Diese Methode
   * wird vom AWT ausgeführt.
   *
   *
   * @param event
   *                  das auslösende Ereignis
   * @param chatframe
   *                  dieser ChatFrame führt den Download aus
   */
  private void downloadRaumBesprechung(String domain, PropertyChangeEvent event, JChatFrame chatframe) {
    // AWT

    Speicherort ort = (Speicherort) event.getOldValue();
    chatDownloadDir = ort.getLocation();
    chatframe.setDownloadDir(chatDownloadDir);

    // AWT

    CircularProgressBarUI circular = new CircularProgressBarUI();
    JProgressBar progressbar = new JProgressBar();
    progressbar.setUI(circular);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_STROKE_WIDTH, 5);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_ACCELERATE, false);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_PULSE_COMPLETION_ACTIVE, true);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_SPARK_ACTIVE, true);
    progressbar.setIndeterminate(false);
    progressbar.setStringPainted(true);
    progressbar.setBackground(Resource.JQUERY_HOMEPAGE);
    progressbar.setForeground(Resource.JQUERY_GREEN_YELLOW);
    progressbar
        .putClientProperty(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
    progressbar.setPreferredSize(new Dimension(50, 50));

    GridBagLayout gridbag = new GridBagLayout();
    GridBagConstraints con = new GridBagConstraints();
    JPanel downloadAnimator = new JPanel(gridbag);
    JLabel downloadLocation = new JLabel(ort.getDateiname());

    con.gridx = 0;
    con.gridy = 0;
    con.anchor = GridBagConstraints.CENTER;
    con.weightx = 1.0;
    con.insets = new Insets(0, 0, 0, 0);
    gridbag.setConstraints(downloadLocation, con);
    downloadAnimator.add(downloadLocation);

    con.gridx = 0;
    con.gridy = 1;
    con.anchor = GridBagConstraints.CENTER;
    con.weightx = 1.0;
    con.insets = new Insets(12, 0, 0, 0);
    gridbag.setConstraints(progressbar, con);
    downloadAnimator.add(progressbar);

    MultilingualButton downloadAbbrechen = new MultilingualButton(KEY.BUTTON_ABBRECHEN);
    JOptionPane pane = new JOptionPane(downloadAnimator, JOptionPane.PLAIN_MESSAGE);
    pane.setOptions(new MultilingualButton[] {downloadAbbrechen});
    pane.setOptionType(JOptionPane.DEFAULT_OPTION);
    JInternalFrame downloadFrame = pane.createInternalFrame(chatframe, downloadAttachment.toString());
    MultilingualInternalFrame multi = new MultilingualInternalFrame(KEY.STRING_DOWNLOAD, downloadFrame);

    downloadAbbrechen.setMnemonic(-1);
    downloadAbbrechen.addActionListener(abbrechenEvent -> {
      cancelDownload.set(ort.getDateiname());
    });

    babelfish.add(downloadAbbrechen);
    babelfish.add(multi);

    multi.setMaximizable(false);
    multi.setResizable(false);
    multi.setIconifiable(false);
    multi.setClosable(false);
    multi.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    multi.setFrameIcon(new ImageIcon(resource.getResource(Resource.DOWNLOAD_CLOUD_32x32)));
    multi.show();

    CompletableFuture.runAsync(() -> {
      // Dateigröße abfragen, Verbindungsabbruch wird gefangen
      try {
        ArrayList<Long> chunknumbers = readChunknumbersFromJson(domain, ort.getNumber());
        Long filesize = readChatfileSize(domain, ort.getNumber());
        boolean done = downloadChatfileSync(domain, ort, progressbar, filesize, chunknumbers);
        if (done) {
          Thread.sleep(Duration.ofSeconds(1));
        }
        else {
          // konnte nicht gespeichert werden
          Files.deleteIfExists(Paths.get(ort.getLocation()));
        }
      }
      catch (InterruptedException | IOException e) {
        log.error(e.getMessage());
      }
      finally {
        EventQueue.invokeLater(() -> {
          babelfish.remove(downloadAbbrechen);
          babelfish.remove(multi);
          multi.doDefaultCloseAction();
        });
      }
    }, executorJersey);

  }



  /**
   * 
   * @param domain
   *                         an diese Domäne wird gesendet
   * @param event
   * @param privateChatframe
   */
  private void downloadRaumPrivate(String domain, PropertyChangeEvent event, JChatFrame privateChatframe) {
    // AWT

    Speicherort ort = (Speicherort) event.getOldValue();
    chatDownloadDir = ort.getLocation();
    privateChatframe.setDownloadDir(chatDownloadDir);

    // AWT

    CircularProgressBarUI circular = new CircularProgressBarUI();
    JProgressBar progressbar = new JProgressBar();
    progressbar.setUI(circular);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_STROKE_WIDTH, 5);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_ACCELERATE, false);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_PULSE_COMPLETION_ACTIVE, true);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_SPARK_ACTIVE, true);
    progressbar.setIndeterminate(false);
    progressbar.setStringPainted(true);
    progressbar.setBackground(Resource.JQUERY_HOMEPAGE);
    progressbar.setForeground(Resource.JQUERY_GREEN_YELLOW);
    progressbar
        .putClientProperty(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
    progressbar.setPreferredSize(new Dimension(50, 50));

    GridBagLayout gridbag = new GridBagLayout();
    GridBagConstraints con = new GridBagConstraints();
    JPanel downloadAnimator = new JPanel(gridbag);
    JLabel downloadLocation = new JLabel(ort.getDateiname());

    con.gridx = 0;
    con.gridy = 0;
    con.anchor = GridBagConstraints.CENTER;
    con.weightx = 1.0;
    con.insets = new Insets(0, 0, 0, 0);
    gridbag.setConstraints(downloadLocation, con);
    downloadAnimator.add(downloadLocation);

    con.gridx = 0;
    con.gridy = 1;
    con.anchor = GridBagConstraints.CENTER;
    con.weightx = 1.0;
    con.insets = new Insets(12, 0, 0, 0);
    gridbag.setConstraints(progressbar, con);
    downloadAnimator.add(progressbar);

    MultilingualButton downloadAbbrechen = new MultilingualButton(KEY.BUTTON_ABBRECHEN);
    JOptionPane pane = new JOptionPane(downloadAnimator, JOptionPane.PLAIN_MESSAGE);
    pane.setOptions(new MultilingualButton[] {downloadAbbrechen});
    pane.setOptionType(JOptionPane.DEFAULT_OPTION);
    JInternalFrame downloadFrame = pane.createInternalFrame(privateChatframe, downloadAttachment.toString());
    MultilingualInternalFrame multi = new MultilingualInternalFrame(KEY.STRING_DOWNLOAD, downloadFrame);

    downloadAbbrechen.setMnemonic(-1);
    downloadAbbrechen.addActionListener(abbrechenEvent -> {
      cancelDownload.set(ort.getDateiname());
    });

    babelfish.add(downloadAbbrechen);
    babelfish.add(multi);

    multi.setMaximizable(false);
    multi.setResizable(false);
    multi.setIconifiable(false);
    multi.setClosable(false);
    multi.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    multi.setFrameIcon(new ImageIcon(resource.getResource(Resource.DOWNLOAD_CLOUD_32x32)));
    multi.show();

    CompletableFuture.runAsync(() -> {
      // Dateigröße abfragen, Verbindungsabbruch wird gefangen
      ArrayList<Long> chunknumbers = readChunknumbersFromPrivate(domain, ort.getNumber());
      Long filesize = readChatfileSizePrivate(domain, ort.getNumber());
      boolean done = downloadPrivateChatfileSync(domain, ort, progressbar, filesize, chunknumbers);
      try {
        if (done) {
          Thread.sleep(Duration.ofSeconds(1));
        }
        else {
          // konnte nicht gespeichert werden
          Files.deleteIfExists(Paths.get(ort.getLocation()));
        }
      }
      catch (InterruptedException | IOException e) {
        log.error(e.getMessage());
      }
      finally {
        EventQueue.invokeLater(() -> {
          babelfish.remove(downloadAbbrechen);
          babelfish.remove(multi);
          multi.doDefaultCloseAction();
        });
      }
    }, executorJersey);
  }



  /**
   * Gab es irgendwelche Ereignisse in der Telefon-Empfänger-GUI?
   *
   *
   * @return ein Listener
   */
  private PropertyChangeListener empfaengerListener(INCOMINGCALL incoming) {

    return new PropertyChangeListener() {

      @Override
      public void propertyChange(PropertyChangeEvent event) {
        Control control = (Control) event.getNewValue();
        switch(control) {
          case TELEFON_ABNEHMEN:
            SoundClip.stopBusytone();
            SoundClip.stopRingtone();
            SoundClip.stopDialtone();
            SoundClip.stopOffhooktone();
            INCOMINGCALL response = new INCOMINGCALL();
            response.setCommand(Command.INCOMINGCALL);
            response.setHeader(RESPONSE);
            response.setDataset(Protocol.DATASET);
            response.setCallerNickname(incoming.getCallerNickname());
            response.setCallerPseudoid(incoming.getCallerPseudoid());
            response.setCallerVoice(incoming.getCallerVoice());
            response.setReceiverPseudoid(incoming.getReceiverPseudoid());
            response.setReceiverNickname(incoming.getReceiverNickname());
            // für Test
            response.setReceiverVoice(voiceQuality.toString());
            response.setReceiverSession(incoming.getReceiverSession());
            response.setCallerSession(incoming.getCallerSession());
            response.setConferencekey(incoming.getConferencekey());
            executorChat.execute(() -> wsClient.sendMessage(response));
            break;
          case SELECTED:
            selectFrame(event.getPropertyName());
            break;
          case TELEFON_VOLUME:
            conferenceSpeakerlist.forEach(speaker -> speaker.setVolume(empfaengerFrame.getVolume()));
            break;
          case TELEFON_AUFLEGEN:

            // muss aufgerufen werden bei einem harten Programmausstieg!!!!!!!!!!!!
            // kommt nach windowClosing()
            // rington abschalten
            SoundClip.stopRingtone();

            if (micProducer != null) micProducer.terminate();
            speakerTerminate();

            ONHOOK onhook = new ONHOOK();
            onhook.setCommand(Command.ONHOOK);
            onhook.setHeader(REQUEST);
            onhook.setDataset(Protocol.DATASET);
            onhook.setCallerPseudoid(incoming.getCallerPseudoid());
            onhook.setReceiverPseudoid(incoming.getReceiverPseudoid());
            onhook.setTo(false);
            onhook.setSignal(true);
            onhook.setConferencekey(onhookConferencekey);

            executorChat.execute(() -> wsClient.sendMessage(onhook));

            // UPDATEPHONE

            // Empfänger

            UPDATEPHONE updatePhoneAll = new UPDATEPHONE();
            updatePhoneAll.setCommand(Command.UPDATEPHONE);
            updatePhoneAll.setHeader(REQUEST);
            updatePhoneAll.setDataset(Protocol.DATASET);
            updatePhoneAll.setOnBusy(false); // ich kann wieder Telefonanrufe
            // entgegennehmen
            updatePhoneAll.setPseudoid(userdata.getPseudoid());
            executorChat.execute(() -> wsClient.sendMessage(updatePhoneAll));

            empfaengerFrame.removeAllListener();
            removeWM(empfaengerFrame); // Erst aus dem WindowManager entfernen
            frameVector.remove(empfaengerFrame);
            babelfish.remove(empfaengerFrame);
            itemAnrufen.setEnabled(true);
            itemTelkoTeilnehmen.setEnabled(true);
            userdata.setVolume(empfaengerFrame.getVolume());
            Thread.startVirtualThread(() -> saveTelefonvolume(userdata.getPseudoid(), userdata.getVolume()));
            break;
          default:
            break;
        }
      }

    };
  }



  private void fensterAnordnen() {

    int x = 0;
    int y = 0;
    for (WM tmp : frameVector) {

      if (tmp instanceof JPlayerInternalFrame internalplayer) {
        Rectangle localRectangle = getBounds();
        internalplayer.setLocation(
            (localRectangle.width - JPlayerInternalFrame.SCREEN_WIDTH) / 2,
            (localRectangle.height - JPlayerInternalFrame.SCREEN_HEIGHT) / 4
        );
        continue;
      }

      if (!(tmp instanceof JInternalFrame frame)) continue;
      frame.setLocation(x, y);
      y += 52;
      x += 83;

      if (x > 800 || y > 600) break;
    }
  }



  /**
   * Mit der Drift kann der Zeitabstand zwischen Client und Server synchronisiert
   * werden.
   * 
   * @return die Zeitdifferenz zwischen Server und Client
   */
  public long getDrift() {
    long t1 = System.currentTimeMillis();

    // Client lokal erzeugen -> wird garantiert geschlossen
    try(
      Client client = ClientBuilder
          .newClient(new ClientConfig().property(ClientProperties.CONNECT_TIMEOUT, 2000))
    ) {

      long serverTime = client
          .target(Constants.PROTOCOL + Constants.DOMAIN + "/javacommserver/clock/read/timestamp").request()
          .get(Long.class);
      long t2 = System.currentTimeMillis();

      long rtt = t2 - t1;

      // Einfache Korrektur: Serverzeit + halbe Laufzeit minus aktuelle Zeit
      return serverTime + rtt / 2 - t2;
    }
    catch (Exception e) {
      return 0; // Im Fehlerfall keine Drift
    }
  }



  /**
   * Die RGB-Werte des Farbschemas abrufen.
   *
   * @param sorte
   *              Eissorte
   * @return RGB-Werte der Eissorte
   */
  public Color getEissorte(Sorte sorte) {
    Color color;
    switch(sorte) {
      case BLAUBEERE:
        color = Resource.JQUERY_HELLBLAU;
        break;
      case ERDBEERE:
        color = Resource.JQUERY_ERDBEERE;
        break;
      case JOGHURT:
        color = Color.WHITE;
        break;
      case MOKKA:
        color = Resource.JQUERY_MOKKA;
        break;
      case VANILLE:
        color = Resource.JQUERY_VANILLE;
        break;
      default:
        color = Color.WHITE;
        break;
    }
    return color;
  }



  /**
   * Wie lautet meine lokale IP-Adresse?
   *
   * @return IP-Adresse
   */
  public String getIp() {
    return myIP;
  }



  void grouproomListener(Control control) {
    switch(control) {
      case TOPICMEMBER:
        requestRaummitglieder(gruppenraum.getRoom());
        break;
      case CLOSE:
        if (launcher != null) launcher.setEnabledGruppenraum(true);
        itemGruppenraum.setEnabled(true);
        gruppenraum.removeAllListener();
        babelfish.remove(gruppenraum);
        break;
      case CREATE:
        CREATETEMPROOM createtemproom = new CREATETEMPROOM();
        createtemproom.setCommand(Command.CREATETEMPROOM);
        createtemproom.setHeader(REQUEST);
        createtemproom.setDataset(Protocol.DATASET);
        createtemproom.setEntry(Entry.PROTECTED);
        createtemproom.setLifetime(Lifetime.TEMPORARY);
        createtemproom.setPseudoid(userdata.getPseudoid());
        createtemproom.setRoom(gruppenraum.getRoom());
        createtemproom.setNickname(userdata.getNickname());
        createtemproom.setRoomtype(Roomtype.GRUPPENRAUM);
        executorChat.execute(() -> wsClient.sendMessage(createtemproom));
        break;
      case UPDATE_CHATUSER:
        List<ChatUser> neueMitgieder = gruppenraum.getNeueMitglieder();
        if (neueMitgieder.size() <= 0) return;
        updateChatuser(
            gruppenraum.getRoom(), Roomtype.GRUPPENRAUM,
            neueMitgieder.toArray(new ChatUser[neueMitgieder.size()])
        );
        break;
      default:
        break;

    }
  }



  void gruppenraumEinrichten() {
    itemGruppenraum.setEnabled(false);
    gruppenraum = new JGruppenraum();
    gruppenraum.setFrameIcon(new ImageIcon(resource.getResource(Resource.GRUPPENRAUM_25x26)));
    gruppenraum.setClosable(true);
    gruppenraum.setSize(new Dimension(JGruppenraum.WIDTH, JGruppenraum.HEIGHT));
    gruppenraum.setResizable(true);
    gruppenraum.setIconifiable(false);
    Rectangle topicRectangle = getBounds();
    gruppenraum.setLocation(
        (topicRectangle.width - JGruppenraum.WIDTH) / 2, (topicRectangle.height - JGruppenraum.HEIGHT) / 4
    );
    desktop.add(gruppenraum, JLayeredPane.MODAL_LAYER, 4);
    babelfish.add(gruppenraum);
    gruppenraum.setLanguage(ISO639.fromValue(rootLanguage.value()));
    gruppenraum.show();
    gruppenraum.addGrouproomListener(event -> {
      Control value = (Control) event.getNewValue();
      grouproomListener(value);
    });
    requestGrouprooms();

  }



  /**
   * Es werden die letzten {@link net.javacomm.share.Constants#HISTORY_MESSAGES} -
   * Nachrichten aus einem Chatraum gelesen.
   *
   * @param room
   *             der Chatraum
   *
   */
  void historymessage(String room) {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      client.register(MessageBodyHISTORYMESSAGE.class);
      StringBuilder historyUrl = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/protocol/historymessage");
      WebTarget webtarget = client.target(historyUrl.toString()).path(room);
      HISTORYMESSAGE response = webtarget.request(MediaType.APPLICATION_JSON).get(HISTORYMESSAGE.class);
      EventQueue.invokeLater(() -> {
        for (WM localWM : frameVector) {
          if (localWM.getFrameTitleId().equals(response.getRoom())) {
            JChatFrame chatframe = (JChatFrame) localWM;
            chatframe.clearMessages();
            RecordInterface[] records = response.getAttachment();
            for (RecordInterface record : records) {
              try {
                chatframe.setMessage(record);
              }
              catch (MessageException e) {
                log.warn(e.getMessage());
              }
            }
          }
        }
      });
    }
    catch (Exception e) {
      log.warn(e.getMessage());
      return;
    }
  }



  /**
   * Das Anwenderprofil wurde zuvor geändert. Alle offenen Chaträume fordern eine
   * Historie an. {@code historyMessage} wird nicht auf Private Chats angewendet,
   * weil Private Chats keine Historie haben.
   *
   */
  void historyMessageAfterUpdateUser() {
    for (JChatFrame chatframe : chatVector) {
      if (chatframe.isPrivate()) continue; // Ticket 13 Private Chats haben keine Historie
      // Ticket 12 Userliste für jeden Chat holen
      CHATUSERLIST chatuserlist = new CHATUSERLIST();
      chatuserlist.setCommand(Command.CHATUSERLIST);
      chatuserlist.setHeader(REQUEST);
      chatuserlist.setDataset(Protocol.DATASET);
      chatuserlist.setRoom(chatframe.getFrameTitleId());
      executorChat.execute(() -> wsClient.sendMessage(chatuserlist));
      Thread.startVirtualThread(() -> historymessage(chatframe.getFrameTitleId()));
    }
  }



  /**
   * Alle eingehenden Botschaften werden über diese Methode verarbeitet.
   *
   * @param message
   *                eine eingehende Nachricht.
   */
  void incoming(MESSAGE message) {

    Command command = message.getCommand();
    if (message.getCommand().equals(Command.CONFERENCEAUDIO)) {}
    else if (message.getCommand().equals(Command.NEWIMAGE)) {}
//    else {
//      log.info("Command=" + message.getCommand() + "|" + message.getHeader());
//    }

    if (message instanceof CHATMESSAGE chatmessage) {
//      log.info("<----- {}", chatmessage.toString());
      if (RESPONSE == chatmessage.getHeader()) {
        // den Raum finden
        EventQueue.invokeLater(() -> {
          for (JChatFrame chatframe : chatVector) {
            try {
              if (chatframe.getFrameTitleId().equals(chatmessage.getRoom())) {
                chatframe.setMessage(chatmessage);
              }
            }
            catch (MessageException e) {
              log.warn(e.getMessage());
            }
          }
        });
      }
    }
    else if (message instanceof CONFERENCEAUDIO conferenceaudio) {
      switch(conferenceaudio.getHeader()) {
        case CONFIRM:
          break;
        case ERROR:
          break;
        case REQUEST:
          // Empfänger
          final byte[] pdu = conferenceaudio.getContent();
          if (pdu == null) return;
          try {
            long receiveTime = System.currentTimeMillis();
            byte[] wave = DecodeFlacToWav.entry(pdu);
            ByteBuffer out = ByteBuffer.wrap(wave, 44, wave.length - 44);
            // wave to raw
            String absenderSession = conferenceaudio.getWebsocketSession();

//            log.info("absender ist eine userid {} ", absenderSession);
//
//            conferenceSpeakerlist.stream().forEach(speaker -> {
//              log.info(speaker.getUserid());
//            });
//            log.info("--");

            conferenceSpeakerlist.stream()
                .filter(conferenceSpeaker -> conferenceSpeaker.getWebsocketSession().equals(absenderSession))
                .findFirst().ifPresent(speaker -> speaker.play(out.array(), receiveTime));

          }
          catch (Exception e) {
            log.warn(e.getMessage());
          }
          return;
        case RESPONSE:
          break;
        default:
          break;
      }
    }
    else if (message instanceof UPDATEFILETYPES filetypes) {
      // transmit anfordern, wenn der Server oben ist und publish on
      // 2 Bedingung pressbutton
      if (
        CONFIRM == filetypes.getHeader() && ftserver.isAlive() && ftsFrame != null && !ftsFrame.isPublished()
      ) {
        try {
          watcher.setDirectory(Paths.get(root.getFileTransferService().getUpload()));
        }
        catch (org.nexuswob.util.NoDirectoryException e) {
          log.error(e.getMessage(), e);
        }
      }
    }
    else if (message instanceof FILETYPES filetypes) {
      EventQueue.invokeLater(() -> schwarzeliste.setSperrliste(filetypes.getFiletypes()));
    }
    else if (message instanceof ONHOOK onhook) {
      if (REQUEST == onhook.getHeader()) {
        SoundClip.stopRingtone();
        try {
          // wahrscheinlich nur in Abnehmen abspielen
          if (onhook.isSignal()) SoundClip.playBusytone();
        }
        catch (IOException | UnsupportedAudioFileException | LineUnavailableException e) {
          log.error(e.getMessage(), e.getCause());
        }

        if (micProducer != null) micProducer.terminate();
        speakerTerminate();

        EventQueue.invokeLater(() -> {
          for (WM wm : frameVector) {
            if (wm.getType() == Frames.ABNEHMEN) {
              JAbnehmenFrame abnehmenframe = (JAbnehmenFrame) wm;
              try {
                log.info("++++++++++++++ Telefon auflegen");
                abnehmenframe.setClosed(true);
                // In Telefon auflegen wird der frameVector verändert.
                // deswegen muss ein return kommen!
              }
              catch (PropertyVetoException e) {
                log.error(e.getMessage(), e);
              }
              break;
            }
          }

          for (WM wm : frameVector) {
            if (wm.getType() == Frames.ANRUFEN) {
              JAnrufenFrame anrufenframe = (JAnrufenFrame) wm;
              try {
                anrufenframe.setClosed(true);
              }
              catch (PropertyVetoException e) {
                log.error(e.getMessage(), e);
              }
              break;
            }
          }
        });

      }
    }
    else if (message instanceof MICROERROR incoming) {
      if (REQUEST == incoming.getHeader()) {

        log.info("angekommen");
        log.info(incoming.getText());

        EventQueue.invokeLater(() -> {
          JOptionPane.showMessageDialog(
              JChat.this, "<html>" + incoming.getText() + "</html>", incoming.getFromNickname(),
              JOptionPane.ERROR_MESSAGE
          );

        });
      }
    }
    else if (message instanceof INCOMINGCALL incoming) {
//      log.info("<----- {}", incoming.toString());
      if (CONFIRM == incoming.getHeader()) {

        EventQueue.invokeLater(() -> {

          onhookConferencekey = incoming.getConferencekey();

          // Lautsprecher starten, wenn möglich? ich bin der empfänger Empfänger
          try {
            // Die Mikrofoneinstellung vom Sender wird in den Lautsprecher für den Empfänger
            // eingetragen
            speakerTerminate();

            ConferenceSpeaker speakerEmpfaenger = new ConferenceSpeaker(
                telefonLautsprecher, TelefonUtil.toTelefonformat(incoming.getCallerVoice()),
                incoming.getCallerSession()
            );

            conferenceSpeakerlist.add(speakerEmpfaenger);
            speakerEmpfaenger.setVolume(userdata.getVolume());
            speakerEmpfaenger.setTargetLatencyMs(100); // 100
                                                       // ms
                                                       // Latenz
            empfaengerFrame.setVolumeGui(userdata.getVolume());

            // Die eigene Mikrofoneinstellung
            micStart(Sprachguete.toSprachguete(voiceQuality.toString()), incoming.getConferencekey());
          }
          catch (MixerNotFoundException e) {
            log.warn(e.getMessage(), e.getCause());
            EventQueue.invokeLater(() -> {
              JOptionPane.showMessageDialog(
                  JChat.this, "<html>" + keinMixer.toString() + "</html>", lautsprecherEinrichten.toString(),
                  JOptionPane.ERROR_MESSAGE
              );
              itemTelefonEinrichten.doClick();

            });
          }
          catch (TelefonformatException | LineUnavailableException e) {
            log.warn(e.getMessage(), e.getCause());
            EventQueue.invokeLater(() -> {
              JOptionPane.showMessageDialog(
                  JChat.this, "<html>" + telefonformatFalsch.toString() + "</html>",
                  lautsprecherEinrichten.toString(), JOptionPane.ERROR_MESSAGE
              );
            });
          }

        });

      }
      else if (RESPONSE == incoming.getHeader()) {

        EventQueue.invokeLater(() -> {

          try {
            // Entferne alten Speaker, falls vorhanden

            speakerTerminate();
            ConferenceSpeaker speakerSender = new ConferenceSpeaker(
                telefonLautsprecher, TelefonUtil.toTelefonformat(incoming.getReceiverVoice()),
                incoming.getReceiverSession()
            );

            conferenceSpeakerlist.add(speakerSender);

            // Der Lautstärkestartwert vom Anrufer
            speakerSender.setVolume(userdata.getVolume()); // wird an das Gerät Lautsprecher gesendet
            speakerSender.setTargetLatencyMs(100); // 100 ms Latenz
            callerframe.setVolume(userdata.getVolume()); // der Wert wird in der GUI angezeigt

            // Sender TCPAUDIO
            // Das Mikrofon wird erst nach dem Lautsprecher gestartet

            onhookConferencekey = incoming.getConferencekey();

            micStart(Sprachguete.toSprachguete(voiceQuality.toString()), incoming.getConferencekey());

          }
          catch (MixerNotFoundException e) {
            log.warn(e.getMessage(), e.getCause());
            EventQueue.invokeLater(() -> {
              JOptionPane.showMessageDialog(
                  JChat.this, "<html>" + keinMixer.toString() + "</html>", lautsprecherEinrichten.toString(),
                  JOptionPane.ERROR_MESSAGE
              );
              itemTelefonEinrichten.doClick();

            });
          }
          catch (TelefonformatException | LineUnavailableException e) {
            log.warn(e.getMessage(), e.getCause());
            EventQueue.invokeLater(() -> {
              JOptionPane.showMessageDialog(
                  JChat.this, "<html>" + telefonformatFalsch.toString() + "</html>",
                  lautsprecherEinrichten.toString(), JOptionPane.ERROR_MESSAGE
              );
            });
          }

        });

      }
      else if (REQUEST == incoming.getHeader()) {
        // Fenster öffnen
        EventQueue.invokeLater(() -> {
          try {
            if (telefonTeilnehmenFrame != null) telefonTeilnehmenFrame.setClosed(true);
          }
          catch (PropertyVetoException e1) {
            log.warn(e1.getMessage(), e1);
          }

          itemAnrufen.setEnabled(false);
          itemTelkoTeilnehmen.setEnabled(false);
          empfaengerFrame = new JAbnehmenFrame();
          windowAnrufen.setTriggerNickname(incoming.getCallerNickname());
          windowAnrufen.addWindowAnrufenListener(empfaengerFrame);

          anrufenEvent(empfaengerFrame);
          empfaengerFrame.setVolumeGui(userdata.getVolume());
          empfaengerFrame.startenKnopfleiste();
          Rectangle localRectangle = getBounds();
          empfaengerFrame.setLocation(
              (localRectangle.width - JAbnehmenFrame.SCREEN_WIDTH) / 2,
              (localRectangle.height - JAbnehmenFrame.SCREEN_HEIGHT) / 4
          );
          desktop.add(empfaengerFrame, JLayeredPane.DEFAULT_LAYER, PLAYER_LAYER);
          babelfish.add(empfaengerFrame);
          empfaengerFrame.setLanguage(ISO639.fromValue(rootLanguage.value()));
          setBackgroundTreeview(helado, empfaengerFrame);
          empfaengerFrame.show();
          frameVector.addElement(empfaengerFrame);
          insertWM(empfaengerFrame, empfaengerFrame.getFrameTitleId());
          empfaengerFrame.addAbnehmenListener(empfaengerListener(incoming));
          try {
            SoundClip.playRingtone();
          }
          catch (IOException | UnsupportedAudioFileException | LineUnavailableException e) {
            log.error(e.getMessage(), e.getCause());
          }

          for (WM wm : frameVector) {
            if (wm.getType() == Frames.ANRUFEN) {
              JAnrufenFrame anrufenframe = (JAnrufenFrame) wm;
              try {
                anrufenframe.setClosed(true);
              }
              catch (PropertyVetoException e) {
                log.error(e.getMessage(), e);
              }
              break;
            }
          }

        });
      }
      else if (HEADER.ERROR == incoming.getHeader()) {
        try {
          SoundClip.playBusytone();
        }
        catch (IOException | UnsupportedAudioFileException | LineUnavailableException e) {
          log.error(e.getMessage(), e.getCause());
        }
        EventQueue.invokeLater(() -> {
          String text;
          switch(incoming.getMultilingualkey()) {
            case STRING_IST_NICHT_MEHR_ONLINE:
              text = istNichtMehrOnline.toString();
              break;
            case STRING_HAT_AUFGELEGT:
              text = hatAufgelegt.toString();
              break;
            default:
              text = "";
              break;
          }
          JOptionPane.showMessageDialog(
              JChat.this, "<html><b>" + incoming.getReceiverNickname() + text + "</b></html>",
              telefonieren.toString(), JOptionPane.WARNING_MESSAGE
          );

        });
      }
    }
    else if (message instanceof ONCALL oncall) {
      if (RESPONSE == oncall.getHeader()) {
        userdata.setOncall(oncall.isOnCall());
      }
    }
    else if (message instanceof DOWNLOAD download) {
      if (RESPONSE == download.getHeader()) {

        if (channels.getChannels().size() >= 2) {
          Runnable runnable = () -> {
            JOptionPane.showMessageDialog(
                JChat.this, "<html>" + alleKanaeleSindBelegt.toString() + "</html>",
                menuOnlinetransfer.getText(), JOptionPane.INFORMATION_MESSAGE
            );
          };
          EventQueue.invokeLater(runnable);
          return;
        }

        Matcher matcher = Protocol.PATH_SEPARATOR.matcher(download.getPathfile());
        if (!matcher.matches()) {
          log.error("ERROR senden");
          return;
        }
        // String filenameOnly = matcher.group(2);
        String pathOnly = matcher.group(1);

        Channel channel = new Channel();
        Slot slot = new Slot();
        slot.setFilename(download.getFilename());
        slot.setSourcepath(pathOnly);
        slot.setSlotnumber(download.getSlot());
        slot.setDownloadDir(root.getFileTransferService().getDownload());
        channel.setSlot(slot);
        channel.setHost(download.getIp());
        channel.setPort(download.getPort());
        channel.addChannelListener(new ChannelListener() {

          @Override
          public void onClosed(ChannelEvent event) {
            channels.getChannels().remove(channel);
          }



          @Override
          public void onError(ChannelEvent event) {
            Runnable runnable = () -> {
              JOptionPane.showMessageDialog(
                  JChat.this, "<html>" + "<b>" + event.getErrorMessage() + "</b>" + "</html>",
                  menuOnlinetransfer.getText(), JOptionPane.ERROR_MESSAGE
              );
            };
            EventQueue.invokeLater(runnable);
            log.info("closing channel - " + event.getErrorMessage());
            channel.softClosing();
          }



          @Override
          public void onFinished(ChannelEvent event) {
            Runnable runnable = () -> {
              ftsFrame.setProgressDownload(channel.getSlot().getSlotnumber(), event.getProgress());
            };
            EventQueue.invokeLater(runnable);
            log.info("Die Übertragung ist abgeschlossen.");
            channel.softClosing();
          }



          @Override
          public void onProgressed(ChannelEvent event) {
            Runnable runnable = () -> {
              ftsFrame.setProgressDownload(channel.getSlot().getSlotnumber(), event.getProgress());
            };
            EventQueue.invokeLater(runnable);
          }



          @Override
          public void onStarted(ChannelEvent event) {
            channels.getChannels().add(channel);
            log.info(channels.getChannels().size());
            Runnable runnable = () -> {
              ftsFrame.setProgressDownload(channel.getSlot().getSlotnumber(), event.getProgress());
            };
            EventQueue.invokeLater(runnable);
          }
        });
        try {
          // die Verbindung über einen Thread herstellen
          // ich bin im CHAT Thread
          // der Videoservice schläft, der RegisterService schläft auch
          channel.connectToServer();
        }
        catch (ChannelException e) {
          log.error(e.getMessage(), e.getCause());
          return;
        }
      }
      else if (HEADER.ERROR == download.getHeader()) {
        Runnable runnable = () -> {
          JOptionPane.showMessageDialog(
              JChat.this, "<html>" + download.getText() + "</html>", menuOnlinetransfer.getText(),
              JOptionPane.INFORMATION_MESSAGE
          );
          // slot löschen
          ftsFrame.clearSlot(download.getSlot());
        };
        EventQueue.invokeLater(runnable);
      }

    }
    else if (message instanceof UPLOADFILES uploadfiles) {
      if (RESPONSE == uploadfiles.getHeader()) {
        // log.info("ip=" + uploadfiles.getIp());
        // log.info("hasStarted=" + uploadfiles.hasStarted());

        List<String> partial = textbuffer.readPartial();
        if (partial.size() == 0) return;

        Runnable runnable = () -> {
          UPLOADFILES request = new UPLOADFILES();
          request.setCommand(Command.UPLOADFILES);
          request.setHeader(REQUEST);
          request.setDataset(Protocol.DATASET);
          request.setIp(myIP);
          request.setStarted(false);
          Uploadfile[] uploadfile = new Uploadfile[partial.size()];
          for (int index = 0; index < uploadfile.length; index++) {
            uploadfile[index] = new Uploadfile();
            String filename = partial.get(index);
            uploadfile[index].setFilename(filename);
            Path path = Paths.get(filename);
            try {
              uploadfile[index].setFilesize(Files.size(path));
            }
            catch (IOException e) {
              log.warn(e.getMessage(), e.getCause());
            }

          }
          request.setUploadfile(uploadfile);
          request.setPseudoid(userdata.getPseudoid());
          request.setPort(transferConfig.getPort());
          wsClient.sendMessage(request);
        };
        executorChat.execute(runnable);
      }
    }
    else if (message instanceof

    DELETEUPLOADFILES deletefiles) {
      if (CONFIRM == deletefiles.getHeader()) {
        log.info("mit der Überwachung nicht aufhören");
      }
      else if (HEADER.ERROR == deletefiles.getHeader()) {
        Runnable runnable = () -> {
          JOptionPane.showMessageDialog(
              JChat.this, "<html>" + deletefiles.getText() + "</html>", "Filesahring",
              JOptionPane.ERROR_MESSAGE
          );
        };
        EventQueue.invokeLater(runnable);
      }
    }
    else if (message instanceof PRIVATEMESSAGE privatemessage) {
      log.info("<----- {}", privatemessage.toString());
      if (REQUEST == privatemessage.getHeader()) {
        EventQueue.invokeLater(() -> {
          int count = frameVector.size();
          for (int index = 0; index < count; index++) {
            WM localWM = frameVector.elementAt(index);
            if (
              !(localWM instanceof JChatFrame chatframe)
                  || !chatframe.isPrivate()
                  || !chatframe.getRemotePseudoid().equals(privatemessage.getPseudoid())
            ) continue;
            try {
              // er kommt beim Empfänger an
              chatframe.setMessage(privatemessage);
              // reply
            }
            catch (MessageException e) {
              log.warn(e.getMessage(), e.getCause());
            }
            break;
          }
        });
        executorChat.execute(() -> {
          PRIVATEMESSAGE response = new PRIVATEMESSAGE();
          response.setCommand(Command.PRIVATEMESSAGE);
          response.setHeader(RESPONSE);
          response.setDataset(Protocol.DATASET);
          response.setMessage(privatemessage.getMessage());
          response.setRemotePseudoid(privatemessage.getRemotePseudoid());
          response.setPseudoid(privatemessage.getPseudoid());
          response.setChatUser(privatemessage.getChatUser());
          response.setDatetime(privatemessage.getDatetime());
          response.setAttachment(privatemessage.getAttachment());
          response.setFilename(privatemessage.getFilename()); // könnten null sein
          response.setFilesize(privatemessage.getFilesize()); // könneten null sein
          log.info("-----> {}", response.toString());
          wsClient.sendMessage(response);
        });
      }
      else if (RESPONSE == privatemessage.getHeader()) {
        EventQueue.invokeLater(() -> {
          for (JChatFrame chatframe : chatVector) {
            if (
              !chatframe.isPrivate()
                  || !chatframe.getRemotePseudoid().equals(privatemessage.getRemotePseudoid())
            ) continue;
            try {
              chatframe.setMessage(privatemessage);
            }
            catch (MessageException e) {
              log.warn(e.getMessage(), e.getCause());
            }
          }
        });
      }
    }
    else if (message instanceof ENTERROOM enterroom) {
      log.info("<----- " + enterroom.toString());
      if (enterroom.getHeader() == RESPONSE) {
        // Chatframe öffnen
        EventQueue.invokeLater(() -> {

          chatFrame = new JChatFrame(enterroom.getRoom());
          windowAnrufen.addWindowAnrufenListener(chatFrame);
          anrufenEvent(chatFrame);

          chatFrame.chatTooltip(chattooltip);
          chatFrame.setLanguage(ISO639.fromValue(rootLanguage.value()));
          chatFrame.setDownloadDir(chatDownloadDir);

          chatFrame.setTickerBackground(root.getEis());
          babelfish.add(chatFrame);
          // benötige unbedingt ROOMTYPE aus der Datenbank, damit ich
          // nicht über Frontendlogik den Raumtyp ermitteln muss!
          // FORUM, PAUSENRAUM, STAMMTISCH erledigt

          switch(enterroom.getRoomtype()) {
            case FORUM:
              chatFrame.setType(Frames.FORUM);
              chatFrame.setFrameIcon(new ImageIcon(resource.getResource(Resource.WORLD)));
              chatFrame.setSize(900, 510);
              break;
            case PAUSENRAUM:
              chatFrame.setType(Frames.PAUSENRAUM);
              chatFrame.setFrameIcon(new ImageIcon(resource.getResource(Resource.PAUSENRAUM)));
              chatFrame.setSize(900, 510);
              break;
            case BESPRECHUNGSRAUM:
              chatFrame.setType(Frames.BESPRECHNUNGSRAUM);
              chatFrame.setFrameIcon(new ImageIcon(resource.getResource(Resource.BESPRECUNGSRAUM_35x26)));
              chatFrame.setSize(1185, 510);
              break;
            case GRUPPENRAUM:
              chatFrame.setType(Frames.GRUPPENRAUM);
              chatFrame.setFrameIcon(new ImageIcon(resource.getResource(Resource.GRUPPENRAUM_25x26)));
              chatFrame.setSize(900, 510);
              break;
            default:
              break;
          }
          chatFrame.setUserID(enterroom.getPseudoid());
          chatFrame.setUpdateNickname(enterroom.getNickname());
          chatFrame.setInputForeground(userdata.getForegroundColor());
          chatFrame.setInputBackground(userdata.getBackgroundColor());
          desktop.add(chatFrame, JLayeredPane.DEFAULT_LAYER, CHAT_LAYER);
          chatVector.addElement(chatFrame);
          setBackgroundTreeview(helado, chatFrame);
          chatFrame.show();
          frameVector.addElement(chatFrame);
          insertWM(chatFrame, chatFrame.getFrameTitleId());

          chatFrame.addChatListener(event -> {
            Object source = event.getSource();
            Control rc = (Control) event.getNewValue();
            switch(rc) {
              case DOWNLOAD:
                for (JChatFrame chatframe : chatVector) {
                  if (chatframe.getFrameTitleId() != event.getPropertyName()) continue;
                  downloadRaumBesprechung(root.getDomain(), event, chatFrame);
                  break;
                }
                break;
              case ANLAGE:
                for (JChatFrame select : chatVector) {
                  if (select.getFrameTitleId() != event.getPropertyName()) continue;
                  chooseAttachment(select);
                  break;
                }
                break;
              case CHATTOOLTIP:
                chattooltip = (Boolean) event.getOldValue();
                Thread.startVirtualThread(
                    () -> saveChattooltip(userdata.getPseudoid(), userdata.getPPassword(), chattooltip, drift)
                );
                break;
              case HISTORYMESSAGE:

                String room[] = new String[] {""};
                for (JChatFrame frame : chatVector) {
                  if (!frame.isSelected()) continue;
                  room[0] = frame.getFrameTitleId();
                }
                Thread.startVirtualThread(() -> historymessage(room[0]));
                break;
              case MESSAGE:
                // AWT
                Botschaft botschaft = (Botschaft) event.getOldValue();
                String record = null;
                JChatFrame internalFrame = null;
                for (JChatFrame chatframe : chatVector) {
                  if (chatframe.isSelected()) {
                    record = chatframe.getFrameTitleId();
                    break;
                  }
                }

                final String chatid = record;
                if (botschaft.isAttachment()) {
                  // mit Attachment

                  for (JChatFrame tmp : chatVector) {
                    if (tmp.getFrameTitleId() != chatid) continue;
                    tmp.removeAnlage();
                    internalFrame = tmp;
                    break;
                  }
                  uploadChatfileAnlage(botschaft, internalFrame, chatid);
                }
                else {
                  // ohne attachment
                  CHATMESSAGE chatMessage = new CHATMESSAGE();
                  chatMessage.setCommand(Command.CHATMESSAGE);
                  chatMessage.setHeader(REQUEST);
                  chatMessage.setDataset(Protocol.DATASET);
                  ChatUser chatuser = new ChatUser();
                  chatuser.setBackgroundColor(userdata.getBackgroundColor());
                  chatuser.setForegroundColor(userdata.getForegroundColor());
                  chatuser.setNickname(userdata.getNickname());
                  chatuser.setPseudoid(userdata.getPseudoid());
                  chatMessage.setChatUser(chatuser);
                  chatMessage.setRoom(record);
                  chatMessage.setMessage(botschaft.getBotschaft());
                  executorChat.execute(() -> wsClient.sendMessage(chatMessage));
                }
                break;
              case SELECTED:
                selectFrame(event.getPropertyName());
                break;
              case QUIT:
                for (JChatFrame chatframe : chatVector) {
                  if (source.equals(chatframe)) {
                    chatframe.removeAllListener();
                    chatVector.removeElement(chatframe);
                    frameVector.remove(chatframe);
                    LEAVEROOM leaveroom = new LEAVEROOM();
                    leaveroom.setCommand(Command.LEAVEROOM);
                    leaveroom.setHeader(REQUEST);
                    leaveroom.setDataset(Protocol.DATASET);
                    leaveroom.setRoom(chatframe.getFrameTitleId());
                    leaveroom.setPseudoid(userdata.getPseudoid());
                    executorChat.execute(() -> wsClient.sendMessage(leaveroom));
                    break;
                  }
                }
                removeWM((JChatFrame) source);
                babelfish.remove(source);
                windowAnrufen.removeWindowAnrufenListener((JChatFrame) source);
                break;
              case HIT:

                callPrivatechat(event);

                break;
              case MOUSE_EXITED:
                break;
              default:
                log.info(event.getNewValue());
                break;
            }
          });

          // Userliste anfordern
          CHATUSERLIST chatuserlist = new CHATUSERLIST();
          chatuserlist.setCommand(Command.CHATUSERLIST);
          chatuserlist.setHeader(REQUEST);
          chatuserlist.setDataset(Protocol.DATASET);
          chatuserlist.setRoom(enterroom.getRoom());
//          log.info("-----> {}", chatuserlist.toString());
          executorChat.execute(() -> wsClient.sendMessage(chatuserlist));
          Thread.startVirtualThread(() -> historymessage(enterroom.getRoom()));
        });
        // Warte bis Bildchirm aufgebaut ist
      }
      else if (enterroom.getHeader() == HEADER.ERROR) {
        // GUI Thread Fenster nach vorne holen
        EventQueue.invokeLater(() -> {

          selectFrame(enterroom.getRoom());

          switch(enterroom.getMultilingualkey()) {
            case SERVER_ZUTRITTSBERECHTIGUNG:
              MultilingualString zutritt = new MultilingualString(
                  KEY.SERVER_ZUTRITTSBERECHTIGUNG, ISO639.fromValue(rootLanguage.value())
              );
              JOptionPane.showMessageDialog(
                  JChat.this, "<html>" + zutritt.toString() + "</html>", "Enter Room",
                  JOptionPane.ERROR_MESSAGE
              );
              break;
            case SERVER_DER_CHATRAUM_IST_NICHT_VORHANDEN:
              // TODO im nächsten Update den Schlüssel einsetzen
              // dies ist eine Übergangslösung
              // Vor dem Release 260501-PRE2 muss MUltilingualkey aktiv werden
//              MultilingualString chatraumNichtVorhanden = new MultilingualString(
//                  KEY.SERVER_DER_CHATRAUM_IST_NICHT_VORHANDEN, ISO639.fromValue(rootLanguage.value())
//              );

              String errortext = enterroom.getText(); // errortext muss dekativiert werden
              JOptionPane.showMessageDialog(
                  JChat.this, "<html>" + errortext + "</html>", "Enter Room", JOptionPane.ERROR_MESSAGE
              );
              break;
            default:
              log.warn("ENTERROOM, dieser Fall ist unbekannt");
              break;
          }

        });
      }
    }
    else if (message instanceof CHATUSERLIST userlist) {
//      log.info("<----- {}", userlist.toString());
      EventQueue.invokeLater(() -> {
        String room = userlist.getRoom();
        for (WM localWM : frameVector) {
          if (localWM.getFrameTitleId().equals(room)) {
            final JChatFrame chatframe = (JChatFrame) localWM;
            chatframe.setUserList(userlist.getChatUser());
            break;
          }
        }
      });
    }
    else if (message instanceof CHATONLINELIST onlinelist) {
      if (REQUEST == onlinelist.getHeader()) {
        Runnable runnable = () -> {
          if (launcher != null) {
            launcher.setRecordList(onlinelist.getChatOnline());
            // Die Anzahl der aktuellen User muss in den Chaträumen ebenfalss
            // angezeigt
            // werden, nicht nur im launcher!
            // onlinelist.getChatOnline()[0].
            // log.info("Die Anzahl der aktuellen User muss in den Chaträumen
            // ebenfalss
            // angezeigt werden, nicht nur im launcher!");
            // alles ok, Fehler behoben
          }
        };
        EventQueue.invokeLater(runnable);
      }
    }
    else if (message instanceof READTOPICROOMOWNER response) {
      if (RESPONSE == response.getHeader()) {
        final Room[] rooms = response.getRoom();
        EventQueue.invokeLater(() -> besprechungsraum.setTopicrooms(rooms));
      }
    }
    else if (message instanceof USERONLINELIST onlinelist) {
      if (RESPONSE == onlinelist.getHeader()) {
        EventQueue.invokeLater(() -> {
          userOnlinelist.clear();
          Collections.addAll(userOnlinelist, onlinelist.getUserOnline());
          if (userdata == null) {
            log.warn(
                "userdata is null, kann bei einer race condition passieren, USRLOGIN ist noch nicht abgeschlossen und eine USERONLINELIST wird empfangen."
            );
            return;
          }
          userOnlinelist.removeIf(list -> list.getNickname().equals(userdata.getNickname()));
          if (personChat != null) {
            personChat.setUserOnlinelist(userOnlinelist);
          }
          if (callerframe != null && isOpened(callerframe.getFrameTitleId())) {
            // Anrufen-Frame ist geöffnet
            callerframe.setUserOnlinelist(userOnlinelist);
          }
          if (teilenframe != null) {
            teilenframe.setUserOnlinelist(userOnlinelist);
          }
          if (telefonTeilnehmenFrame != null) {

            Thread.startVirtualThread(
                () -> readPresence(
                    telefonTeilnehmenFrame.getKonferenzname(), telefonTeilnehmenFrame.getConferenceKey(),
                    userdata.getWebsocketSession()
                )
            );

          }
          // alle Chatframes durchlaufen und Farben, Nicknames anpassen
          historyMessageAfterUpdateUser();
        });
      }
    }
    else if (message instanceof CALLREMOTEUSER callremote) {

      log.info("<----- " + callremote.toString());

      if (REQUEST == callremote.getHeader()) {
        if (restrictionOn) {
          // nimmt den Anruf nicht entgegen
          CALLREMOTEUSER response = new CALLREMOTEUSER();
          response.setCommand(Command.CALLREMOTEUSER);
          response.setHeader(RESPONSE);
          response.setDataset(Protocol.DATASET);
          response.setLocalNickname(callremote.getRemoteNickname());
          response.setPseudoid(callremote.getRemotePseudoid());
          response.setRemoteNickname(callremote.getLocalNickname());
          response.setRemotePseudoid(callremote.getPseudoid());
          log.info("----->" + response.toString());
          executorChat.execute(() -> wsClient.sendMessage(response));
        }
        else {

          EventQueue.invokeLater(() -> {
            openLauncherIf(frameVector);

            chatFrame = new JChatFrame(callremote.getRemoteNickname());
            chatFrame.setSize(1185, 510);
            windowAnrufen.addWindowAnrufenListener(chatFrame);
            anrufenEvent(chatFrame);
            chatFrame.setLanguage(ISO639.fromValue(rootLanguage.value()));
            chatFrame.setDownloadDir(chatDownloadDir);

            chatFrame.chatTooltip(chattooltip);
            chatFrame.setFrameIcon(new ImageIcon(resource.getResource(Resource.PRIVAT_26x26)));
            chatFrame.setPrivate(callremote.getRemotePseudoid());
            chatFrame.setLanguage(ISO639.fromValue(rootLanguage.value()));
            chatFrame.setTickerBackground(root.getEis());
            babelfish.add(chatFrame);
            chatFrame.setInputForeground(userdata.getForegroundColor());
            chatFrame.setInputBackground(userdata.getBackgroundColor());
            desktop.add(chatFrame, JLayeredPane.DEFAULT_LAYER, CHAT_LAYER);
            chatVector.addElement(chatFrame);
            chatFrame.addChatListener(createPrivatechatListener(callremote.getRemoteNickname(), chatFrame));
            setBackgroundTreeview(helado, chatFrame);
            chatFrame.show();
            frameVector.addElement(chatFrame);
            insertWM(chatFrame, chatFrame.getFrameTitleId());

          });

          // der Angerufene nimmt den Anruf entgegen 1:1 Chat
          CALLREMOTEUSER response = new CALLREMOTEUSER();
          response.setCommand(Command.CALLREMOTEUSER);
          response.setHeader(CONFIRM);
          response.setDataset(Protocol.DATASET);
          response.setLocalNickname(callremote.getRemoteNickname());
          response.setPseudoid(callremote.getRemotePseudoid());
          response.setRemoteNickname(callremote.getLocalNickname());
          response.setRemotePseudoid(callremote.getPseudoid());
          log.info("-----> " + response.toString());

          executorChat.execute(() -> wsClient.sendMessage(response));
        }
      }
      else if (HEADER.ERROR == callremote.getHeader()) {
        EventQueue.invokeLater(() -> {
          // Die Gegenstelle ist bereits offline
          // chatFrame2.offline();
          if (personChat != null) {
            // log.info("angekommen");
            personChat.setErrorMessage(callremote.getErrorMessage());
          }
        });
      }
    }
    else if (message instanceof LEAVEPRIVATECHAT leaveChat) {
      log.info("<----- " + leaveChat.toString());
      if (REQUEST == leaveChat.getHeader()) {
        // wurde serverseitig durch cleanUser ausgelöst
        EventQueue.invokeLater(() -> {
          int countFrames = chatVector.size();
          for (int n = 0; n < countFrames; n++) {
            JChatFrame chatframe = chatVector.elementAt(n);
            if (chatframe.isPrivate()) {
              if (!leaveChat.getRemotePseudoid().equals(chatframe.getRemotePseudoid())) continue;
              chatframe.offline();
              chatframe.removeAllListener();
              removeWM(chatframe);
              frameVector.remove(chatframe);
              chatVector.remove(chatframe);
              chatframe.doDefaultCloseAction();
              break; // for
            }
          }
        });
      }
      else if (RESPONSE == leaveChat.getHeader()) {
        EventQueue.invokeLater(() -> {
          for (JChatFrame chatframe : chatVector) {
            if (chatframe.isPrivate()) {
              if (!leaveChat.getRemotePseudoid().equals(chatframe.getRemotePseudoid())) continue;
              chatframe.offline();
              // aus dem Baum entfernen
              chatframe.removeAllListener();
              chatVector.remove(chatframe);
              removeWM(chatframe);
              frameVector.remove(chatframe);
              chatframe.doDefaultCloseAction();
              break;
            }
          }
        });
      }
    }
    else if (message instanceof DELETEROOM delete) {
      if (RESPONSE == delete.getHeader()) {
        EventQueue.invokeLater(() -> {
          besprechungsraum.removeRoom(delete.getRoom());
          JOptionPane.showMessageDialog(
              JChat.this, "<html>" + delete.getText() + "</html>", raumname.toString(),
              JOptionPane.INFORMATION_MESSAGE
          );
        });
      }
      else if (HEADER.ERROR == delete.getHeader()) {
        EventQueue.invokeLater(() -> {
          // explizit room löschen
          besprechungsraum.removeEditableElement();
          JOptionPane.showMessageDialog(
              JChat.this, "<html>" + delete.getText() + "</html>",
              besprechungsRauemeVerwalten.toString() + " - " + delete.getRoom(),
              JOptionPane.INFORMATION_MESSAGE
          );
        });
      }
    }
    else if (message instanceof CREATETEMPROOM temproom) {
      if (RESPONSE == temproom.getHeader()) {
        switch(temproom.getRoomtype()) {
          case BESPRECHUNGSRAUM:
            EventQueue.invokeLater(() -> {
              // Knöpfe umschalten
              String text = "<html>"
                  + serverChatraumAngelegt.toString().replace("XXX", "<b>" + temproom.getRoom() + "</b>")
                  + "</html>";
              JOptionPane.showMessageDialog(
                  JChat.this, text, itemBesprechungsraum.getText(), JOptionPane.INFORMATION_MESSAGE
              );
              besprechungsraum.focusTo();
            });
            // Braodcast Raumliste
            ROOMLIST roomlist = new ROOMLIST();
            roomlist.setCommand(Command.ROOMLIST);
            roomlist.setHeader(REQUEST);
            roomlist.setDataset(Protocol.DATASET);
            executorChat.execute(() -> wsClient.sendMessage(roomlist));
            READTOPICROOMOWNER topicroom = new READTOPICROOMOWNER();
            topicroom.setCommand(Command.READTOPICROOMOWNER);
            topicroom.setHeader(REQUEST);
            topicroom.setDataset(Protocol.DATASET);
            topicroom.setOwner(userdata.getNickname());
            topicroom.setPseudoid(userdata.getPseudoid());
            executorChat.execute(() -> wsClient.sendMessage(topicroom));
            break;
          case GRUPPENRAUM:
            // ich werde immer geöffnet, weil ich der Erzeuger bin
            EventQueue.invokeLater(() -> {
              String text = "<html>"
                  + serverChatraumAngelegt.toString().replace("XXX", "<b>" + temproom.getRoom() + "</b>")
                  + "</html>";
              JOptionPane.showMessageDialog(
                  JChat.this, text, itemGruppenraum.getText(), JOptionPane.INFORMATION_MESSAGE
              );
              gruppenraum.focusTo();
            });
            // betrete den Raum
            ENTERROOM request = new ENTERROOM();
            request.setCommand(Command.ENTERROOM);
            request.setHeader(REQUEST);
            request.setDataset(Protocol.DATASET);
            request.setPseudoid(userdata.getPseudoid());
            request.setNickname(userdata.getNickname());
            request.setRoom(temproom.getRoom());
            log.info(request.toString());
            executorChat.execute(() -> wsClient.sendMessage(request));
            EventQueue.invokeLater(() -> {
              gruppenraum.setRoom(temproom.getRoom());
            });
            requestRaummitglieder(temproom.getRoom());

            break;
          case PAUSENRAUM:
            // ich werde immer geöffnet, weil ich der Erzeuger bin
            // betrete den Raum
            ENTERROOM requestPausenraum = new ENTERROOM();
            requestPausenraum.setCommand(Command.ENTERROOM);
            requestPausenraum.setHeader(REQUEST);
            requestPausenraum.setDataset(Protocol.DATASET);
            requestPausenraum.setPseudoid(userdata.getPseudoid());
            requestPausenraum.setNickname(userdata.getNickname());
            requestPausenraum.setRoom(temproom.getRoom());;
            log.info(requestPausenraum.toString());
            executorChat.execute(() -> wsClient.sendMessage(requestPausenraum));
            switch(temproom.getEntry()) {
              case PROTECTED:
                break;
              case PUBLIC:
                // jeder darf den Raum betreten
                EventQueue.invokeLater(() -> {
                  try {
                    pausenraum.setClosed(true);
                  }
                  catch (PropertyVetoException e) {
                    log.error(e.getMessage(), e.getCause());
                  }
                  itemPausenraum.setEnabled(true);
                });
                break;
            }
            break;
          case FORUM:
            break;
          default:
            break;
        }
      }
      else if (HEADER.ERROR == temproom.getHeader()) {
        Runnable runnable = () -> {
          String text = "";
          switch(temproom.getMultilingualkey()) {
            case SERVER_ZU_KURZER_RAUMNAME:
              text = "<html>"
                  + serverZuKurzerRaumname.toString().replace("XXX", "<b>" + temproom.getRoom() + "</b>")
                  + "</html>";
              break;
            case SERVER_DER_CHATRAUM_IST_VORHANDEN:
              text = "<html>"
                  + serverDerChatraumIstVorhanden.toString()
                      .replace("XXX", "<b>" + temproom.getRoom() + "</b>")
                  + "</html>";
              break;
            case SERVER_DER_CHATNAME_IST_NICHT_ERLAUBT:
              text = "<html>"
                  + serverChatnameNichtErlaubt.toString().replace("XXX", "<b>" + temproom.getRoom() + "</b>")
                  + "</html>";
              break;
            default:
              log.warn("noch nicht implementiert");
              break;
          }
          JOptionPane.showMessageDialog(JChat.this, text, raumname.toString(), JOptionPane.ERROR_MESSAGE);
          switch(temproom.getRoomtype()) {
            case BESPRECHUNGSRAUM:
              break;
            case FORUM:
              break;
            case GRUPPENRAUM:
              gruppenraum.disableButtonRaumErstellen();
              break;
            case PAUSENRAUM:
              pausenraum.disableButtonRaumErstellen();
              break;
            default:
              break;

          }
        };
        EventQueue.invokeLater(runnable);
      }
    }
    else if (message instanceof UPDATEUSER updateuser) {
      if (updateuser.getHeader() == CONFIRM) {

        userdata.setBackgroundColor(updateuser.getBackgroundColor());
        userdata.setForegroundColor(updateuser.getForegroundColor());
        userdata.setNickname(updateuser.getNickname());

        String back = Crypto.decryptAES(updateuser.getIdentity(), secretAES);
        Token token = Token.toToken(back);

        Login login = root.getLogin(); // Login ist aus der config.xml
        if (updateProfil.isPasswordWritable() || updateProfil.isMailWritable()) {
          if (updateProfil.isPasswordWritable()) {
            login.setPassword(token.getPassword());
          }
          else {
            login.setPassword(null);
          }
          if (updateProfil.isMailWritable()) {
            login.setEmail(token.getEMail());
            login.setUserid(null);
          }
          else {
            login.setUserid(null);
            login.setEmail(null);
          }
        }
        else {
          login.setPassword(null);
          login.setUserid(null);
          login.setEmail(null);
        }
        EventQueue.invokeLater(() -> {
          JOptionPane.showMessageDialog(
              updateProfil, profilGespeichert.toString(), kontodaten.toString(),
              JOptionPane.INFORMATION_MESSAGE
          );
          for (JChatFrame chatframe : chatVector) {
            chatframe.setInputBackground(updateuser.getNickname(), updateuser.getBackgroundColor());
            chatframe.setInputForeground(updateuser.getNickname(), updateuser.getForegroundColor());
            chatframe.setUpdateNickname(updateuser.getNickname());
          }
          if (telefonTeilnehmenFrame != null) {
            telefonTeilnehmenFrame.setNickname(userdata.getNickname());
          }
          // Farbanpassungen Remoterechner
          historyMessageAfterUpdateUser();

        });
      }
      else if (updateuser.getHeader() == HEADER.ERROR) {
        EventQueue.invokeLater(
            () -> JOptionPane.showMessageDialog(
                JChat.this, updateuser.getText(), kontodaten.toString(), JOptionPane.ERROR_MESSAGE
            )
        );
      }
    }
    else if (message instanceof PROYECTORCLOSING projektor) {
      switch(projektor.getHeader()) {
        case CONFIRM:
          break;
        case ERROR:
          break;
        case REQUEST:
          EventQueue.invokeLater(() -> {
            removeWM(projektorframe);
            frameVector.remove(projektorframe);
            projektorframe.removeAllListener();
            babelfish.remove(projektorframe);
            projektorframe.closeWindow();
          });
          PROYECTORCLOSING response = new PROYECTORCLOSING();
          response.setCommand(Command.PROYECTORCLOSING);
          response.setHeader(RESPONSE);
          response.setDataset(Protocol.DATASET);
          response.setPseudoid(userdata.getPseudoid());
          executorChat.execute(() -> wsClient.sendMessage(response));
          break;
        case RESPONSE:
          break;
        default:
          break;
      }
    }
    else if (message instanceof IMAGE image) {
//      log.info("<----- " + image.toString());
      switch(image.getHeader()) {
        case CONFIRM:
          break;
        case ERROR:
          Runnable runnable = () -> {
            String text;
            switch(image.getMultilingualkey()) {
              case SERVER_MUSS_NOCH_SEINEN_PROJEKTOR_AUSSCHALTEN:
                text = projektorAusschalten.toString().replace("XXX", image.getReceiverNickname());
                break;
              case SERVER_LEHNT_UEBERTRAGUNGEN_AB:
                text = lehntUebertragungenAb.toString().replace("XXX", image.getReceiverNickname());
                break;
              case SERVER_DER_EMPFAENGER_IST_VERSCHWUNDEN:
                text = empfaengerIstVerschwunden.toString().replace("XXX", image.getReceiverNickname());
                break;
              case SERVER_VIDEO_RUNNING:
                text = videoRunning.toString().replace("XXX", "<b>" + image.getReceiverNickname() + "</b>");
                break;
              case STRING_IST_IN_EINER_KONFERENZ:
                text = "<b>" + image.getReceiverNickname() + "</b> " + istInEinerKonferenz.toString();
                break;
              case SERVER_FORWARDING_IMAGE:
                text = forwardingImage.toString();
                break;
              default:
                throw new UnsupportedOperationException("IMAGE|ERROR");
            }
            JOptionPane.showMessageDialog(
                JChat.this, "<html>" + text + "</html>", itemTeilenframe.getText(),
                JOptionPane.INFORMATION_MESSAGE
            );
          };
          EventQueue.invokeLater(runnable);
          break;
        case REQUEST:
          // RECEIVER
          IMAGE response = new IMAGE();
          response.setCommand(Command.IMAGE);
          response.setHeader(RESPONSE);
          response.setDataset(Protocol.DATASET);
          response.setSenderPseudoid(image.getSenderPseudoid());
          response.setSenderNickname(image.getSenderNickname());
          response.setReceiverNickname(image.getReceiverNickname());
          executorChat.execute(() -> wsClient.sendMessage(response));
          // ich bin in Chat-Thread
          EventQueue.invokeLater(() -> {

            projektorframe = new ProjektorInternalFrame();
            windowAnrufen.addWindowAnrufenListener(projektorframe);
            anrufenEvent(projektorframe);
            Rectangle localRectangle = getBounds();
            projektorframe.setLocation(
                (localRectangle.width - ProjektorInternalFrame.SCREEN_WIDTH) / 2 + 52,
                (localRectangle.height - ProjektorInternalFrame.SCREEN_HEIGHT) / 4 + 52
            );
            desktop.add(projektorframe, JLayeredPane.DEFAULT_LAYER, PLAYER_LAYER);
            babelfish.add(projektorframe);
            projektorframe.setLanguage(ISO639.fromValue(rootLanguage.value()));
            setBackgroundTreeview(helado, projektorframe);
            projektorframe.show();
            try {
              projektorframe.setMaximum(true);
            }
            catch (PropertyVetoException e) {
              log.info(e.getMessage(), e);
            }
            projektorframe.setHorizontalDivider(1d);
            frameVector.addElement(projektorframe);
            insertWM(projektorframe, projektorframe.getFrameTitleId());
            projektorframe.addProjektorListener(projektoreventEvent -> {
              Control value = (Control) projektoreventEvent.getNewValue();
              switch(value) {
                case CLOSE:
                  removeWM(projektorframe);
                  frameVector.remove(projektorframe);
                  projektorframe.removeAllListener();
                  babelfish.remove(projektorframe);
                  windowAnrufen.removeWindowAnrufenListener(projektorframe);

                  // Projektor close senden
                  BEAMEROFF beamer = new BEAMEROFF();
                  beamer.setCommand(Command.BEAMEROFF);
                  beamer.setHeader(REQUEST);
                  beamer.setDataset(Protocol.DATASET);
                  beamer.setPseudoid(userdata.getPseudoid());
//                  log.info("-----> " + beamer.toString());
                  executorChat.execute(() -> {
                    wsClient.sendMessage(beamer);
                  });
                  break;
                case SELECTED:
                  selectFrame(projektoreventEvent.getPropertyName());
                  break;
                default:
                  throw new UnsupportedOperationException("unbekannter Fall");
              }
            });
          });
          // Projektor öffnen
          // onProject asynchron senden
          Thread.startVirtualThread(() -> switchProjektor(userdata.getPseudoid()));
          // auf Empfang gehen
          break;
        case RESPONSE:
          // ich bin der Server
          teilenframe.startStop(true);
          // Das Aufzeichnen von Screenshots beginnt
          // 1:1 Übertragung, keine Videokonferenz
          // TODO

          ImageCaster.getInstance().recordScreenshots(image.getSenderPseudoid(), image.getConferencekey());
          break;
        default:
          break;
      }
    }
    else if (message instanceof USRLOGIN userlogin) {
      switch(userlogin.getHeader()) {
        case CONFIRM:
//          log.info("<----- " + userlogin.toString());
          String decrypt = Crypto.decryptAES(userlogin.getIdentity(), secretAES);
          Token token = Token.toToken(decrypt);
          userdata = new Userdata();
          userdata.setBackgroundColor(userlogin.getBackgroundColor());
          userdata.setForegroundColor(userlogin.getForegroundColor());
          userdata.setVolume(userlogin.getVolume());
          userdata.setOncall(userlogin.getOncall());
          userdata.setNickname(userlogin.getNickname());
          userdata.setWebsocketSession(userlogin.getSession());
          userdata.setMail(token.getEMail());
          userdata.setPseudoid(token.getPseudoid());
          // Pseudopasswort setzen
          userdata.setPPassword(token.getPPassword());

//          log.info("token {}", token.toString());
//          log.info("userdata {}", userdata.toString());

          // userdata.setPassword(token.getPassword()); // mit AES verschlüsseln
          // Wenn das Updatefenster aufgerufen wird, dann mit AES entschlüsseln

          userdata.setPassword(Crypto.encryptAES(token.getPassword(), secretAES));

          telefonateAblehnen.setSelected(userdata.isOncall());
          // Umstellen Multipart, weil sonst das Passwort geloggt wird

          // you have logged in successfully
          // UPDATE UDP-PORT, weiul er sofort angerufen werden kann

          UPDATEPHONE phone = new UPDATEPHONE();
          phone.setCommand(Command.UPDATEPHONE);
          phone.setHeader(REQUEST);
          phone.setDataset(Protocol.DATASET);
          phone.setPseudoid(userdata.getPseudoid());
          phone.setOnBusy(false);
          executorChat.execute(() -> wsClient.sendMessage(phone));

          // Ist das Verzeichnis vorhanden, dann übernehmen
          // readData Telefonvolume, Telefonport, Sprachguete, Iptvvolume

          boolean isAdmin = isAdministrator(userdata.getPseudoid(), userdata.getPPassword(), drift);
          TransferUser result = readUserdataSyncHeader(
              userdata.getPseudoid(), userdata.getPPassword(), drift
          );
          restrictionOn = result.getChatdenied();
          playervolume = result.getPlayervolume();
          voiceQuality = result.getTelefonpuffer();
          telefonLautsprecher = result.getSpeaker();
          telefonMikrofon = result.getMikrofon();
          onrekorder = result.getOnrekord();
          iptvvolume = result.getIptvvolume();
          chattooltip = result.getChattooltip();
          language = result.getLanguage() == null ? Language.de : result.getLanguage();

          // durch einen Rechnerwechesel können die Verzeichnisse nicht mehr stimmen
          chatDownloadDir = result.getChatdownloaddir();
          if (chatDownloadDir == null || !Files.exists(Paths.get(chatDownloadDir))) {
            chatDownloadDir = System.getProperty("user.home");
          }
          if (chatUploadDir == null || !Files.exists(Paths.get(chatUploadDir))) {
            chatUploadDir = System.getProperty("user.home");
          }

          itemChatanfragenAblehnen.setSelected(restrictionOn);
          initPhone(result);

          // TransferConfig muss bereit sein
          transferConfig = readFiletransferConfig(userdata.getPseudoid());
          initFiletransferService(root, transferConfig);
          executorWatcher.scheduleAtFixedRate(watcher.task(), 12, 12, TimeUnit.SECONDS);

          helado = result.getHelado();
          // Aufruf ist synchron
          EventQueue.invokeLater(() -> {
            switchLanguage(ISO639.fromValue(language.toString()));
            switch(result.getHelado()) {
              case Arando:
                switchColorTheme(Sorte.BLAUBEERE);
                break;
              case Fresa:
                switchColorTheme(Sorte.ERDBEERE);
                break;
              case Moca:
                switchColorTheme(Sorte.MOKKA);
                break;
              case Vainilla:
                switchColorTheme(Sorte.VANILLE);
                break;
              case Yogur:
                switchColorTheme(Sorte.JOGHURT);
                break;
              case Limón:
                switchColorTheme(Sorte.ZITRONE);
                break;
              default:
                throw new UnsupportedOperationException(
                    result.getHelado().toString() + " - diese Eissorte wird nicht unterstützt"
                );
            }
            if (isAdmin) {
              // menubar.remove(menuAdministrator);
              if (firstInvoke) {
                firstInvoke = false;
                admin();
              }
            }
            else {
              menubar.remove(menuAdministrator);
            }
            switchOnMenu(true);
            MultilingualLabel label = new MultilingualLabel(
                KEY.LABEL_ERFOLGREICH_ANGEMELDET, ISO639.fromValue(rootLanguage.value())
            );
            Timer timer = new Timer(4000, event -> {
              SwingUtilities.getWindowAncestor(label).dispose();
            });
            timer.setRepeats(false);
            timer.start();
            JOptionPane
                .showMessageDialog(JChat.this, label, jLogon.getTitle(), JOptionPane.INFORMATION_MESSAGE);
            timer.stop();
          });
          Thread.startVirtualThread(this::updateMessage);
          break;
        case ERROR:
          EventQueue.invokeLater(() -> {

            MultilingualLabel label;
            if (userlogin.getMultilingualkey() == KEY.STRING_KONTO_DEAKTIVIERT) {
              label = new MultilingualLabel(
                  KEY.STRING_KONTO_DEAKTIVIERT, ISO639.fromValue(rootLanguage.value())
              );
            }
            else {
              label = new MultilingualLabel(
                  KEY.LABEL_ANMELDUNG_FEHLGESCHLAGEN, ISO639.fromValue(rootLanguage.value())
              );
            }
            Timer timer = new Timer(5000, event -> {
              SwingUtilities.getWindowAncestor(label).dispose();
            });
            timer.setRepeats(false);
            timer.start();

            JOptionPane.showMessageDialog(JChat.this, label, jLogon.getTitle(), JOptionPane.ERROR_MESSAGE);
            timer.stop();

            itemAnmelden.setEnabled(true);
            itemRegistrieren.setEnabled(true);
            itemHochladenRunterladen.setEnabled(false);
            itemTelefonEinrichten.setEnabled(false);
            telefonateAblehnen.setEnabled(false);
          });
          break;
        case REQUEST:
          break;
        case RESPONSE:
          break;
        default:
          break;

      }
    }
    switch(command) {
      case BEAMEROFF:
        BEAMEROFF beamer = (BEAMEROFF) message;
//        log.info("<----- " + beamer.toString());
        switch(beamer.getHeader()) {
          case CONFIRM:
            break;
          case ERROR:
            break;
          case REQUEST:
            ImageCaster.getInstance().stopRecording();
            BEAMEROFF response = new BEAMEROFF();
            response.setCommand(Command.BEAMEROFF);
            response.setHeader(RESPONSE);
            response.setDataset(Protocol.DATASET);
            response.setPseudoid(beamer.getPseudoid());
//            log.info("-----> {}", response.toString());
            wsClient.sendMessage(response); // ich bin in Chat-Thread
            EventQueue.invokeLater(() -> {
              teilenframe.startStop(false);
            });
            break;
          case RESPONSE:
            break;
          default:
            break;
        }
        break;
      case BREAKROOMS:
        break;
      case CALLPRIVATECHAT:
        CALLPRIVATECHAT call = (CALLPRIVATECHAT) message;

        log.info("<----- " + call.toString());
        if (RESPONSE == call.getHeader()) {
          // Meldung ausgeben, dass die Gegenstelle keine Anrufe entgegennimmt
          Runnable runnable = () -> {
            if (personChat != null) {
              String text = serverLehntChatanfragenAb.toString().replace("XXX", call.getRemoteNickname());
              personChat.setErrorMessage(text);
            }
            else {
              String text = "<html>"
                  + serverLehntChatanfragenAb.toString()
                      .replace("XXX", "<b>" + call.getRemoteNickname() + "</b>")
                  + "</html>";
              JOptionPane.showMessageDialog(
                  JChat.this, text, chatAbgelehnt.toString(), JOptionPane.INFORMATION_MESSAGE
              );
            }
          };
          EventQueue.invokeLater(runnable);
        }
        else if (CONFIRM == call.getHeader()) {
          EventQueue.invokeLater(() -> {
            // ist der Launcher geöffnet

            openLauncherIf(frameVector);

            chatFrame = new JChatFrame(call.getRemoteNickname());
            windowAnrufen.addWindowAnrufenListener(chatFrame);
            anrufenEvent(chatFrame);

            chatFrame.setLanguage(ISO639.fromValue(rootLanguage.value()));
            chatFrame.setDownloadDir(chatDownloadDir);
            chatFrame.chatTooltip(chattooltip);
            chatFrame.setFrameIcon(new ImageIcon(resource.getResource(Resource.PRIVAT_26x26)));
            chatFrame.setPrivate(call.getRemotePseudoid());
            chatFrame.setTickerBackground(root.getEis());
            babelfish.add(chatFrame);
            chatFrame.setInputForeground(userdata.getForegroundColor());
            chatFrame.setInputBackground(userdata.getBackgroundColor());
            desktop.add(chatFrame, JLayeredPane.DEFAULT_LAYER, CHAT_LAYER);
            chatVector.addElement(chatFrame);
            chatFrame.addChatListener(createPrivatechatListener(call.getRemoteNickname(), chatFrame));
            chatFrame.setSize(1185, 510);
            setBackgroundTreeview(helado, chatFrame);
            chatFrame.show();
            frameVector.addElement(chatFrame);
            insertWM(chatFrame, chatFrame.getFrameTitleId());
            if (personChat != null) personChat.doDefaultCloseAction(); // muss hier stehen wegen
            // doubleClick 1:1 Chat
          });

        }
        else if (HEADER.ERROR == call.getHeader()) {

          EventQueue.invokeLater(() -> {

            // 1:1 Chat schon vorhanden
            if (personChat != null) {
              switch(call.getMultilingualkey()) {
                case STRING_EINSAME_SEELEN:
                  personChat.setErrorMessage(einsameSeelen.toString());
                  break;
                case SERVER_DER_CHAT_LAEUFT_BEREITS:
                  personChat.setErrorMessage(chatIsRunning.toString());
                  break;
                case SERVER_USER_IST_OFFLINE:
                  // TODO Text wird noch inHTML angezeigt
                  personChat.setErrorMessage(call.getErrorMessage());
                  break;
                default:
                  personChat.setErrorMessage(call.getErrorMessage());
                  break;
              }
            }
            else {
              JOptionPane.showMessageDialog(
                  JChat.this, call.getErrorMessage(), "1:1 Chat", JOptionPane.INFORMATION_MESSAGE
              );
            }

          });
        }
        break;
      case CALLREMOTEUSER:
        break;
      case CANDIDATETOPICMEMBER:
        CANDIDATETOPICMEMBER member = (CANDIDATETOPICMEMBER) message;
        if (RESPONSE == member.getHeader()) {
          final ChatUser[] candidate = member.getChatUser();
          switch(member.getRoomtype()) {
            case PAUSENRAUM:
              break;
            case BESPRECHUNGSRAUM:
              EventQueue.invokeLater(() -> besprechungsraum.setCandidatemembers(candidate));
              break;
            case FORUM:
              break;
            case GRUPPENRAUM:
              EventQueue.invokeLater(() -> gruppenraum.setCandidatemembers(candidate));
              break;
            default:
              break;
          }
        }
        break;
      case CHANGETOPICMEMBER:
        CHANGETOPICMEMBER changeTopicMember = (CHANGETOPICMEMBER) message;
        if (RESPONSE == changeTopicMember.getHeader()) {
          switch(changeTopicMember.getRoomtype()) {
            case BESPRECHUNGSRAUM:
              EventQueue.invokeLater(() -> {
                JOptionPane.showMessageDialog(
                    JChat.this, mitgliederlisteAngepasst.toString(), besprechungsRauemeVerwalten.toString(),
                    JOptionPane.INFORMATION_MESSAGE
                );
                besprechungsraum.setTopicmembers(changeTopicMember.getChatUser());
              });
              break;
            case PAUSENRAUM:
              break;
            case FORUM:
              break;
            case GRUPPENRAUM:
              EventQueue.invokeLater(() -> {
                JOptionPane.showMessageDialog(
                    JChat.this, freundeslisteAngepasst.toString(), gruppenraumEinrichten.toString(),
                    JOptionPane.INFORMATION_MESSAGE
                );
              });
              break;
            default:
              break;
          }

        }
        else if (HEADER.ERROR == changeTopicMember.getHeader()) {
          final String error = changeTopicMember.getErrorMessage();
          EventQueue.invokeLater(
              () -> JOptionPane.showMessageDialog(
                  JChat.this, "<html>" + "<head/>" + "<body>" + error + "</body>" + "</html>",
                  besprechungsRauemeVerwalten.toString(), JOptionPane.ERROR_MESSAGE
              )
          );
        }
        break;
      case CHATMESSAGE:
        break;
      case CHATONLINELIST:
        break;
      case CHATUSERLIST:
        break;
      case CONFERENCE:
        CONFERENCE conference = (CONFERENCE) message;
//        log.info("<----- {}", conference.toString());
        switch(conference.getHeader()) {
          case CONFIRM:
            break;
          case ERROR:
            break;
          case REQUEST:
            break;
          case RESPONSE:
            // habe ich mich selbst angefunkt?, shutdownTelefonkonferenz();

            // Knöpfe einfärben, muss der AWT-Thread machen
            EventQueue.invokeLater(() -> {
              if (telefonTeilnehmenFrame == null || telefonTeilnehmenFrame.isClosed()) return;

              telefonTeilnehmenFrame
                  .setKonferenzraumuser(conference.getKonferenzraumUser(), userdata.getWebsocketSession());
              telefonTeilnehmenFrame
                  .setKnopfleiste(userdata.getWebsocketSession(), conference.getKonferenzraumUser());

              if (conference.getPseudoid().equals(userdata.getPseudoid())) {
                if (!conference.isOffhook()) {
                  // Hörer aufgelegt in der Conference
                  micTerminate();
                  speakerTerminate();
                  // Framevektor durchlaufen
                  Optional<WM> first = frameVector.stream()
                      .filter(wm -> wm.getType() == Frames.TELKOPROJEKTOR).findFirst(); // kann nur einen
                                                                                        // geben

                  if (first.isPresent()) {
                    // Projektor schließen
                    telkoprojektorframe = (TelkoProjektorInternalFrame) first.get();
                    telkoprojektorframe.closeWindow();
                  }
                }
                else {
                  // Hörer nicht aufgelegt
                  // lies alle Konferenzraumuser aus und passe die Speakerlist an
                  KonferenzraumUser[] alleKonfernzuser = conference.getKonferenzraumUser();

                  // finde alle anwesenden Konferenzuer;
                  List<KonferenzraumUser> alleAnwesend = Stream.of(alleKonfernzuser)
                      .filter(KonferenzraumUser::isAnwesend).collect(Collectors.toList());

                  // ich bin neu hinzugekommen
                  // öffne für jeden vorhanden Konferenzteilnehmer eine Telefonleitung
                  // für mich selber darf keine Telefonleitung geöffnet werden

                  alleAnwesend.forEach(anwesend -> {
                    if (anwesend.getWebsocketSession().equals(userdata.getWebsocketSession())) {
                      return;
                    }

                    // Bugfix verhindert doppelte Einträge
                    if (
                      conferenceSpeakerlist.stream().anyMatch(
                          speaker -> speaker.getWebsocketSession().equals(anwesend.getWebsocketSession())
                      )
                    ) {
                      return;
                    }

                    try {
                      // das hier sind andere Konferenzteilnehmer, aber nicht ich
                      ConferenceSpeaker neu = createConferenceSpeaker(
                          userdata.getVolume(), anwesend.getWebsocketSession()
                      );
                      conferenceSpeakerlist.add(neu);
//                      log.info("neue Leitung für {}", anwesend.getWebsocketSession());
                    }
                    catch (TelefonformatException | MixerNotFoundException | LineUnavailableException e) {
                      JOptionPane.showMessageDialog(
                          JChat.this, e.getMessage(), menuPhone.toString(), JOptionPane.INFORMATION_MESSAGE
                      );
                    }
                  });
                }
              }
              else {// andere Anwender sind in die Telko eingetreten oder ausgetreten

                // lies alle Konferenzraumuser aus und passe die Speakerlist an
                KonferenzraumUser[] alleKonfernzuser = conference.getKonferenzraumUser();

                // finde alle nicht anwesenden Konferenzuer;
                List<KonferenzraumUser> alleNichtAnwesend = Stream.of(alleKonfernzuser)
                    .filter(konferenzuser -> !konferenzuser.isAnwesend()).collect(Collectors.toList());

                // finde alle anwesenden Konferenzuer;
                List<KonferenzraumUser> alleAnwesend = Stream.of(alleKonfernzuser)
                    .filter(KonferenzraumUser::isAnwesend).collect(Collectors.toList());

                // alleNichtAnwesend aus der speakerlist Streichen
                for (KonferenzraumUser nichtVorhanden : alleNichtAnwesend) {
                  for (ConferenceSpeaker conferenceSpeaker : conferenceSpeakerlist) {
                    if (
                      conferenceSpeaker.getWebsocketSession().equals(nichtVorhanden.getWebsocketSession())
                    ) {
                      // streichen
                      conferenceSpeaker.release();
                      conferenceSpeakerlist.remove(conferenceSpeaker);
                      break;
                    }
                  }
                }

                // öffne für jeden vorhandenen Konferenzteilnehmer eine Telefonleitung
                // für mich selber darf keine Telefonleitung geöffnet werden
                // für bereits vorhandene Anwender darf auch keine neue Leitung geöffnet werden
                alleAnwesend.forEach(anwesend -> {
                  if (anwesend.getWebsocketSession().equals(userdata.getWebsocketSession())) return;
                  if (
                    conferenceSpeakerlist.stream().anyMatch(
                        conferenceSpeaker -> conferenceSpeaker.getWebsocketSession()
                            .equals(anwesend.getWebsocketSession())
                    )
                  ) return;
                  try {
//                    log.info("neue Leitung für {}", anwesend.getWebsocketSession());
                    ConferenceSpeaker neu = createConferenceSpeaker(
                        userdata.getVolume(), anwesend.getWebsocketSession()
                    );
                    conferenceSpeakerlist.add(neu);
                  }
                  catch (TelefonformatException | MixerNotFoundException | LineUnavailableException e) {
                    JOptionPane.showMessageDialog(
                        JChat.this, e.getMessage(), menuPhone.getText(), JOptionPane.INFORMATION_MESSAGE
                    );
                  }
                });

              }

//              log.info("ich bin=" + userdata.getSession());
//              log.info("--- komplette Liste ---");
//              conferenceSpeakerlist.forEach(speaker -> {
//                log.info(speaker.getSession());
//              });
//              
//              
//              log.info("--");

            });

            break;
          default:
            break;
        }
        break;
      case CONFERENCEVIDEO:
        CONFERENCEVIDEO conferencevideo = (CONFERENCEVIDEO) message;
//        log.info("<----- " + conferencevideo.toString());
        switch(conferencevideo.getHeader()) {
          case CONFIRM:
            break;
          case ERROR:
            EventQueue.invokeLater(() -> {

              if (!conferencevideo.isShared()) {
                ImageCaster.getInstance().stopRecording();
                // löst ein CONFERENCEVIDEO aus
                telefonTeilnehmenFrame.stopAnimation(); // SHARING darf nicht gesendet werden, wegen error
              }

              switch(conferencevideo.getMultilingualkey()) {
                case SERVER_DRITTE_PERSON:
                  break;
                default:
                  throw new UnsupportedOperationException("CONFERENCEVIDEO|ERROR");
              }
              JOptionPane.showMessageDialog(
                  JChat.this, drittePerson.toString(), itemTeilenframe.getText(),
                  JOptionPane.INFORMATION_MESSAGE
              );
            });
            break;
          case REQUEST:
//            log.info("isShared {}", conferencevideo.isShared());
            if (conferencevideo.isShared()) {
              // Projektor öffnen
              Thread.startVirtualThread(() -> switchProjektor(userdata.getPseudoid()));

              EventQueue.invokeLater(() -> {

                telkoprojektorframe = new TelkoProjektorInternalFrame();
                telkoprojektorframe.setNickname(conferencevideo.getNickname()); // vom Sender
                windowAnrufen.addWindowAnrufenListener(telkoprojektorframe);
                anrufenEvent(telkoprojektorframe);
                Rectangle localRectangle = getBounds();
                telkoprojektorframe.setLocation(
                    (localRectangle.width - ProjektorInternalFrame.SCREEN_WIDTH) / 2 + 52,
                    (localRectangle.height - ProjektorInternalFrame.SCREEN_HEIGHT) / 4 + 52
                );
                desktop.add(telkoprojektorframe, JLayeredPane.DEFAULT_LAYER, PLAYER_LAYER);
                babelfish.add(telkoprojektorframe);
                telkoprojektorframe.setLanguage(ISO639.fromValue(rootLanguage.value()));
                setBackgroundTreeview(helado, telkoprojektorframe);
                telkoprojektorframe.show();
                try {
                  telkoprojektorframe.setMaximum(true);
                }
                catch (PropertyVetoException e) {
                  log.info(e.getMessage(), e);
                }
                telkoprojektorframe.setHorizontalDivider(1d);
                frameVector.addElement(telkoprojektorframe);
                insertWM(telkoprojektorframe, telkoprojektorframe.getFrameTitleId());
                telkoprojektorframe.addProjektorListener(projektoreventEvent -> {
                  Control value = (Control) projektoreventEvent.getNewValue();
                  switch(value) {
                    case CLOSE:
                      removeWM(telkoprojektorframe);
                      frameVector.remove(telkoprojektorframe);
                      telkoprojektorframe.removeAllListener();
                      babelfish.remove(telkoprojektorframe);
                      windowAnrufen.removeWindowAnrufenListener(telkoprojektorframe);
                      Thread.startVirtualThread(() -> switchProjektor(userdata.getPseudoid(), false));
                      break;
                    case SELECTED:
                      selectFrame(projektoreventEvent.getPropertyName());
                      break;
                    default:
                      throw new UnsupportedOperationException("unbekannter Fall");
                  }
                });
                telkoprojektorframe.toFront();
              });
            }
            else {
              // Framevektor durchlaufen
              Optional<WM> first = frameVector.stream().filter(wm -> wm.getType() == Frames.TELKOPROJEKTOR)
                  .findFirst(); // kann nur einen geben
              if (first.isPresent()) {
                telkoprojektorframe = (TelkoProjektorInternalFrame) first.get();
                telkoprojektorframe.closeWindow();
              }
            }
            break;
          case RESPONSE:
            break;
          default:
            break;
        }
        break;
      case CONFERENCEMUTE:
        CONFERENCEMUTE conferencemute = (CONFERENCEMUTE) message;
//        log.info("<----- {}", conferencemute.toString());
        switch(conferencemute.getHeader()) {
          case CONFIRM:
            break;
          case ERROR:
            break;
          case REQUEST:
            // organisator wird ersetzt durch conferencekey

            Thread.startVirtualThread(
                () -> readPresence(
                    conferencemute.getKonferenzraum(), telefonTeilnehmenFrame.getConferenceKey(),
                    userdata.getWebsocketSession()
                )
            );

            break;
          case RESPONSE:
            break;
          default:
            break;
        }
        break;
      case CREATETEMPROOM:
        break;
      case DELETEROOM:
        break;
      case DELETEUPLOADFILES:
        break;
      case DELETE_USER:
        break;
      case DIAL:
        DIAL dial = (DIAL) message;
//        log.info("<----- " + dial.toString());
        if (RESPONSE == dial.getHeader()) {
          EventQueue.invokeLater(() -> {
            callerframe.waehlenAbgeschlossen();
            // ich warte auf die Bestätigung der Gegenseite
            // oder ich lege selber auf.
            // Future 10 Sekunden?
            // callerFrame Fenster Titel ändern
            removeWM(callerframe);
//            KEY.STRING_ANRUFER
            // Achtung " - " + dial.getReceiverNickname() muss mit ManagerInternalFrame
            // übereinstimmen
            callerframe.setFrameTitleId(itemAnrufen.getText() + " - " + dial.getReceiverNickname());
            insertWM(callerframe, callerframe.getFrameTitleId());
            windowAnrufen.setTriggerNickname(dial.getReceiverNickname());
            WindowAnrufenEvent windowAnrufenEvent = new WindowAnrufenEvent(callerframe);
            windowAnrufenEvent.setNickname(dial.getReceiverNickname());
            windowAnrufenEvent.setCode(ISO639.fromValue(rootLanguage.value()));
            windowAnrufen.trigger(windowAnrufenEvent);
          });

          // ich habe alle Daten für den Verbindungsaufbau erhalten
          receiverPseudoid.set(dial.getReceiverPseudoid());
          INCOMINGCALL incomingcall = new INCOMINGCALL();
          incomingcall.setCommand(Command.INCOMINGCALL);
          incomingcall.setHeader(REQUEST);
          incomingcall.setDataset(Protocol.DATASET);
          incomingcall.setCallerNickname(dial.getCallerNickname());
          incomingcall.setCallerPseudoid(dial.getCallerPseudoid());
          incomingcall.setCallerVoice(dial.getCallerVoice());
          incomingcall.setCallerSession(dial.getCallerSession());
          incomingcall.setReceiverPseudoid(dial.getReceiverPseudoid());
          incomingcall.setReceiverNickname(dial.getReceiverNickname());
          incomingcall.setReceiverSession(dial.getReceiverSession());
          incomingcall.setConferencekey(UUID.randomUUID().toString());

//          log.info("-----> {}", incomingcall.toString());

          executorChat.execute(() -> wsClient.sendMessage(incomingcall));
        }
        else if (HEADER.ERROR == dial.getHeader()) {
          try {
            SoundClip.playBusytone();
          }
          catch (IOException | UnsupportedAudioFileException | LineUnavailableException e) {
            log.error(e.getMessage(), e.getCause());
          }
          EventQueue.invokeLater(() -> {
            switch(dial.getMultilingualkey()) {
              case STRING_NIMMT_KEINE_ANRUFE_ENTGEGEN:
                JOptionPane.showMessageDialog(
                    JChat.this,
                    "<html><b>" + dial.getReceiverNickname()
                        + "</b>"
                        + nimmtKeineAnrufeEntgegen.toString()
                        + "</html>",
                    telefonieren.toString(), JOptionPane.INFORMATION_MESSAGE
                );
                break;
              case STRING_IST_IN_EINER_KONFERENZ:
                JOptionPane.showMessageDialog(
                    JChat.this,
                    "<html><b>" + dial.getReceiverNickname()
                        + "</b>"
                        + istInEinerKonferenz.toString()
                        + "</html>",
                    telefonieren.toString(), JOptionPane.INFORMATION_MESSAGE
                );
                break;
              case STRING_IST_AM_TELEFONIEREN:
                JOptionPane.showMessageDialog(
                    JChat.this,
                    "<html><b>" + dial.getReceiverNickname()
                        + "</b>"
                        + istAmTelefonieren.toString()
                        + "</html>",
                    telefonieren.toString(), JOptionPane.INFORMATION_MESSAGE
                );
                break;
              case STRING_IST_NICHT_ERREICHBAR:
                JOptionPane.showMessageDialog(
                    JChat.this,
                    "<html><b>" + dial.getReceiverNickname()
                        + "</b>"
                        + istNichtErreichbar.toString()
                        + "</html>",
                    telefonieren.toString(), JOptionPane.INFORMATION_MESSAGE
                );
                break;
              default:
                log.warn("Dieser Fall wurde nicht behandelt, - " + dial.getErrorMessage());
                break;
            }
            callerframe.hoererAuflegenEinschalten();
          });
          UPDATEPHONE phone = new UPDATEPHONE();
          phone.setCommand(Command.UPDATEPHONE);
          phone.setHeader(REQUEST);
          phone.setDataset(Protocol.DATASET);
          phone.setOnBusy(false);
          phone.setPseudoid(userdata.getPseudoid());
          executorChat.execute(() -> wsClient.sendMessage(phone));
        }

        break;
      case DOWNLOAD:
        break;
      case ENTERROOM:
        break;
      case FILETYPES:
        break;
      case FTSDOWNLOAD:
        break;
      case HISTORYMESSAGE:
        break;
      case IMAGE:
        break;
      case INCOMINGCALL:
        break;
      case KEEPALIVE:
        break;
      case LEAVEPRIVATECHAT:
        break;
      case LEAVEROOM:
        break;
      case MICROERROR:
        break;
      case NEWIMAGE:
        if (!isOccupied.compareAndSet(false, true)) return; // belegt
        if (message.getHeader() == REQUEST) {
          NEWIMAGE newimage = (NEWIMAGE) message;

          Thread.startVirtualThread(() -> {
            try {
              // 1. I/O-Task: Bild blockierend laden (effizient über Virtual Thread)
              Optional<BufferedImage> pictureWithoutMouseOpt = loadScreenshot(newimage.getPseudoid());

              if (pictureWithoutMouseOpt.isEmpty()) {
                return;
              }

              // Instanzvariable für den NEWMOUSEPOINTER-Case aktualisieren
              pictureWithoutMouse = pictureWithoutMouseOpt;
              BufferedImage picture = pictureWithoutMouseOpt.get();

              // 2. CPU-Task: Aufwendiges Zeichnen im HINTERGRUND (Virtual Thread) erledigen!
              BufferedImage copyMouseLocal = new BufferedImage(
                  picture.getWidth(), picture.getHeight(), picture.getType()
              );
              Graphics2D g2d = copyMouseLocal.createGraphics();
              g2d.drawImage(picture, 0, 0, null);

              if (mausWrapper != null) {
                g2d.drawImage(duke, mausWrapper.getX(), mausWrapper.getY(), null);
              }
              g2d.dispose();

              // 3. UI-Task: Nur die fertige Grafik an AWT übergeben
              EventQueue.invokeLater(() -> {
                showScreenshot(copyMouseLocal);
              });

            }
            catch (Exception e) {
              log.error("Fehler beim Verarbeiten des Screenshots", e);
            }
            finally {
              // Garantiert das Freigeben, egal was passiert
              isOccupied.set(false);
            }

          });

        }
        break;
      case NEWMOUSEPOINTER:
        // Kein null-Check mehr nötig! Wir prüfen direkt, ob ein Bild existiert.
        if (pictureWithoutMouse.isEmpty()) return;
        if (!isOccupied.compareAndSet(false, true)) return; // belegt
        if (message.getHeader() == REQUEST) {

          NEWMOUSEPOINTER mousepointer = (NEWMOUSEPOINTER) message;
          mausWrapper = new MausWrapper(mousepointer.getX(), mousepointer.getY());
          if (pictureWithoutMouse.isEmpty()) {
            isOccupied.set(false); // frei
            return;
          }
          // Netzwerk-Thread befreien für das nächste Paket
          Thread.startVirtualThread(() -> {
            try {
              BufferedImage picture = pictureWithoutMouse.get();
              // Reine CPU-Arbeit: Lokales Image erstellen und zeichnen
              BufferedImage localCopyMouse = new BufferedImage(
                  picture.getWidth(), picture.getHeight(), picture.getType()
              );
              Graphics2D g2d = localCopyMouse.createGraphics();
              g2d.drawImage(picture, 0, 0, null);
              g2d.drawImage(duke, mausWrapper.getX(), mausWrapper.getY(), null);
              g2d.dispose();

              // Wechsel in den AWT-Thread für die Anzeige
              EventQueue.invokeLater(() -> {
                showScreenshot(localCopyMouse);
              });

            }
            catch (Exception e) {
              log.error("Fehler beim Zeichnen des Mauszeigers", e);
            }
            finally {
              // Das Freigeben MUSS hier im Thread passieren,
              // da die GUI-Aktualisierung asynchron in der Zukunft liegt.
              isOccupied.set(false); // frei
            }
          });
        }

        break;
      case ONCALL:
        break;
      case ONHOOK:
        break;
      case ONHOOKCALLER:
        break;
      case PRIVATECHATFILE:
        if (message.getHeader() == RESPONSE) {
          EventQueue.invokeLater(() -> uploadPrivateChatfile((PRIVATECHATFILE) message));
          return;
        }
      case PRIVATEMESSAGE:
        break;
      case PROTECTEDTEMPROOM:
        break;
      case PROYECTORCLOSING:
        break;
      case READROOMS:
        READROOMS readrooms = (READROOMS) message;
        if (RESPONSE == readrooms.getHeader()) {
          EventQueue.invokeLater(() -> {
            switch(readrooms.getRoomtype()) {
              case BESPRECHUNGSRAUM:
                break;
              case FORUM:
                break;
              case GRUPPENRAUM:
                gruppenraum.setRooms(readrooms.getRooms());
                break;
              case PAUSENRAUM:
                break;
              default:
                break;
            }
          });
        }
        break;
      case READTOPICROOMOWNER:
        break;
      case ROOMLIST:
        ROOMLIST roomlist = (ROOMLIST) message;
        if (roomlist.getHeader() == RESPONSE && launcher != null) {
          EventQueue.invokeLater(() -> {
            launcher.setRoomlist(roomlist.getRoom());
          });
        }

        break;
      case SCREENCAST:
        break;
      case SEARCHFILES:
        break;
      case SIGNIN:
        break;
      case STOPSENDINGVIDEO:
        STOPSENDINGVIDEO stopSendingVideo = (STOPSENDINGVIDEO) message;
        log.info("<----- " + stopSendingVideo.toString());
        switch(stopSendingVideo.getHeader()) {
          case CONFIRM:
            break;
          case ERROR:
            break;
          case REQUEST:

            telefonTeilnehmenFrame.stopAnimation();
            ImageCaster.getInstance().stopRecording();

            break;
          case RESPONSE:
            break;
          default:
            break;
        }
        break;
      case TOPICMEMBER:
        TOPICMEMBER topicmember = (TOPICMEMBER) message;
//        log.info("<----- {}", topicmember.toString());

        if (RESPONSE == topicmember.getHeader()) {
          final ChatUser[] roommember = topicmember.getChatUser();
          // ROOMTYPE muss geprüft werden
          switch(topicmember.getRoomtype()) {
            case BESPRECHUNGSRAUM:
              EventQueue.invokeLater(() -> besprechungsraum.setTopicmembers(roommember));
              // Anfrage stellen
              break;
            case PAUSENRAUM:
              break;
            case FORUM:
              log.warn("TOPICMEMBER FORUM ist neu?");
              return;
            case GRUPPENRAUM:
              EventQueue.invokeLater(() -> gruppenraum.setTopicmembers(roommember));
              break;
            default:
              break;
          }
          CANDIDATETOPICMEMBER candidate = new CANDIDATETOPICMEMBER();
          candidate.setCommand(Command.CANDIDATETOPICMEMBER);
          candidate.setHeader(REQUEST);
          candidate.setDataset(Protocol.DATASET);
          candidate.setPseudoid(topicmember.getPseudoid());
          candidate.setRoom(topicmember.getRoom());
          executorChat.execute(() -> wsClient.sendMessage(candidate));
        }
        break;
      case TRANSFER:
        break;
      case UPDATEFILETYPES:
        break;
      case UPDATEPHONE:
        break;
      case UPDATEPORT:
        break;
      case UPDATEUSER:
        break;
      case UPLOADFILES:
        break;
      case USERONLINELIST:
        break;
      case USRLOGIN:
        break;
      default:
        throw new UnsupportedOperationException("Befehl wird nicht bearbeitet");

    }

  }



  /**
   * Die DVD-Einstellungen werden vorgenommen.
   *
   * @param root
   *             Wurzelknoten der Konfigurationsdatei
   */
  void initDvd(Config root) {
    Land land = root.getDvd().getLand();
    switch(land.getValue()) {
      case GERMAN:
        itemDeutsch.setSelected(true);
        break;
      case SPANISH:
        itemSpanisch.setSelected(true);
        break;
      case ENGLISH:
        itemEnglisch.setSelected(true);
        break;
      case FRENCH:
        itemFranzoesisch.setSelected(true);
        break;
      case ITALIAN:
        itemItalienisch.setSelected(true);
        break;
      case PORTUGUESE:
        itemPortugiesisch.setSelected(true);
        break;
      case RUSSIAN:
        itemRussisch.setSelected(true);
        break;
      case TURKEY:
        itemTuerkisch.setSelected(true);
        break;
      default:
        break;
    }
  }



  /**
   * Der Filetransferservice wird gestartet.
   *
   * @param root
   *             Wurzelknoten der Konfigurationsdatei
   */
  void initFiletransferService(Config root, TransferFiletransferConfig transferConfig) {
    if (root.getFileTransferService() == null) {
      FileTransferService fts = new FileTransferService();
      root.setFileTransferService(fts);
      try {
        ftserver = new FTServer(transferConfig.getPort());
      }
      catch (PortException | IOException e) {
        log.error(e.getMessage(), e.getCause());
      }
    }
    switch(transferConfig.getStarttype()) {
      case AUTOMATIC_DE:
        // Server wird bei automatic gestartet
        try {
          ftserver = new FTServer(transferConfig.getPort());
        }
        catch (PortException | IOException e) {
          log.error(e.getMessage(), e.getCause());
        }
        executorFileTransferServer.execute(ftserver);
        break;
      case AUTOMATIC_EN:
        try {
          ftserver = new FTServer(transferConfig.getPort());
        }
        catch (PortException | IOException e) {
          log.error(e.getMessage(), e.getCause());
        }
        executorFileTransferServer.execute(ftserver);
        break;
      case AUTOMATIC_ES:
        try {
          ftserver = new FTServer(transferConfig.getPort());
        }
        catch (PortException | IOException e) {
          log.error(e.getMessage(), e.getCause());
        }
        executorFileTransferServer.execute(ftserver);
        break;
      case MANUAL_DE:
        ftserver = new FTServer();
        break;
      case MANUAL_EN:
        ftserver = new FTServer();
        break;
      case MANUAL_ES:
        ftserver = new FTServer();
        break;
      default:
        break;
    }
    String downloadDir = transferConfig.getDownloadDir();
    if (downloadDir != null && Files.isDirectory(Paths.get(downloadDir))) {
      root.getFileTransferService().setDownload(downloadDir);
    }

    String uploadDir = transferConfig.getUploadDir();
    if (uploadDir != null) {
      if (Files.isDirectory(Paths.get(uploadDir))) {
        root.getFileTransferService().setUpload(uploadDir);
      }
      else {
        // wegen BLACKLIST
        // ein User könnte neu angelegt sein und erstellt als erstes eine Blacklist
        root.getFileTransferService()
            .setUpload(Environment.getInstance().getUploadDir().toAbsolutePath().toString());
      }
    }
    else {
      // wegen BLACKLIST
      root.getFileTransferService()
          .setUpload(Environment.getInstance().getUploadDir().toAbsolutePath().toString());
    }

  }



  /**
   * Die aktuelle Sprache wird aus der Konfigurationsdatei gelesen und die Sprache
   * für alle Knoten aktualisiert.
   *
   * @param root
   *             der Wurzelknoten der Konfigurationsdatei
   */
  void initLanguage(Config root) {
    if (root.getLanguage() == null) root.setLanguage(ISO6391.DE);
    rootLanguage = root.getLanguage();
    ISO639 newValue = ISO639.fromValue(rootLanguage.value());
    switchLanguage(newValue);
  }



  /**
   * Die Configeinstellung für das Modul Mediaplayer wird eingerichtet.
   *
   * @param root
   *             Wurzelknoten der Konfigurationsdatei
   */
  void initMediaplayer(Config root) {
    actionBallerina = new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent event) {

        MultilingualCheckBoxMenuItem source = (MultilingualCheckBoxMenuItem) event.getSource();
        // source ist entscheidend
        if (source.isSelected()) {
          alleHakenAusSpiellisteEntfernen();
          // log.info("aktiviere " + source.getText());
          source.setSelected(true);
          // ohne Window Sccließen und buttonClose
          removeAllPlayerFrames(Control.NULL);
          itemSpielliste.setSelected(true);
          FileNameExtensionFilter extensionsFilter = null;
          try {
            for (Directory dir : root.getMediaplayer().getDirectories().getDirectory()) {
              if (!dir.getPathToDirectory().equals(source.getText())) continue;
              List<String> extensions = dir.getExtensions();
              extensionsFilter = new FileNameExtensionFilter(
                  null, extensions.toArray(new String[extensions.size()])
              );
              break;
            }
            playlist = PlayerFilter.audiofiles(PlayerFilter.toPath(source.getText()), extensionsFilter);
            playShuffle(playlist);
          }
          catch (FilterException | NoAudiofileException | NoDirectoryException | NullPathException e) {
            log.error(e.getMessage(), e.getCause());
            itemSpielliste.setSelected(false);
            source.setSelected(false);
          }
          catch (ShuffleException e) {
            JOptionPane.showMessageDialog(
                JChat.this, "<html>" + alleMusikstuecke.toString() + "</html>", "Shuffle",
                JOptionPane.INFORMATION_MESSAGE
            );
            itemSpielliste.setSelected(false);
          }
        }
        else {
          // Alle bisherigen Songs werden unterbrochen und die Frames gelöscht
          // ohne buttonClose oder Window Schließen
          removeAllPlayerFrames(Control.NULL);
          JOptionPane.showMessageDialog(
              JChat.this, "<html>" + zufuallsmodus.toString() + "</html>", "Shuffle",
              JOptionPane.INFORMATION_MESSAGE
          );
          itemSpielliste.setSelected(false);
        }
      }

    };
    // Directories vorhanden?
    Mediaplayer mediaplayer = root.getMediaplayer();
    if (mediaplayer.getDirectories() != null) {
      menuMusikkiste.add(new JSeparator());
      Directories directories = mediaplayer.getDirectories();
      for (Directory dir : directories.getDirectory()) {
        MultilingualCheckBoxMenuItem item = new MultilingualCheckBoxMenuItem(dir.getPathToDirectory());
        item.addActionListener(actionBallerina);
        menuMusikkiste.add(item);
      }
    }
  }



  /**
   * Die Grundeinstellung für das Telefon. Die globalen Variablen
   * {@code telefonLautsprecher} und {@code telefonMikrofon} werden angepasst.
   *
   *
   * @param user
   *             die Einstellungen kommen aus der Datenbank
   */
  void initPhone(TransferUser user) {
    if (user.getSpeaker() == null) {
      try {
        List<Info> speaker = TelefonUtil.speakerMixer(Telefonformat.MEDIUM);
        telefonLautsprecher = speaker.get(0).getName();
      }
      catch (IndexOutOfBoundsException e) {}
    }
    if (user.getMikrofon() == null) {
      try {
        List<Info> micro = TelefonUtil.microMixer(Telefonformat.MEDIUM);
        telefonMikrofon = micro.get(0).getName();
      }
      catch (IndexOutOfBoundsException e) {}
    }
    // udpendpoint = new UDPEndpoint();
    // udpendpoint.setTelefonformat(TelefonUtil.toTelefonformat(user.getTelefonpuffer().toString()));
    // udpendpoint.setPort(user.getTelefonport());
  }



  void initStreams(Config root2) {

    if (root2.getStream() == null) {
      net.javacomm.client.config.schema.Stream stream = new net.javacomm.client.config.schema.Stream();
      Livestreams livestreams = new Livestreams();

      for (int index = 0; index < StreamFrame.SLOTS; index++) {

        Livestream livestream = new Livestream();
        livestream.setAssigned(false);

        livestreams.getLivestream().add(livestream);

      }

      stream.setLivestreams(livestreams);
      root2.setStream(stream);

    }

  }



  /**
   * Ein InternalFrame wird in den WindowManager gehängt.
   *
   * @param neuerFrame
   *                    ein InternalFrame
   * @param baumansicht
   *                    unter diesem Namen wird der Frame in der Baumansicht
   *                    angezeigt
   */
  private void insertWM(WM neuerFrame, String baumansicht) {
    if (neuerFrame == null) throw new IllegalArgumentException("paramWM is null");
    for (WM bestehendeFrames : frameVector) {
      // der neue Frame bekommt alle vorhandenen Frames

      // ist der WM JTelefonkonferenzBeitretenFrame

      neuerFrame.insertWM(bestehendeFrames);
      if (neuerFrame.getId() != bestehendeFrames.getId()) {
        // alle alten Frames bekommen den neuen Frame angehängt
        bestehendeFrames.insertWM(neuerFrame);
      }
    }
  }



  /**
   * Ist der Anwender ein Administrator?
   *
   *
   * @param pseudoid
   *                       der angemeldetet Benutzer
   * @param pseudoPassword
   *                       dieses Passwort ist eine UUIDv4
   * @param drift
   * 
   * @return {@code true}, er ist ein Administrator
   */
  public boolean isAdministrator(String pseudoid, String pseudoPassword, long drift) {

    CBlock cblock = standardBlock.create(pseudoPassword, drift);

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
        .append("/javacommserver/administrator");
    try(Client client = ClientBuilder.newClient(config);) {
      WebTarget webtarget = client.target(url.toString());

      try(
        Response response = webtarget.request(MediaType.TEXT_PLAIN).header("pseudoid", pseudoid)
            .header("cblock", cblock.toString()).get()
      ) {
        return response.getStatusInfo() == Status.NO_CONTENT;
      }

    }
    catch (Exception e) {
      log.warn(e.getMessage());
      return false;
    }

  }



  public boolean isAudioAvailable() {
    if (!TelefonUtil.isMicro()) {
      // Texte müssen übersetzt werden
      JOptionPane.showMessageDialog(
          JChat.this, "<html>" + mikrofonFehlt.toString() + "</html>",
          telefonierenIstNichtMoeglich.toString(), JOptionPane.WARNING_MESSAGE
      );
      return false;
    }
    if (!TelefonUtil.isHeadphone() && !TelefonUtil.isSpeaker()) {
      JOptionPane.showMessageDialog(
          JChat.this, "<html>" + lautsprecher.toString() + "</html>", telefonierenIstNichtMoeglich.toString(),
          JOptionPane.WARNING_MESSAGE
      );
      return false;
    }
    if (telefonMikrofon == null) {
      JOptionPane.showMessageDialog(
          JChat.this, mikrofonAuswaehlen.toString(), telefonierenIstNichtMoeglich.toString(),
          JOptionPane.WARNING_MESSAGE
      );
      return false;
    }
    return true;
  }



  /**
   * Ist der Frame bereits geöffnet?
   *
   * @return
   */
  boolean isOpened(String record) {
    boolean opened = false;
    int i = frameVector.size();
    for (int j = 0; j < i; j++) {
      WM localWM = frameVector.elementAt(j);
      opened = opened || localWM.getFrameTitleId().equals(record);
    }
    return opened;
  }



  /**
   * Unter welcher IP-Adresse wird der Client im Internet gesehen.
   *
   *
   * @param url
   *            JAX-RS Serveradresse
   * @return die ermittelte IP-Adresse
   */
  public String jaxrsRemoteIp(String url) {
    Client restfulClient = ClientBuilder.newClient();
    WebTarget targetFTS = restfulClient.target(url);
    String ip = targetFTS.request(MediaType.TEXT_PLAIN).get(String.class);
    restfulClient.close();
    return ip;
  }



  /**
   * Unter welcher IP-Adresse wird der Client im Internet gesehen.
   *
   *
   * @param url
   *            JAX-RS Serveradresse
   * @return die ermittelte IP-Adresse
   */
  public CompletableFuture<String> jaxrsRemoteIpAsync(String url) {
    return CompletableFuture.supplyAsync(() -> {
      ClientConfig config = new ClientConfig();
      config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
      config.property(ClientProperties.READ_TIMEOUT, 1500);
      try(Client client = ClientBuilder.newClient(config)) {
        WebTarget targetFTS = client.target(url);
        String ip = targetFTS.request(MediaType.TEXT_PLAIN).get(String.class);
        return ip;
      }
    });

  }



  /**
   * Die Serververbindung ist verloren gegangen. Alle Fenster schließen. Chat
   * Launcher ausblenden, Telefon, Projektor ausblenden
   * 
   */
  private void lineBroken() {

    // alle Mikrofone und Speaker terminieren.

    log.info("!!! Line Broken !!!");
    ImageCaster.getInstance().stopRecording();

    // auskommentiert ist alles weg
    if (personChat != null) {
      log.info("personChat.doDefaultCloseAction()");
      personChat.doDefaultCloseAction(); // nicht in frameVector
    }
    if (besprechungsraum != null) {
      log.info("besprechungsraum.doDefaultCloseAction()");
      besprechungsraum.doDefaultCloseAction(); // nicht in frameVector
    }
    if (gruppenraum != null) {
      log.info("gruppenraum.doDefaultCloseAction()");
      gruppenraum.doDefaultCloseAction(); // nicht in frameVector
    }
    if (pausenraum != null) {
      log.info("pausenraum.doDefaultCloseAction()");
      pausenraum.doDefaultCloseAction(); // nicht in frameVector
    }
    if (updateProfil != null) {
      log.info("updateProfil.doDefaultCloseAction()");
      updateProfil.doDefaultCloseAction(); // nicht in frameVector
    }
    if (telefonErstellenFrame != null) {
      log.info("telefonErstellenFrame.doDefaultCloseAction()");
      telefonErstellenFrame.doDefaultCloseAction(); // nicht in frameVector
    }
    if (administratorFrame != null) {
      administratorFrame.doDefaultCloseAction(); // ist in frameVector
    }
    if (einwilligungFrame != null) {
      log.info("Einwilligung.doDefaultCloseAction()");
      einwilligungFrame.doDefaultCloseAction(); // ist in frameVector
    }
    if (teilenframe != null) {
      teilenframe.doDefaultCloseAction(); // ist in frameVector
      // teilenframe schließt ProjektorInternalFrame
    }

    List<WindowManagerInternalFrame> help = frameVector.stream()
        .filter(wm -> !(wm instanceof JPlayerInternalFrame) && !(wm instanceof StreamFrame))
        .map(m -> (WindowManagerInternalFrame) m).collect(Collectors.toList());

    help.forEach(wm -> {
      if (wm instanceof TelkoProjektorInternalFrame) {
        ((TelkoProjektorInternalFrame) wm).closeWindow();
      }
      else {
        wm.doDefaultCloseAction();
      }
    });
    help.clear();
    chatVector.removeAllElements();
    JOptionPane.showMessageDialog(desktop, linebroken.toString());
    switchOnMenu(false);

  }



  /**
   * 
   * @param domain
   *               von dieser Domäne werden die Bilder geladen
   * @param userid
   *               von diesem Anwender stammen die Bilder
   */
  private Optional<BufferedImage> loadScreenshot(String pseudoid) {
    BufferedImage image = null;
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    config.property(ClientProperties.READ_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder();
      url.append(Constants.PROTOCOL).append(Constants.DOMAIN).append("/javacommserver/screenshot/load/");
      WebTarget webtarget = client.target(url.toString());
      try(
        Response response = webtarget.request(MediaType.APPLICATION_OCTET_STREAM).header("pseudoid", pseudoid)
            .get();
        InputStream is = response.readEntity(InputStream.class)
      ) {
        image = ImageIO.read(is);
      }
    }
    catch (Exception e) {}
    return Optional.ofNullable(image);
  }



  /**
   * Ein Anwender meldet sich an. Userid und Mailadresse können identisch sein,
   * wenn in der GUI nur ein Eingabefeld für den Benutzernamen vorgesehen ist. Der
   * Server prüft ob einer der beiden Werte mit dem Password ein gültiges Paar
   * ergibt. (Mail/Password) oder (Userid/Password) muss ein gültiges Paar
   * ergeben.
   *
   * @param userid
   *                 diese Userid
   * @param password
   *                 dieses password
   * @param email
   *                 diese Mailadresse
   */
  public final void login(String userid, String password, String email) {
    CompletableFuture<String> resultIP = jaxrsRemoteIpAsync(Environment.FTS_URL);
    if (resultIP.isCompletedExceptionally()) {
      log.warn("keine Verbindung zum Service, " + resultIP.exceptionNow().getMessage());
      EventQueue.invokeLater(() -> {
        JOptionPane.showMessageDialog(
            JChat.this, leitungGebrochen.toString(), verbindungsproblem.toString(),
            JOptionPane.WARNING_MESSAGE
        );
        itemAnmelden.setEnabled(true);
        itemRegistrieren.setEnabled(true);
      });
      return;
    }

    CompletableFuture<PublicKey> futureRSA = CompletableFuture.supplyAsync(this::readRsaPublicKey);
    CompletableFuture<String> futureToken = CompletableFuture.supplyAsync(this::readToken);

    CompletableFuture.allOf(futureRSA, resultIP, futureToken).join();
    rsa = futureRSA.join();
    myIP = resultIP.join();

    Thread.startVirtualThread(() -> {
      for (int index = 0; index < 20; index++) {
//        drift = Math.abs(readServerTime() - System.currentTimeMillis());
        drift = getDrift();
      }
//      log.info("drift {}", drift);
    });

    String onetime = futureToken.join();
//    log.info("IP für den FTS --- > " + getIp());
    USRLOGIN userlogin = new USRLOGIN();
    userlogin.setAgent(Agent.Desktop); // aktuell optional
    Token token = new Token();
    token.setPassword(password);
    token.setUserid(userid);
    token.setEMail(email);
    token.setAES(Base64.getEncoder().encodeToString(secretAES.getEncoded()));
    token.setOnetime(onetime);

    String encryptedRSA = Crypto.encrypteRSA(token.toString(), rsa);

    userlogin.setIdentity(encryptedRSA);
    userlogin.setDataset(Protocol.DATASET);
    userlogin.setCommand(Command.USRLOGIN);
    userlogin.setHeader(REQUEST);
    executorChat.execute(() -> wsClient.sendMessage(userlogin));
  }



  /**
   * Mikrofon-Producer-Consumer wird gestartet. Der Producer muss immer zuerst
   * gestartet werden.
   * 
   * @param voiceQuality
   *                     diese Sprachqualität
   */
  protected void micStart(Sprachguete voiceQuality) {
    micStart(voiceQuality, null);
  }



  protected void micStart(Sprachguete voiceQuality, String conferencekey) {

    try {
      micQueue = new ArrayBlockingQueue<>(1);

      micProducer = new MicProducer(micQueue);
      micProducer.setTelefonformat(telefonMikrofon, TelefonUtil.toTelefonformat(voiceQuality));
      micProducer.start();

      micConsumer = new MicConferenceConsumer(micQueue, wsClient);
      micConsumer.setWebsocketSession(userdata.getWebsocketSession()); // besser über Methodenparameter

      if (conferencekey != null) micConsumer.setConferenceKey(conferencekey);
      micConsumer.start();

    }
    catch (MikrofonException | MicronameException e) {
      log.warn(e.getMessage());
      // This error often occurs with Bluetooth microphones that have been
      // disconnected in the meantime. Check the Bluetooth connection if you have a
      // Bluetooth microphone.
      // Este error suele producirse con los micrófonos Bluetooth que se han
      // desconectado entretanto. Comprueba la conexión Bluetooth si tienes un
      // micrófono Bluetooth.
      JOptionPane.showMessageDialog(
          JChat.this, "<html>" + e.getMessage() + "</html>", mikrofonEinrichten.toString(),
          JOptionPane.ERROR_MESSAGE
      );
    }

  }



  /**
   * Mikrofon-Producer-Consumer wird heruntergefahren. Der Consumer muss vor dem
   * Producer terminiert werden.
   */
  private void micTerminate() {

    if (micConsumer != null) {
      micConsumer.terminate();
      try {
        micConsumer.join();
        log.info("3.1 " + micConsumer.getClass().getSimpleName() + " micConsumer geschlossen - ok");
      }
      catch (InterruptedException e) {
        log.info("3 " + micConsumer.getClass().getSimpleName() + " micConsumer geschlossen - nok");
      }
    }
    if (micProducer != null) {
      try {
        micProducer.terminate();
        micProducer.join(2000); // Timeout für Sicherheit
        log.info("3.2 " + micProducer.getClass().getSimpleName() + " micProducer geschlossen - ok");
      }
      catch (InterruptedException e) {
        log.info("3.2 " + micProducer.getClass().getSimpleName() + " micProducer geschlossen - nok");
      }
    }
//    speakerTerminate();
  }



  private void nichtAngemeldet() {
    MultilingualString nichtAngemeldet = new MultilingualString(
        KEY.FRAME_LOGIN, ISO639.fromValue(rootLanguage.value())
    );
    MultilingualLabel label = new MultilingualLabel(
        KEY.LABEL_NICHT_ANGEMELDET, ISO639.fromValue(rootLanguage.value())
    );
    Timer timer = new Timer(5000, event2 -> {
      SwingUtilities.getWindowAncestor(label).dispose();
    });
    timer.setRepeats(false);
    timer.start();
    JOptionPane.showMessageDialog(JChat.this, label, nichtAngemeldet.toString(), JOptionPane.WARNING_MESSAGE);
    timer.stop();
    Thread.startVirtualThread(this::updateMessage);
  }



  /**
   * Für eine Telefonkonferenz wird die Bildschirmübertragung aktiviert oder
   * deaktiviert.
   * 
   * @param nickname
   *                      der Absendernickname
   * @param konferenzraum
   *                      in diesen Konferenzraum wird das Bild gesendet
   * @param conferencekey
   *                      der Konferenzschlüssel löst die Empfänger auf
   * 
   * @param sharing
   *                      {@code true}, der Bildschirm wird geteilt; {@code false}
   *                      der Bildschirm wird nicht geteilt
   */
  protected void onConferenceVideo(String nickname, String konferenzraum, String conferencekey,
      boolean sharing) {

    CONFERENCEVIDEO conferenceVideo = new CONFERENCEVIDEO();
    conferenceVideo.setCommand(Command.CONFERENCEVIDEO);
    conferenceVideo.setHeader(REQUEST);
    conferenceVideo.setDataset(Protocol.DATASET);
    conferenceVideo.setKonferenzraum(konferenzraum);
    conferenceVideo.setConferencekey(conferencekey);
    conferenceVideo.setShared(sharing);
    conferenceVideo.setNickname(nickname);
//    log.info("-----> {}", conferenceVideo.toString());
    executorChat.execute(() -> wsClient.sendMessage(conferenceVideo));

  }



  /**
   * Öffne die Chatliste/Launcher, wenn sie nicht geöffnet ist.
   * 
   * @param framelist
   *                  alle Frames
   * 
   * @return {@code true}, Launcher wurde geöffnet
   */
  private boolean openLauncherIf(List<WM> framelist) {

    boolean noPresent = framelist.stream().noneMatch(wm -> wm.getType() == Frames.CHATROOMS);
    if (noPresent) {
      // Launcher öffnen
      itemLaunch.doClick();
      try {
        launcher.setIcon(true);
      }
      catch (PropertyVetoException e) {
        log.info(e.getMessage(), e);
      }
    }
    return noPresent;
  }



  /**
   * Alle Telefonkonferenzen werden zurückgegeben, die dem Organisator gehören.
   * Ein Organisator kann viele Telefonkonferenzen haben.
   *
   * @param domain
   *               an diese Domäne wird gesendet
   * @param userid
   *               der Organisator der Konferenz
   *
   * @return alle Telefonkonferenzen von {@code userid}
   */
  public void organizeTelefonkonferenzSync(String pseudoid) {
    ArrayList<TransferOrganizeConferenceCall> result = null;
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      client.register(MessageBodyTransferOrganizeConferenceCall.class);
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/telko/organize/");

      WebTarget webtarget = client.target(url.toString());
      GenericType<ArrayList<TransferOrganizeConferenceCall>> configType = new GenericType<>() {};
      result = webtarget.request(MediaType.APPLICATION_JSON_TYPE).header("pseudoid", pseudoid)
          .get(configType);
    }
    catch (Exception e) {
      log.error(e.getMessage());
    }
    final ArrayList<TransferOrganizeConferenceCall> data = result;
    EventQueue.invokeLater(() -> {
      if (data != null) {
        telefonkonferenzframe.setData(data);
        telefonkonferenzframe.show();
        itemTelkoOrganisieren.setEnabled(false);
        itemTelkoErstellen.setEnabled(false);
      }
      else {
        // Exception wurde geworfen
        babelfish.remove(telefonkonferenzframe);
        telefonkonferenzframe.removeAllListener();
        JOptionPane.showMessageDialog(
            JChat.this, leitungGebrochen.toString(), verbindungsproblem.toString(),
            JOptionPane.WARNING_MESSAGE
        );
      }
    });
  }



  void pausenraumEinrichten() {
    itemPausenraum.setEnabled(false);
    pausenraum = new JPausenraum();
    pausenraum.setMinuten(readSchwellenwert(root.getDomain()));
    pausenraum.setFrameIcon(new ImageIcon(resource.getResource(Resource.PAUSENRAUM)));
    pausenraum.setClosable(true);
    pausenraum.setSize(new Dimension(JPausenraum.WIDTH, JPausenraum.HEIGHT));
    pausenraum.setResizable(true);
    pausenraum.setIconifiable(false);
    Rectangle localRectangle = getBounds();
    pausenraum.setLocation(
        (localRectangle.width - JPausenraum.WIDTH) / 2, (localRectangle.height - JPausenraum.HEIGHT) / 4
    );
    desktop.add(pausenraum, JLayeredPane.MODAL_LAYER, 4);
    babelfish.add(pausenraum);
    pausenraum.setLanguage(ISO639.fromValue(rootLanguage.value()));
    pausenraum.show();
    pausenraum.addPausenroomListener(arg -> {
      pausenraumListener((Control) arg.getNewValue());
    });
  }



  /**
   * Ein Ereignis wird aus dem Pausenraum gemeldet.
   *
   *
   * @param control
   *                das Ereignis
   */
  void pausenraumListener(Control control) {
    switch(control) {
      case CLOSE:
        if (launcher != null) launcher.setEnabledPausenraum(true);
        itemPausenraum.setEnabled(true);
        pausenraum.removeAllListener();
        babelfish.remove(pausenraum);
        break;
      case CREATE:
        // senden
        CREATETEMPROOM createtemproom = new CREATETEMPROOM();
        createtemproom.setCommand(Command.CREATETEMPROOM);
        createtemproom.setHeader(REQUEST);
        createtemproom.setDataset(Protocol.DATASET);
        createtemproom.setEntry(Entry.PUBLIC);
        createtemproom.setLifetime(Lifetime.TEMPORARY);
        createtemproom.setPseudoid(userdata.getPseudoid());
        createtemproom.setRoom(pausenraum.getRoom());
        createtemproom.setNickname(userdata.getNickname());
        createtemproom.setRoomtype(Roomtype.PAUSENRAUM);
        executorChat.execute(() -> wsClient.sendMessage(createtemproom));
        break;
      default:
        break;
    }
  }



  /**
   * Ein Playerframe wird aus dem Windowmanager und der Frameverwaltung entfernt.
   *
   * @param source
   *               der zu entferenende Playerframe
   */
  void playerframeQuit(JPlayerInternalFrame source) {
    int countFrames = playerVector.size();
    for (int n = 0; n < countFrames; n++) {
      JPlayerInternalFrame playerframe = playerVector.elementAt(n);
      if (source.equals(playerframe)) {
        playerVector.removeElementAt(n);
        frameVector.remove(playerframe);
        break;
      }
    }
    removeWM(source);
    internalplayer.removeAllListener();
    menuInhaltsverzeichnis.setEnabled(false); // es gibt keine offenen Player mehr, deshalb
    // auch keine DVD-Titel
    babelfish.remove(internalplayer);
    windowAnrufen.removeWindowAnrufenListener(internalplayer);
  }



  void playerInternalframe(String mediaformat, PLAYERMODE mode) throws MediaException {
    playerInternalframe(mediaformat, mode, ISO6391.DE);
  }



  /**
   *
   * Bisher wird nicht unterschieden, ob es sich um ein Einzellied oder um ein
   * Shufflelied handelt.
   *
   *
   * @param mediaformat
   *                    die vom FileChooser ausgewählte Datei
   * @param mode
   *                    SHUFFE, SINGLE, DVD
   * @param language
   *                    mit welcher Sprache die DVD abgespielt werden soll
   * @throws MediaException
   *                        die Datei oder das Medium ist korrupt
   */
  void playerInternalframe(String mediaformat, PLAYERMODE mode, ISO6391 language) throws MediaException {
    try {
      futureVideolan.get();
    }
    catch (InterruptedException | ExecutionException e1) {
      log.error(e1.getMessage(), e1.getCause());
    }
    internalplayer = new JPlayerInternalFrame(
        Paths.get(mediaformat), playervolume, mode, itemVollbildmodus.isSelected()
    );
    windowAnrufen.addWindowAnrufenListener(internalplayer);
    anrufenEvent(internalplayer);

    Rectangle localRectangle = getBounds();
    internalplayer.setLocation(
        (localRectangle.width - JPlayerInternalFrame.SCREEN_WIDTH) / 2,
        (localRectangle.height - JPlayerInternalFrame.SCREEN_HEIGHT) / 4
    );
    desktop.add(internalplayer, JLayeredPane.DEFAULT_LAYER, PLAYER_LAYER);
    internalplayer.fensterteilerMaximieren();
    try {
      internalplayer.setMaximum(true);
    }
    catch (PropertyVetoException e) {
      log.info(e.getMessage(), e);
    }
    setBackgroundTreeview(helado, internalplayer);
    internalplayer.show();
    frameVector.addElement(internalplayer);
    playerVector.addElement(internalplayer);
    insertWM(internalplayer, internalplayer.getFrameTitleId());

    internalplayer.addPlayerListener(event -> {
      Control rc = (Control) event.getNewValue();
      switch(rc) {
        case TITEL_ABSPIELEN:
          itemTitel.doClick();
          break;
        case PLAYERMODE:
          if ((PLAYERMODE) event.getOldValue() == PLAYERMODE.DVD) {
            menuInhaltsverzeichnis.setEnabled(true);
            menuInhaltsverzeichnis.removeAll();
            List<TitleDescription> alleDvdTitel = internalplayer.findeAlleDvdTitel();

            ButtonGroup group = new ButtonGroup();
            titel = new JCheckBoxMenuItem[alleDvdTitel.size()];
            for (int index = 0; index < alleDvdTitel.size(); index++) {
              if (alleDvdTitel.get(index).isMenu()) {
                titel[index] = new JCheckBoxMenuItem();
                titel[index].setText(alleDvdTitel.get(index).name());
              }
              else {
                TitleDescription tmp = alleDvdTitel.get(index);
                if (tmp.name() == null) {
                  if (index < 10) {
                    titel[index] = new JCheckBoxMenuItem();
                    titel[index].setText(
                        dvdTitel.toString() + "_"
                            + index
                            + "     "
                            + JPlayerInternalFrame.format(tmp.duration() / 1000)
                    );

                  }
                  else {
                    titel[index] = new JCheckBoxMenuItem();
                    titel[index].setText(
                        dvdTitel.toString() + index
                            + "     "
                            + JPlayerInternalFrame.format(tmp.duration() / 1000)
                    );
                  }
                }
                else {
                  titel[index] = new JCheckBoxMenuItem();
                  titel[index].setText(tmp + "     " + JPlayerInternalFrame.format(tmp.duration() / 1000));
                }
              }
              titel[index].addActionListener(action -> {
                for (int m = 0; m < titel.length; m++) {
                  if (titel[m].equals(action.getSource())) {
                    internalplayer.startDvdTitle(m);
                    break;
                  }
                }
              });
              group.add(titel[index]);
              menuInhaltsverzeichnis.add(titel[index]);
            }
            try {
              titel[1].setSelected(true);
            }
            catch (Exception e) {}
          }
          else {
            menuInhaltsverzeichnis.removeAll();
            menuInhaltsverzeichnis.setEnabled(false);
          }
          break;
        case MEDIAPLAYER_START:
          break;
        case VOLLBILDMODUS:
          itemVollbildmodus.doClick();
          break;
        case VOLUME:
          JPlayerInternalFrame source = (JPlayerInternalFrame) event.getSource();
          playervolume = source.getVolume();
          break;
        case SONG_FINISHED:
          // MediaPlayer oder Shuffle
          if (itemSpielliste.isSelected()) {
            internalplayer.removeAllListener();
            removeAllPlayerFrames(Control.SONG_FINISHED);
            try {
              playShuffle(playlist);
            }
            catch (ShuffleException e) {
              JOptionPane.showMessageDialog(
                  JChat.this, "<html>" + alleMusikstuecke.toString() + "</html>", "Shuffle",
                  JOptionPane.INFORMATION_MESSAGE
              );
            }
            catch (IllegalArgumentException e) {
              // ein Bug
              log.warn(
                  "playlist ist null, was nicht sein darf. Der Fehler wurde abgefangen und kann reproduziert werden."
              );
            }
          }
          else {
//            internalplayer.switchOnPlayermenu(false);
          }
          break;
        case SELECTED:
          selectFrame(event.getPropertyName());
          break;
        case DVD_QUIT:
          source = (JPlayerInternalFrame) event.getSource();
          playerframeQuit(source);
          break;
        case SINGLE_QUIT:
          source = (JPlayerInternalFrame) event.getSource();
          playerframeQuit(source);
          break;
        case SHUFFLE_WINDOW_CLOSE:
          removeAllPlayerFrames(Control.SHUFFLE_CONTINUE); // vorerst
          internalplayer.removeAllListener();
          itemSpielliste.setSelected(false);
          alleHakenAusSpiellisteEntfernen();

          break;
        case SHUFFLE_CONTINUE:
          // wird vom Knopf ausgelöst
          removeAllPlayerFrames(Control.SHUFFLE_CONTINUE);
          internalplayer.removeAllListener();
          try {
            playShuffle(playlist);
          }
          catch (ShuffleException e) {
            JOptionPane.showMessageDialog(
                JChat.this, "<html>" + alleMusikstuecke.toString() + "</html>", "Shuffle",
                JOptionPane.INFORMATION_MESSAGE
            );
            itemSpielliste.setSelected(false);
          }
          break;
        case MEDIAPLAYER_FRAME_ACTIVATED:
          if (itemFensterImHintergrund.isSelected()) {
            internalplayer.toBack();
          }
          break;
        case MOUSE_EXITED:
          log.info("player exited");
          break;
        default:
          break;
      }
    });
    if (PlayerFilter.isSupportedAudioformat(Paths.get(mediaformat))) {
      internalplayer.audiostart();
      if (itemFensterImHintergrund.isSelected()) {
        internalplayer.toBack();
      }
    }
    else if (PlayerFilter.isSupportedVideoformat(Paths.get(mediaformat))) {
      internalplayer.videostart(mode, language);
      if (itemVollbildmodus.isSelected()) {
        // Bildschirm maximieren
        if (internalplayer.isShowing() && itemVollbildmodus.isSelected()) {
          setExtendedState(Frame.MAXIMIZED_BOTH);
          try {
            internalplayer.setMaximum(true);
          }
          catch (PropertyVetoException e) {
            log.error(e.getMessage(), e.getCause());
          }
        }
        else {
          setExtendedState(Frame.NORMAL);
        }
      }
      if (itemFensterImHintergrund.isSelected()) {
        internalplayer.toBack();
      }
    }
    babelfish.add(internalplayer);
    internalplayer.setLanguage(ISO639.fromValue(rootLanguage.value()));
  }



  /**
   * Spiele ein Lied aus der Shuffleliste ab und kürze die Liste um das
   * abgespielte Lied.
   *
   * @param playlist
   *                 eine Spielliste
   * @throws ShuffleException
   *                          keine weiteren Musikstücke vorhanden
   */
  void playShuffle(List<Path> playlist) throws ShuffleException {
    Path lied = null;
    try {
      lied = PlayerFilter.shuffle(playlist);
    }
    catch (ShuffleException e) {
      alleHakenAusSpiellisteEntfernen();
      itemSpielliste.setSelected(false);
      throw e;
    }

    playShuffleSong(lied);

  }



  /**
   * Spiele ein Lied aus der Shuffleliste ab und kürze die Liste um das
   * abgespielte Lied.
   * 
   * @param playlist2
   *                   diese Spielliste
   * @param firstTitle
   *                   dieser Titel soll als erstes abgespielt werden und ist ein
   *                   vollqualifizierter Dateiname
   */
  void playShuffle(List<Path> playlist2, String firstTitle) {
    Path lied = Paths.get(firstTitle);
    playlist2.removeIf(filter -> filter.getFileName().equals(lied.getFileName()));
    playShuffleSong(lied);
  }



  /**
   * Abspielen.
   * 
   * @param lied
   *             dieser Titel soll als erstes abgespielt werden und ist ein
   *             vollqualifizierter Dateiname
   */
  private void playShuffleSong(Path lied) {
    Mediaplayer mediaplayer = root.getMediaplayer();
    mediaplayer.setSinglefile(lied.toAbsolutePath().toString());
    try {
      playerInternalframe(lied.toAbsolutePath().toString(), PLAYERMODE.SHUFFLE);
    }
    catch (MediaException e) {
      // Spiele das nächste Lied aus der Liste ab.
      log.warn(e.getMessage(), e);
      internalplayer.shuffleNext();
    }
  }



  /**
   * Vor dem Download wird die Dateigröße ermittelt.
   * 
   * @param domain
   *               an diese Domäne wird gesendet
   * @param anlage
   *               ist der Datenbankschlüssel
   * @return der Anhang hat diese Größe
   */
  Long readChatfileSize(String domain, Long anlage) {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
          .append("/javacommserver/chat/anlage/size");

      WebTarget webtarget = client.target(url.toString());
      return webtarget.request(MediaType.TEXT_PLAIN).header("anlage", anlage).get(Long.class);
    }
  }



  /**
   * Vor dem Download wird die Dateigröße ermittelt.
   * 
   * @param domain
   *               an diese Domäne wird gesendet
   * @param anlage
   *               ist der Datenbankschlüssel
   * @return der Anhang hat diese Größe
   */
  Long readChatfileSizePrivate(String domain, Long anlage) {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
          .append("/javacommserver/chat/anlage/private/size");
      long result = client.target(url.toString()).request(MediaType.TEXT_PLAIN).header("anlage", anlage)
          .get(Long.class);
      return result;
    }
  }



  /**
   * Alle Chunknummern einr Datei/Anlage werden zurückgegeben.
   * 
   * @param domain
   *               an diese Domäne wird gesendet
   * @param anlage
   *               Identifier für eine Datei/Anlage
   * @return dieser Rückgabewert enthält alle Chunknummern in sortierter
   *         Reihenfolge
   */
  public ArrayList<Long> readChunknumbersFromJson(String domain, long anlage) {
    try(Client client = ClientBuilder.newClient()) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
          .append("/javacommserver/chat/anlage/chunknumbers");

      WebTarget webtarget = client.target(url.toString());
      String result = webtarget.request(MediaType.TEXT_PLAIN).header("anlage", anlage).get(String.class);
      Gson gson = new Gson();
      ArrayList<Long> liste = gson.fromJson(result, new TypeToken<ArrayList<Long>>() {});
      return liste;
    }
  }



  /**
   * Alle Chunknummern einr Datei/Anlage werden zurückgegeben.
   * 
   * @param domain
   *               an diese Domäne wird gesendet
   * @param anlage
   *               Identifier für eine Datei/Anlage
   * @return dieser Rückgabewert enthält alle Chunknummern in sortierter
   *         Reihenfolge
   * @throws InterruptedException
   * @throws ExecutionException
   * @throws TimeoutException
   *                              die Antwort dauert zu lange
   */
  public ArrayList<Long> readChunknumbersFromPrivate(String domain, long anlage) {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
          .append("/javacommserver/chat/anlage/chunknumbers/private");

      WebTarget webtarget = client.target(url.toString());
      String result = webtarget.request(MediaType.TEXT_PLAIN).header("anlage", anlage).get(String.class);
      Gson gson = new Gson();
      ArrayList<Long> liste = gson.fromJson(result, new TypeToken<ArrayList<Long>>() {});
      return liste;
    }
  }



  /**
   * Die Benutzerdaten für den Filetransferservice werden gelesen.
   *
   *
   * @param pseudoid
   *                 die Benutzer-ID
   * @return die Benutzerdaten
   */
  public TransferFiletransferConfig readFiletransferConfig(String pseudoid) {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    config.property(ClientProperties.READ_TIMEOUT, 1500);
    try(Client client = ClientBuilder.newClient(config)) {
      client.register(MessageBodyTransferFiletransferConfig.class);
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/filetransfer/read");
      WebTarget target = client.target(url.toString());
      return target.request(MediaType.APPLICATION_JSON).header("pseudoid", pseudoid)
          .get(TransferFiletransferConfig.class);
    }
  }



  /**
   * Der Anwender möchte wissen, in welchen Konferenzen er Mitglied ist. Jede
   * Konferenz hat einen Schlüssel.
   *
   * @param pseudoid
   *                 ein Anwender
   *
   * @return eine Liste aller Konferenzen
   */
  public void readJoinedConferences(String pseudoid) {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      client.register(MessageBodyTransferKonferenzraum.class);
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/telko/read/joined/conferences/");
      WebTarget webtarget = client.target(url.toString());
      GenericType<ArrayList<TransferKonferenzraum>> configType = new GenericType<>() {};
      ArrayList<TransferKonferenzraum> response = webtarget.request(MediaType.APPLICATION_JSON)
          .header("pseudoid", pseudoid).get(configType);
      EventQueue.invokeLater(() -> {
        // rechte List löschen
        telefonTeilnehmenFrame.setData(response);
        telefonTeilnehmenFrame.refreshData();
      });
    }
    catch (Exception e) {
      log.warn("Konferenzräume konnten nicht gelesen werden.");
    }
  }



  /**
   * Das ODX-Verzeichnis wird zurückgegeben.
   *
   * @param domain
   *               an diese Domäne wird gesendet
   * @param userid
   *               der ODX-Anwender
   * @return eine Datei oder ein Verzeichnis, welches zuletzt gelesen wurde
   */
  public Future<String> readOdxDirectoryAsync(String domain, String userid) {
    Client client = ClientBuilder.newClient();
    StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
        .append("/javacommserver/odx/read/load/directory/");
    WebTarget target = client.target(url.toString()).path(userid);
    Future<String> dirctory = target.request(MediaType.TEXT_PLAIN).async().get(String.class);
    return dirctory;
  }



  /**
   * Ist die aktuelle Version veraltet?
   *
   *
   * @param domain
   *               an diese Domain wird gesendet
   *
   * @return {@code true}, sie ist veraltet
   */
  public boolean readOutdatedVersions(String domain) {

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    ArrayList<String> result = new ArrayList<>();
    try(Client client = ClientBuilder.newClient(config)) {
      client.register(MessageBodyOutdated.class);
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/administrator/read/outdated");
      WebTarget target = client.target(url.toString());
      GenericType<ArrayList<String>> configType = new GenericType<>() {};
      result = target.request(MediaType.APPLICATION_JSON_TYPE).get(configType);
    }
    catch (ProcessingException e) {
      log.error("Die aktuelle Version konnte nicht geprüft werden, ob sie veraltet ist.");
      log.warn(e.getMessage());
    }
    return result.contains(Constants.VERSION);
  }



  /**
   * Die Liste zeigt an, wer online und wer offline ist.
   *
   * @param domain
   *                         an diese Domäne wird gesendet
   * @param konferenzname
   *                         der Konferenzraum
   * @param organisator
   *                         der Organisator
   * @param websocketSession
   *                         der Konferenzteilnehmer
   */
  public void readPresence(String konferenzname, String conferencekey, String websocketSession) {
//    log.info("conferencekey {}", conferencekey);

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      client.register(MessageBodyCONFERENCE.class);
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/telko/read/presence/");
      WebTarget webtarget = client.target(url.toString());
      CONFERENCE response = webtarget.request(MediaType.APPLICATION_JSON)
          .header("konferenzname", konferenzname).header("conferencekey", conferencekey)
          .get(CONFERENCE.class);

//      log.info("<----- {}", response.toString());
      EventQueue.invokeLater(() -> {
        KonferenzraumUser[] konferenzraumuser = response.getKonferenzraumUser();
        telefonTeilnehmenFrame.setKonferenzraumuser(konferenzraumuser, websocketSession);
        telefonTeilnehmenFrame.setKnopfleiste(websocketSession, konferenzraumuser);
      });
    }
    catch (Exception e) {
      log.info("Der Konferenzteilnehmerstatus konnte nicht gelesen werden.");
    }

  }



  /**
   * Lies alle Anwender ein, die den Konferenzraum betreten könnten. Der
   * Rückgabewert sind n-Paare aus (Nickname/Userid). Der Konferenzorganisator ist
   * nicht in den Paaren enthalten, weil er immer an der Konferenz teilnimmt.
   * 
   * 
   * @param domain
   *                      an diese Domäne wird gesendet
   * @param konferenzraum
   *                      der Konferenzname
   * @param pseudoid
   *                      pseudoid ist der Organisator der Konferenz
   */
  public void readRestlicheMitgliederSync(String konferenzraum, String pseudoid) {

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      client.register(MessageBodyArrayList.class);
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/telko/lesen/users");
      WebTarget webtarget = client.target(url.toString());
      GenericType<ArrayList<String>> configType = new GenericType<>() {};
      // hier ist der Organisator identisch mit der userid
      List<String> nicknames = webtarget.request(MediaType.APPLICATION_JSON_TYPE)
          .header("konferenzraum", konferenzraum).header("pseudoid", pseudoid).get(configType);
      telefonkonferenzframe.setRestlicheMitglieder(konferenzraum, nicknames);
    }
    catch (Exception e) {
      log.warn("nicknames/user konnten nicht gelesen werden");
    }

  }



  /**
   * Der RSA Public Key wird vom Server abgeholt.
   * 
   * @return
   */
  public PublicKey readRsaPublicKey() {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    config.property(ClientProperties.READ_TIMEOUT, 1500);
    try(Client client = ClientBuilder.newClient(config)) {
      WebTarget target = client
          .target(Constants.PROTOCOL + Constants.DOMAIN + "/javacommserver/rsa/public/key");
      String key = target.request(MediaType.TEXT_PLAIN).get(String.class);
      PublicKey publickeyRSA = Crypto.loadPublicRSAKey(key);
      return publickeyRSA;
    }
  }



  public int readSchwellenwert(String domain) {
    Integer result = 150;
    if (domain == null) return result;
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    config.property(ClientProperties.READ_TIMEOUT, 1500);
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
          .append("/javacommserver/forum/schwellenwert");
      WebTarget target = client.target(url.toString());
      result = target.request().get(Integer.class);
      return result;
    }
  }



  /**
   * Hole ein Einmal-Token für das IDENTITY Attribut.
   * 
   * @return dieses Einmal-Token
   */
  public String readToken() {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    config.property(ClientProperties.READ_TIMEOUT, 3000);
    try(Client client = ClientBuilder.newClient(config)) {
      WebTarget target = client
          .target(Constants.PROTOCOL + Constants.DOMAIN + "/javacommserver/user/read/token");
      String token = target.request(MediaType.TEXT_PLAIN).post(null, String.class);
      return token;
    }
  }



  /**
   * Lies die Benutzerdaten aus.
   *
   *
   * @param pseudoid
   *                       pseudoPassword die Userid von einem Anwender
   * @param pseudoPassword
   *                       das Pseudopasswort von einem Anwender
   * 
   * @return das Transferobjekt enthält die Benutzerdaten oder ist leer
   *
   * @throws UserData404Exception
   *                              die Benutzerdaten konnten nicht vom Server
   *                              gelesen werden
   */
  public TransferUser readUserdataSyncHeader(String pseudoid, String pseudoPassword, long drift) {

    CBlock cblock = standardBlock.create(pseudoPassword, drift);

    try(Client client = ClientBuilder.newClient()) {
      ArrayList<TransferUser> result = new ArrayList<>();
      client.register(MessageBodyTransferUser.class);

      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/user/read/data");
      WebTarget target = client.target(url.toString());
      GenericType<ArrayList<TransferUser>> configType = new GenericType<>() {};
      result = target.request(MediaType.APPLICATION_JSON).header("pseudoid", pseudoid)
          .header("cblock", cblock.toString()).get(configType);
      return result.get(0);
    }
  }



  /**
   * Lies alle Benutzeranträge aus.
   * 
   * @param pseudoid
   *                 dieser User ist ein Admin
   * @param block
   *                 enthält Pseudopassword und Timestamp
   * @return alle Benutzeranträge
   * 
   */
  private void readUserRegistration(String pseudoid, CBlock block) {

    Optional<ArrayList<TransferUser>> result = Optional.empty();
    StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
        .append("/javacommserver/administrator/user");
    try(
      Client client = ClientBuilder.newClient().property(ClientProperties.CONNECT_TIMEOUT, 2000);
      FormDataMultiPart multipart = new FormDataMultiPart()
    ) {
      client.register(MultiPartFeature.class);
      client.register(MessageBodyTransferUser.class);
      WebTarget webtarget = client.target(url.toString());
      GenericType<ArrayList<TransferUser>> configType = new GenericType<>() {};
      multipart.field("pseudoid", pseudoid);
      multipart.field("cblock", block.toString());
      result = Optional.ofNullable(
          webtarget.request(MediaType.APPLICATION_JSON)
              .post(Entity.entity(multipart, multipart.getMediaType()), configType)
      );
    }
    catch (Exception e) {}
    result.ifPresentOrElse(
        registration -> EventQueue.invokeLater(() -> administratorFrame.setBenutzerantraege(registration)),
        () -> EventQueue.invokeLater(
            () -> JOptionPane.showMessageDialog(
                JChat.this, "Die Benutzeranträge konnten nicht gelesen werden.", menuAdministrator.getText(),
                JOptionPane.ERROR_MESSAGE
            )
        )
    );

  }



  /**
   * Der Anmeldedialog ruft diese Methode auf, um eine Benutzerregistrierung
   * vorzunehmen.
   * 
   * @param key
   *            dieser Schriftzug auf dem 2. Knopf
   * @return mit disem Status DISMISSED, ELAPSED, GUEST, CONNECTED,
   *         WITHOUT_REGISTRATION wird der Registrierungsbildschirm verlassen
   */
  private STATUS registryFrame(KEY key) {
    final STATUS[] status = {STATUS.GUEST};

    RegistryButton registryButton = new RegistryButton(key);
    babelfish.add(registryButton);
    registryFrame = new JRegistryFrame(this, registryButton);
    registryFrame.setKey(KEY.REGISTRIEREN);
    babelfish.add(registryFrame);
    root.setLanguage(rootLanguage);
    initLanguage(root);

    registryFrame.addRegisterListener(event -> {
      // Klasse Register

      Control value = (Control) event.getNewValue();

      switch(value) {
        case NEUES_PASSWORD:
          // der Anwender wurde als neuer User bestätigt
          jLogon.setPassword(userdata.getPassword());
          status[0] = STATUS.PROVISIONAL;
          break;
        case OHNE_REGISTRIEREN:
//         // autoconnect aktivieren
          status[0] = status[0] == STATUS.PROVISIONAL ? STATUS.PROVISIONAL : STATUS.WITHOUT_REGISTRATION; // SIGNIN
          // erfolgreich
          break;
        case REGISTER:
          // in einen Thread auslagern
          // Registierenknopf wurde gedrückt.
          // ein SignIn wird durchgeführt.
          // anschließend ein AutoLogin

          executorJersey.execute(() -> {

            SIGNIN signin;
            try {
              Future<String> rseultIP = jaxrsRemoteIpAsync(Environment.FTS_URL);
              // signin ist synchron
              signin = signin(jLogon.getUrl(), registryFrame.getEmail(), registryFrame.getLanguage().value());
              myIP = rseultIP.get(1, TimeUnit.SECONDS);
            }
            catch (Exception e) {
              log.warn(e.getMessage());
              log.warn("SIGNIN kann nicht ausgeführt werden");
              status[0] = STATUS.GUEST;
              return;
            }

//            log.info("<----- " + signin.toString());

            if (CONFIRM == signin.getHeader()) {
              // völlig neuer Anwender
              // autoconnect
              EventQueue.invokeLater(() -> {
                if (signin.getHeader() == CONFIRM) {
                  registryFrame.confirmUser();
                }
                registryFrame.setTextArea(
                    new MultilingualString(signin.getMultilingualkey(), registryFrame.getLanguage())
                        .toString(),
                    Color.WHITE, Resource.JQUERY_GREEN
                );
                registryFrame.disableInput();
                itemRegistrieren.setEnabled(false);
              });

              // mache ein Autoconnect
              jLogon.setMail(signin.getEmail());
              jLogon.setPassword(signin.getPassword());
              status[0] = STATUS.PROVISIONAL;
            }
            else if (RESPONSE == signin.getHeader()) {
//               log.info("neues Password anfordern");
//               log.info("nur möglich, wenn nicht angemeldet");

              userdata = new Userdata();
              userdata.setNickname(signin.getNickname());
              userdata.setBackgroundColor(signin.getBackgroundColor());
              userdata.setForegroundColor(signin.getForegroundColor());
              userdata.setNeuesPassword(signin.getNeuesPassword());
              userdata.setConfirmationKey(signin.getConfirmationKey());
            }
            else if (HEADER.ERROR == signin.getHeader()) {
              EventQueue.invokeLater(() -> {
                switch(Benutzerstatus.toBenutzerstatus(signin.getErrorCode())) {
                  case MÖCHTE_VOLLMITGLIED_WERDEN:
                    registryFrame.setTextArea(signin.getText(), Resource.JQUERY_BLAU, Resource.JQUERY_YELLOW);
                    break;
                  case VOLLMITGLIEDSCHAFT_BESTÄTIGT:
                    registryFrame
                        .setTextArea(signin.getText(), Resource.JQUERY_BLAU, Resource.JQUERY_GREEN_YELLOW);
                    break;
                  case MITGLIEDSCHAFT_ABGELEHNT:
                    registryFrame.setTextArea(signin.getText(), Color.WHITE, Resource.JQUERY_RED);
                    break;
                  case VOLLMITGLIED:
                    registryFrame
                        .setTextArea(signin.getText(), Resource.JQUERY_BLAU, Resource.JQUERY_GREEN_YELLOW);
                    break;
                  default:
                    log.warn("kein Benutzerstatus");
                    break;
                }
                registryFrame.disableInput();
              });
            }
          });

          break;
        default:
          break;
      }

    });

    registryFrame.setVisible(true);
    registryFrame.removeAllRegisterListener();
    babelfish.remove(registryButton);
    babelfish.remove(registryFrame);
    return status[0];
  }



  /**
   * Alle bisherigen Songs werden unterbrochen und die Frames gelöscht. Dabei wird
   * unterschieden, ob der Anwender mit einem Button.Close oder Window. Schließen
   * die Aktion ausgelöst hatte.
   *
   * @param control
   *                {@code Controll.QUIT} wurde vom Anwender aktiv ausgelöst,
   *                sonst {@code Controll.NULL}
   */
  private void removeAllPlayerFrames(Control control) {
    playerVector.forEach(player -> {
      // muss vor pressedCloseButton() stehen
      // der Listener wird abgeschaltet und verhindert eine Schleife
      player.removeAllListener();
      if (control != Control.SHUFFLE_CONTINUE) player.mp3CloseButton(); // der Anwender schließt
      // das Fenster
      removeWM(player);
      frameVector.remove(player);
    });
    playerVector.removeAllElements();
  }



  /**
   * Entferne einen InternalFrame aus dem WindowManager.
   *
   * @param internalFrame
   *                      genau dieser Frame soll entfernt werden
   */
  private void removeWM(WM internalFrame) {
    for (WM localWM : frameVector) {
      localWM.removeWM(internalFrame);
    }
  }



  /**
   * Der Anwender fordert ein neues Passwort an. Der Aufruf kommt aus dem <b> Menü
   * JavaComm Neues Passwort anforern</b>.
   * 
   * @param iso
   *                     diese Sprache
   * @param encryptedRSA
   *                     verschlüsselt mit RSA Public Key
   */
  private void renewPassword(ISO639 iso, String encryptedRSA) {
    Status httpStatus;
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    config.property(ClientProperties.READ_TIMEOUT, 5000);

    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/user/renew/password");
      WebTarget target = client.target(url.toString()).path(language.toString());
      // !!!!!!!!!!!! kam zu einem SocketTimeout
      // Exception in thread "AWT-EventQueue-0" jakarta.ws.rs.ProcessingException:
      // java.net.SocketTimeoutException: Read timed out
      try(Response response = target.request(MediaType.TEXT_PLAIN).put(Entity.text(encryptedRSA))) {
        httpStatus = (Status) response.getStatusInfo();
        switch(httpStatus) {
          case BAD_REQUEST:
            EventQueue.invokeLater(
                () -> registryFrame.setTextArea(
                    new MultilingualString(KEY.STRING_NOT_ENCRYPTING_PROPERLY, iso).toString(),
                    Resource.JQUERY_BLAU, Resource.JQUERY_YELLOW
                )
            );
            break;
          case NOT_FOUND:

            EventQueue.invokeLater(
                () -> registryFrame.setTextArea(
                    new MultilingualString(
                        KEY.STRING_KEIN_REGISTRIERTER_ANWENDER, registryFrame.getLanguage()
                    ).toString(), Color.WHITE, Resource.JQUERY_RED
                )
            );
            break;
          case FORBIDDEN:
            EventQueue
                .invokeLater(
                    () -> registryFrame.setTextArea(
                        new MultilingualString(KEY.STRING_KONTO_GESPERRT, registryFrame.getLanguage())
                            .toString(),
                        Resource.JQUERY_BLAU, Resource.JQUERY_GREEN_YELLOW
                    )
                );

            break;
          case OK:
            EventQueue
                .invokeLater(
                    () -> registryFrame.setTextArea(
                        new MultilingualString(KEY.SERVER_NEUES_PASSWORT, registryFrame.getLanguage())
                            .toString(),
                        Color.WHITE, Resource.JQUERY_GREEN
                    )
                );

            break;

          default:
            log.error(httpStatus.getStatusCode() + " - HTTP, dieser Fehler wurde nicht berücksichtigt");
            throw new UnsupportedOperationException(
                httpStatus.getStatusCode() + " - HTTP, dieser Fehler wurde nicht berücksichtigt"
            );
        }
      }
    }
    catch (Exception e) {
      log.warn(e.getMessage());
    }
    EventQueue.invokeLater(() -> registryFrame.disableInput());

  }



  /**
   * Eine Liste aller privater Pausenräume wird angefordert.
   *
   *
   */
  void requestBreakrooms() {
    Runnable task = () -> {
      READROOMS request = new READROOMS();
      request.setCommand(Command.READROOMS);
      request.setHeader(REQUEST);
      request.setDataset(Protocol.DATASET);
      request.setPseudoid(userdata.getPseudoid());
      request.setRoomtype(Roomtype.PAUSENRAUM);
      wsClient.sendMessage(request);
    };
    executorChat.execute(task);

  }



  void requestGrouprooms() {
    Runnable task = () -> {
      READROOMS request = new READROOMS();
      request.setCommand(Command.READROOMS);
      request.setHeader(REQUEST);
      request.setDataset(Protocol.DATASET);
      request.setPseudoid(userdata.getPseudoid());
      request.setRoomtype(Roomtype.GRUPPENRAUM);
      wsClient.sendMessage(request);
    };
    executorChat.execute(task);
  }



  /**
   * Erfrage alle Raummitglieder.
   *
   *
   * @param room
   *             ein Chatraum
   */
  void requestRaummitglieder(String room) {
    TOPICMEMBER member = new TOPICMEMBER();
    member.setCommand(Command.TOPICMEMBER);
    member.setHeader(REQUEST);
    member.setDataset(Protocol.DATASET);
    member.setPseudoid(userdata.getPseudoid());
    member.setRoom(room);
    log.info("-----> {}", member.toString());
    executorChat.execute(() -> wsClient.sendMessage(member));
  }



  @Override
  /**
   * JApplication
   */
  public void run() {
    setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
    Environment env = Environment.getInstance();
    if (env.isWindows()) {
      log.info("VideoLAN Windows64 libdir ---> " + env.getLibdir());
      Callable<Void> callable = () -> {
        // UnsatisfiedLinkError beim Debuggen
        try {
          System.load(Paths.get(env.getLibdir(), "libvlccore.dll").toString());
          System.load(Paths.get(env.getLibdir(), "libvlc.dll").toString());
          Path plugindir = Paths.get(env.getLibdir(), "plugins");
          log.info("PluginDir ---> " + plugindir);
          GUI.getInstance().loadDll(plugindir);
          log.info("VideoLAN Win64    ---> ok!");
        }
        catch (UnsatisfiedLinkError error) {
          log.error(error);
          log.info("VideoLAN Win64    ---> nok");
        }
        return null;
      };
      futureVideolan = executorVideolan.submit(callable);
    }
    else if (env.isLinux()) {
      boolean found = new NativeDiscovery().discover();
      if (found) {
        log.info("VideoLAN            ---> ok!");
      }
      else {
        log.info("VideoLAN            ---> nok");
      }
      // Bugfix Linux futureVideolan darf nicht null sein, weil es sonst in
      // playerInternalframe futureVideolan.get() zu einem NullPointer kommt
      Callable<Void> callable = () -> null;
      futureVideolan = executorVideolan.submit(callable);
    }
    else if (env.isMac()) {
      log.warn("MacOS wird aus lizenzrechtlichen Gründen nicht unterstützt. VideoLAN ---> nok");
      Callable<Void> callable = () -> null;
      futureVideolan = executorVideolan.submit(callable);
    }
    else {
      log.warn("VideoLAN              ---> nok");
    }
    setIconImage(new ImageIcon(resource.getResource(Resource.LOGO_16x16)).getImage());
    addWindowStateListener(event -> {
      if (event.getOldState() == Frame.MAXIMIZED_BOTH && event.getNewState() == Frame.NORMAL) {
        fensterAnordnen();
      }
    });
    addWindowStateListener(new WindowStateListener() {

      @Override
      public void windowStateChanged(WindowEvent e) {
        switch(getExtendedState()) {
          case Frame.NORMAL:
            itemVollbildmodus.setSelected(false);
            playerVector.forEach(player -> {
              player.setPopupIconVollbildmodus(false);
            });
            break;
          case Frame.MAXIMIZED_BOTH:
            // internalframe maximieren
            itemVollbildmodus.setSelected(true);
            playerVector.forEach(player -> {
              player.setPopupIconVollbildmodus(true);
              try {
                player.setMaximum(true);
              }
              catch (PropertyVetoException e1) {
                log.info(e1.getMessage(), e1);
              }
            });
            break;
          default:
            break;
        }
      }

    });
    addWindowListener(new WindowAction() {

      @Override
      public void windowClosing(WindowEvent event) {

        // update wird gerade durchgeführt
        if (!macheKeinUpdate && !unzipped) {
          ImageIcon anaranjado = new ImageIcon(getClass().getResource(Resource.FRAGEZEICHEN_46x48));

          final String JA_VERLASSEN = ja.toString();
          final String NEIN_FORTSETZEN = nein.toString();
          Object[] options = new Object[] {JA_VERLASSEN, NEIN_FORTSETZEN};
          JOptionPane pane = new JOptionPane(
              sollDieLaufendeAktualisierung.toString(), JOptionPane.QUESTION_MESSAGE,
              JOptionPane.DEFAULT_OPTION, anaranjado, options
          );
          List<Component> result = Util.getAllComponents(pane);
          for (Component component : result) {
            if (component instanceof JButton button) {
              if (button.getText().equals(JA_VERLASSEN)) {
                button.setMnemonic(ja.getVirtualKey().ordinal() + 65);
              }
              else if (button.getText().equals(NEIN_FORTSETZEN)) {
                button.setMnemonic(nein.getVirtualKey().ordinal() + 65);
              }
            }
          }

          JInternalFrame question = pane.createInternalFrame(desktop, programmende.toString());
          question.addInternalFrameListener(new InternalFrameAdapter() {
            @Override
            public void internalFrameClosing(InternalFrameEvent event) {
              if (pane.getValue().toString().equals(JA_VERLASSEN)) {
                macheKeinUpdate = true;
                unzipped = false;
                autoupdater.cancel(true);
                JChat.this.processWindowEvent(new WindowEvent(JChat.this, WindowEvent.WINDOW_CLOSING));
                return;
              }
              else if (pane.getValue().toString().equals(NEIN_FORTSETZEN)) {
                // nichts machen
              }
            }
          });
          question.setMaximizable(false);
          question.setResizable(false);
          question.setIconifiable(false);
          question.setClosable(false);
          question.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
          question.setFrameIcon(new ImageIcon(resource.getResource(Resource.FIND)));
          question.show();
          return;
        }
        if (userdata != null) {
          switch(helado) {
            case Arando:
              root.setEis(Sorte.BLAUBEERE);
              break;
            case Fresa:
              root.setEis(Sorte.ERDBEERE);
              break;
            case Limón:
              root.setEis(Sorte.ZITRONE);
              break;
            case Moca:
              root.setEis(Sorte.MOKKA);
              break;
            case Vainilla:
              root.setEis(Sorte.VANILLE);
              break;
            case Yogur:
              root.setEis(Sorte.JOGHURT);
              break;
            default:
              log.warn(helado.toString() + "- diese Eissorte konnte nicht gespeichert werden");
          }
          Thread.startVirtualThread(
              () -> saveGlobalAttributes(
                  root.getDomain(), userdata.getPseudoid(), userdata.getPPassword(), drift, playervolume,
                  iptvvolume, helado, language, chatDownloadDir, chatUploadDir
              )
          );
        }
        int number = 1;
        if (projektorframe != null) {
          projektorframe.doDefaultCloseAction(); // an den Sender schicken
          log.info(number++ + " 1:1 Screen Sharing Receiver geschlossen - ok");
        }

        // Bildschirmfreigabe Teilen
        if (teilenframe != null) {
          teilenframe.doDefaultCloseAction();
          log.info(number++ + " 1:1 Screen Sharing Sender geschlossen - ok");
        }

        if (telkoprojektorframe != null) {
          telkoprojektorframe.closeWindow();
          log.info(number++ + "Videokonferenz Receiver geschlossen - ok");
        }

        if (telefonTeilnehmenFrame != null) {
          if (telefonTeilnehmenFrame.isShared()) {
            telefonTeilnehmenFrame.stopAnimation();
            ImageCaster.getInstance().stopRecording();
            onConferenceVideo(
                userdata.getNickname(), telefonTeilnehmenFrame.getKonferenzname(),
                telefonTeilnehmenFrame.getConferenceKey(), telefonTeilnehmenFrame.isShared()
            );
          }
          log.info(number++ + " Videokonferenz Sender geschlossen - ok");
        }

        if (iptvframe != null) {
          iptvframe.shutdown();
        }
        log.info(number++ + " iptvframe geschlossen - ok");

        speakerTerminate();
        log.info(number++ + " Speaker geschlossen - ok");

        SoundClip.release();
        boolean done = false;

        micTerminate();
        // Telefonkonferenz auflegen senden
        try {
          conference(false); // passiert bei Programm verlassen aus der Telko
        }
        catch (Exception e) {}

        done = false;

        for (Channel channel : channels.getChannels()) {
          channel.hardClosing();
        }

        wsClient.removeWebsocketListener(websocketAction);
        wsClient.shutdown();
        log.info(number++ + " Websocket geschlossen - ok");

        done = false;

        // ohne shutdown bleibt der Port belegt
        if (ftserver != null) ftserver.shutdown();
        executorFileTransferServer.shutdown();
        done = false;
        try {
          done = executorFileTransferServer.awaitTermination(5, TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
          log.error(e.getMessage(), e.getCause());
        }
        finally {
          if (!done) executorFileTransferServer.shutdownNow();
        }

        log.info(number++ + " FTServer geschlossen - ok");

        for (JPlayerInternalFrame frame : playerVector) {
          frame.embeddedVideoplayerShutdown();
          frame.audioplayerRelease();
        }

        log.info(number++ + " Mediaplayer geschlossen - ok");

        done = false;
        try {
          done = executorVideolan.awaitTermination(1, TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
          log.error(e.getMessage(), e.getCause());
        }
        finally {
          if (!done) executorVideolan.shutdownNow();
        }
        log.info(number++ + " Videolan geschlossen - ok");

        done = false;
        try {
          done = executorWatcher.awaitTermination(1, TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
          log.error(e.getMessage(), e.getCause());
        }
        finally {
          if (!done) executorWatcher.shutdownNow();
        }
        log.info(number++ + " Watcher geschlossen - ok");

        done = false;
        try {
          done = executorJersey.awaitTermination(1, TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
          log.error(e.getMessage(), e.getCause());
        }
        finally {
          if (!done) executorJersey.shutdownNow();
        }
        log.info(number++ + " Jersey geschlossen - ok");

        ImageCaster.getInstance().stopRecording();
        log.info(number++ + " ImageCaster geschlossen - ok");

        done = false;
        executorChat.shutdown();
        try {
          done = executorChat.awaitTermination(2, TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
          log.error(e.getMessage(), e.getCause());
        }
        finally {
          if (!done) executorChat.shutdownNow();
        }
        log.info(number++ + " Network geschlossen - ok");

        JerseyClientFactory.shutdown();
        log.info(number++ + " Jersey geschlossen - ok");

        log.info("terminated successfully :-)");
        JChat.this.dispose();
        if (outdated && unzipped) {
          try {
            // Das Update wurde heruntergeladen und ist entpackt
            // batchfile ist eine ausführbare Datei
            Path batchfile = env.unpackUpdate();
            if (env.isWindows()) {
              Runtime.getRuntime().exec(new String[] {"cmd", "/c", batchfile.toString()});
            }
            else if (env.isLinux()) {
              // Das Update wurde heruntergeladen und ist entpackt
              Set<PosixFilePermission> filePermission = PosixFilePermissions.fromString("rwxr-xr-x");
              Files.setPosixFilePermissions(batchfile, filePermission);
              Runtime.getRuntime().exec(new String[] {"xterm", "-e", batchfile.toString()});
            }
          }
          catch (Exception e) {
            log.error(e.getMessage(), e);
          }
        }
        else if (GUI.properties != null) {
          try {
            if (env.isWindows()) {
              Runtime.getRuntime()
                  .exec(new String[] {"taskkill", "/fi", "\"windowtitle", "eq", "windows2\""});
            }
            else if (env.isLinux()) {
              Runtime.getRuntime().exec(new String[] {"pkill", "windows2"});
            }
          }
          catch (IOException e) {
            log.error(e.getMessage(), e);
          }
        }
        try {
          root.setLanguage(rootLanguage);
          configfile.write(root);
        }
        catch (JAXBException | IOException e) {
          log.error(e.getMessage(), e);
        }
        dispose();
        System.exit(0);
      }
    });
    // Bildschirmgröße
    setPreferredSize(JChat.PREFRRED_DIMENSION);
    setMinimumSize(JChat.MINIMUM_SCREENSIZE);
    // zentrieren
    Dimension screensize = toolkit.getScreenSize();
    int midx = (screensize.width - getPreferredSize().width) / 2;
    int midy = (screensize.height - getPreferredSize().height) / 2;
    if (midx < 0 || midy < 0) {
      setPreferredSize(new Dimension(screensize.width, screensize.height));
      midx = 0;
      midy = 0;
      setBounds(midx, midy, getPreferredSize().width, getPreferredSize().height);
    }
    else {
      setBounds(midx, midy, getPreferredSize().width, getPreferredSize().height);
    }

    menubar.setLayout(gridbag);

    setJMenuBar(menubar);

    con.gridwidth = 1;
    con.weightx = 0.0;
    con.insets = new Insets(6, 0, 6, 6);
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(menuJavacomm, con);
    menubar.add(menuJavacomm);
    itemRegistrieren.addActionListener(event -> {
      STATUS status = registryFrame(KEY.BUTTON_ABBRECHEN);
      if (status == STATUS.PROVISIONAL) {
        connect(jLogon.getUrl());
      }
    });
    itemRegistrieren.setEnabled(false);
    itemRegistrieren.setIcon(new ImageIcon(resource.getResource(Resource.SIGNIN_28x28)));
    menuJavacomm.add(itemRegistrieren);

    itemAnmelden.setEnabled(false);
    itemAnmelden.setIcon(new ImageIcon(resource.getResource(Resource.LOGIN_26x26)));
    itemAnmelden.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent event) {
        List<String> domains = null;
        domains = Environment.getInstance().createDomainlist();
        babelfish.remove(jLogon);
        jLogon = new JLogon(
            JChat.this, "Logon", root.getDomain(), domains, root.getEis(), root.getLogin().getPassword(),
            root.getLogin().getEmail()
        );
        // der Modaldialog jLogon enthält alle Anmeldedaten
        jLogon.setKey(KEY.FRAME_LOGIN);
        jLogon.addLanguageListener(eventLanguage -> {
          switchLanguage((ISO639) eventLanguage.getNewValue());
          rootLanguage = ISO6391.fromValue(((ISO639) eventLanguage.getNewValue()).value());
          root.setLanguage(rootLanguage);

        });
        babelfish.add(jLogon);
        root.setLanguage(rootLanguage);
        initLanguage(root);
        jLogon.init();
        jLogon.setVisible(true);

        switch(jLogon.getStatus()) {
          case PROVISIONAL:
            // an dieser Stelle wurde früher der Modemton abgespielt
            // connect aktivieren
            connect(jLogon.getUrl());
            break;
          case DISMISSED:
            // Abbrechen geklickt
            // kann sich im Programm erneut anmelden
            jLogon.setClose();
            menubar.remove(menuAdministrator);
            menubar.revalidate();
            nichtAngemeldet();
            firstInvoke = true;
            break;
          case ELAPSED:
            jLogon.setClose();
            break;
          case GUEST:
            // an dieser Stelle wurde früher der Modemton abgespielt
            // connect aktivieren
            connect(jLogon.getUrl());
            break;
          case WITHOUT_REGISTRATION:
            throw new UnsupportedOperationException(
                STATUS.WITHOUT_REGISTRATION.toString() + " muss noch implementiert werden"
            );
          default:
            break;
        }
      }

    });
    menuJavacomm.add(itemAnmelden);
    menuJavacomm.addSeparator();

    itemNeuesPasswortAnfordern.setIcon(new ImageIcon(resource.getResource(Resource.CRYPTO_26x26)));
    itemNeuesPasswortAnfordern.addActionListener(event -> {
      // ein neues Passwort wird angefordert
      PasswortAnfordernButton pw = new PasswortAnfordernButton();
      registryFrame = new JRegistryFrame(this, pw, passwortAnfordern.toString());
      registryFrame.setKey(KEY.NEUES_PASSWORT_ANFORDERN);
      babelfish.add(pw);
      babelfish.add(registryFrame);
      initLanguage(root);
      registryFrame.addRegisterListener(passwordEvent -> {

        Control value = (Control) passwordEvent.getNewValue();

        switch(value) {
          case OHNE_REGISTRIEREN:
            break;
          case REGISTER:

            // RSA Schlüssel erneut anfordern
            try {
              rsa = readRsaPublicKey();
            }
            catch (Exception e) {

              // Fehlermeldung RSA Schlüssel kann nicht abgeholt werden
              // Verbindungsprobleme Serverleitung

              log.error(e.getMessage());

              break;
            }

            // in einen Thread auslagern
            // Registierenknopf wurde gedrückt.
            // ein SignIn wird durchgeführt.
            // anschließend ein AutoLogin

            // userid/token/rsa verschlüsseln

            // get/renew/password/identity

            // userid gibt es nicht
            // email ist bekannt
            // password gibt es nicht
            // aes ist bekannt

            Token token = new Token();
            token.setEMail(registryFrame.getEmail());
            token.setAES(Crypto.getBase64FromAES(secretAES));
            String encryptedRSA = Crypto.encrypteRSA(token.toString(), rsa);

            ISO639 iso = registryFrame.getLanguage();

            Thread.startVirtualThread(() -> renewPassword(iso, encryptedRSA));

            break;
          default:
            throw new UnsupportedOperationException("unbekannter status," + value.toString());
        }
      });
      registryFrame.setVisible(true);
      registryFrame.removeAllRegisterListener();
      babelfish.remove(pw);
      babelfish.remove(registryFrame);
    });
    menuJavacomm.add(itemNeuesPasswortAnfordern);
    menuJavacomm.addSeparator();

    itemAufWiedersehen.addActionListener(
        (ActionEvent arg0) -> processWindowEvent(new WindowEvent(JChat.this, WindowEvent.WINDOW_CLOSING))
    );
    itemAufWiedersehen.setIcon(new ImageIcon(resource.getResource(Resource.ADIOS_28x28)));
    menuJavacomm.add(itemAufWiedersehen);

    // Menu User

    gridbag.setConstraints(menuKonto, con);
    menubar.add(menuKonto);
    itemBearbeiten.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent event) {
        // in userdata muss dass Passwort entschlüsselt werden
        userdata.setPassword(Crypto.decryptAES(userdata.getPassword(), secretAES));
        updateProfil = new JUpdateProfilFrame(userdata);
        babelfish.add(updateProfil);
        updateProfil.setLanguage(ISO639.fromValue(language.toString()));
        updateProfil.setMailWritable(root.getLogin().getEmail() != null);
        updateProfil.setPasswordWritable(root.getLogin().getPassword() != null);
        updateProfil.addUserListener(new PropertyChangeListener() {

          @Override
          public void propertyChange(PropertyChangeEvent event) {
            Control rc = (Control) event.getNewValue();
            Userdata value = updateProfil.getChangeUser();
            userdata.setPassword(
                value != null ? Crypto.encryptAES(value.getPassword(), secretAES)
                    : Crypto.encryptAES(userdata.getPassword(), secretAES)
            );

            switch(rc) {
              case SAVE:
                updateUser(value);
                break;
              case SAVE_AND_CLOSE:
                updateUser(value);
                updateProfil.removeAllListener();
                babelfish.remove(updateProfil);
                itemBearbeiten.setEnabled(true);
                break;
              case CLOSE:
                updateProfil.removeAllListener();
                babelfish.remove(updateProfil);
                itemBearbeiten.setEnabled(true);
                break;
              default:
                break;
            }
          }



          private void updateUser(Userdata value) {
            Token token = new Token();
            UPDATEUSER updateuser = new UPDATEUSER();
            updateuser.setCommand(Command.UPDATEUSER);
            updateuser.setHeader(REQUEST);
            updateuser.setDataset(Protocol.DATASET);
            updateuser.setBackgroundColor(value.getBackgroundColor());
            updateuser.setForegroundColor(value.getForegroundColor());
            updateuser.setNickname(value.getNickname());
            token.setEMail(value.getEmail());
            token.setPassword(value.getPassword());
            token.setPseudoid(value.getPseudoid());
            updateuser.setIdentity(Crypto.encryptAES(token.toString(), secretAES));
//            log.info("pseudoid {}", value.getPseudoid());
//            log.info("token {}", token.toString());
//            log.info("-----> " + updateuser.toString());
            executorChat.execute(() -> wsClient.sendMessage(updateuser));
          }

        });
        Rectangle localRectangle = getBounds();
        updateProfil.setLocation(
            (localRectangle.width - JUpdateProfilFrame.WIDTH) / 2,
            (localRectangle.height - JUpdateProfilFrame.HEIGHT) / 4
        );
        desktop.add(updateProfil, JLayeredPane.MODAL_LAYER, 0);
        itemBearbeiten.setEnabled(false);
        updateProfil.show();
      }

    });
    itemBearbeiten.setEnabled(false);
    itemBearbeiten.setIcon(new ImageIcon(resource.getResource(Resource.PERSONAL)));
    menuKonto.add(itemBearbeiten);

    menuKonto.add(menuEis);
    menuEis.setIcon(new ImageIcon(resource.getResource(Resource.FARBSCHEMA_26x26)));
    menuEis.add(itemMokka);
    itemMokka.addActionListener(event -> {
      try {
        setColorSample(Sorte.MOKKA);
      }
      catch (UnsupportedLookAndFeelException e1) {
        log.error(e1.getMessage(), e1.getCause());
      }
    });
    menuEis.add(itemVanille);
    itemVanille.addActionListener(event -> {
      try {
        setColorSample(Sorte.VANILLE);
      }
      catch (UnsupportedLookAndFeelException e1) {
        log.error(e1.getMessage(), e1.getCause());
      }

    });
    menuEis.add(itemJoghurt);
    itemJoghurt.addActionListener(event -> {
      try {
        setColorSample(Sorte.JOGHURT);
      }
      catch (UnsupportedLookAndFeelException e1) {
        log.error(e1.getMessage(), e1.getCause());
      }
    });
    menuEis.add(itemBlaubeere);
    itemBlaubeere.addActionListener(event -> {
      try {
        setColorSample(Sorte.BLAUBEERE);
      }
      catch (UnsupportedLookAndFeelException e1) {
        log.error(e1.getMessage(), e1.getCause());
      }
    });
    menuEis.add(itemErdbeere);
    itemErdbeere.addActionListener(event -> {
      try {
        setColorSample(Sorte.ERDBEERE);
      }
      catch (UnsupportedLookAndFeelException e1) {
        log.error(e1.getMessage(), e1.getCause());
      }
    });
    menuEis.add(itemZitrone);
    itemZitrone.addActionListener(event -> {
      try {
        setColorSample(Sorte.ZITRONE);
      }
      catch (UnsupportedLookAndFeelException e1) {
        log.error(e1.getMessage(), e1.getCause());
      }
    });

    groupEis.add(itemMokka);
    groupEis.add(itemVanille);
    groupEis.add(itemJoghurt);
    groupEis.add(itemBlaubeere);
    groupEis.add(itemErdbeere);
    groupEis.add(itemZitrone);
    // Das Farbschema wird gewählt. Auch für das Logon
    switchColorTheme(root.getEis());

    // MenuChat
    gridbag.setConstraints(menuChat, con);

    menubar.add(menuChat);
    itemLaunch.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent arg0) {
        itemLaunch.setEnabled(false);
        launcher = new JLauncher();
        launcher.setIconifiable(true);

        windowAnrufen.addWindowAnrufenListener(launcher);
        anrufenEvent(launcher);
        launcher.setRestriction(itemChatanfragenAblehnen.isSelected());
        launcher.setFrameIcon(new ImageIcon(getClass().getClassLoader().getResource(WMResource.LAUNCH)));
        launcher.setLanguage(ISO639.fromValue(rootLanguage.value()));
        launcher.addSelectListener(new PropertyChangeListener() {

          @Override
          public void propertyChange(PropertyChangeEvent event) {
            Control rc = (Control) event.getNewValue();
            switch(rc) {
              case TOPIC:
                itemBesprechungsraum.setEnabled(false);
                besprechungsraumEinrichten(userdata.getPseudoid(), userdata.getNickname());

                break;
              case PRIVATECHAT:
                ActionEvent actionEvent = new ActionEvent(
                    itemPrivateTalk, ActionEvent.ACTION_PERFORMED, "1:1 Chat", System.currentTimeMillis(), 16
                );
                itemOneListener.actionPerformed(actionEvent);
                break;
              case REQUEST:
                // Anfrage
                Room room = (Room) event.getOldValue();
                ENTERROOM enterroom = new ENTERROOM();
                enterroom.setCommand(Command.ENTERROOM);
                enterroom.setHeader(REQUEST);
                enterroom.setDataset(Protocol.DATASET);
                enterroom.setPseudoid(userdata.getPseudoid());
                enterroom.setNickname(userdata.getNickname());
                enterroom.setRoom(room.getChatid());
//                log.info("-----> " + enterroom.toString());
                executorChat.execute(() -> wsClient.sendMessage(enterroom));
                break;
              case CLOSE:
                frameVector.stream()
                    .filter(
                        chat -> chat.getType() == Frames.BESPRECHNUNGSRAUM
                            || chat.getType() == Frames.PRIVATE_CHAT
                            || chat.getType() == Frames.FORUM
                            || chat.getType() == Frames.GRUPPENRAUM
                            || chat.getType() == Frames.PAUSENRAUM

                    ).map(m -> (JChatFrame) m).collect(Collectors.toList())
                    .forEach(JChatFrame::doDefaultCloseAction);

                windowAnrufen.removeWindowAnrufenListener(launcher);
                removeWM(launcher);
                frameVector.remove(launcher);
                babelfish.remove(launcher);
                launcher.removeAllListener();
                itemLaunch.setEnabled(true);
                break;
              case SELECTED:
                selectFrame(event.getPropertyName());
                break;
              case ROOM:
                pausenraumEinrichten();
                break;
              case GROUPROOM:
                gruppenraumEinrichten();
                break;
              case RESTRICTION:
                itemChatanfragenAblehnen.setSelected((Boolean) event.getOldValue());
                ActionEvent test = new ActionEvent(
                    itemChatanfragenAblehnen, ActionEvent.ACTION_PERFORMED, "Incoming call restriction"
                );
                restrictionListener.actionPerformed(test);
                break;
              case MOUSE_EXITED:
                break;
              default:
                throw new UnsupportedOperationException(rc.toString());
            }
          }

        });

        babelfish.add(launcher);

        try {
          executorChat.execute(() -> {
            ROOMLIST roomlist = new ROOMLIST();
            roomlist.setCommand(Command.ROOMLIST);
            roomlist.setHeader(REQUEST);
            wsClient.sendMessage(roomlist);
          });
        }
        catch (RejectedExecutionException e) {
          log.warn("executorChat.shutdown()");
          log.info("Fenster einblenden neu anmelden");
          return;
        }
        // launcher.setRecordList(recordList); // recordlist hängt von der
        // Antwort ab!
        Rectangle localRectangle = getBounds();
        launcher.setLocation(
            (localRectangle.width - JLauncher.WIDTH) / 2, (localRectangle.height - JLauncher.HEIGHT) / 4
        );
        desktop.add(launcher, JLayeredPane.DEFAULT_LAYER, CHAT_LAYER);
        launcher.setEnabled1OneChat(true);

        setBackgroundTreeview(helado, launcher);
        launcher.show();
        frameVector.addElement(launcher);
        insertWM(launcher, launcher.getFrameTitleId());
        if (!itemBesprechungsraum.isEnabled()) launcher.setEnabledBesprechungsraum(false);
        if (!itemPrivateTalk.isEnabled()) launcher.setEnabled1OneChat(false);
        if (!itemPausenraum.isEnabled()) launcher.setEnabledPausenraum(false);
      }

    });
    itemLaunch.setIcon(new ImageIcon(getClass().getClassLoader().getResource(WMResource.LAUNCH)));
    itemLaunch.setEnabled(false);
    menuChat.add(itemLaunch);
    menuChat.addSeparator();

    itemForum.setIcon(new ImageIcon(getClass().getClassLoader().getResource(WMResource.FORUM_26x26)));
    itemForum.addActionListener(event -> {

      itemForum.setEnabled(false);
      forum = new JForum();
      forum.setMinuten(readSchwellenwert(root.getDomain()));
      forum.setClosable(true);
      forum.setSize(new Dimension(JForum.WIDTH, JForum.HEIGHT));
      forum.setResizable(true);
      forum.setIconifiable(false);
      forum.setFrameIcon(new ImageIcon(getClass().getClassLoader().getResource(WMResource.FORUM_26x26)));
      babelfish.add(forum);
      initLanguage(root);

      Rectangle localRectangle = getBounds();

      forum.setLocation(
          (localRectangle.width - JForum.WIDTH) / 2, (localRectangle.height - JForum.HEIGHT) / 4
      );
      desktop.add(forum, JLayeredPane.MODAL_LAYER, 4);
      forum.show();
      forum.addForumListener((PropertyChangeEvent bla) -> {
        Control value = (Control) bla.getNewValue();
        switch(value) {
          case CLOSE:
            forum.removeAllListener();
            itemForum.setEnabled(true);
            babelfish.remove(forum);
            break;
          default:
            break;
        }
      });

    });
    menuChat.add(itemForum);

    itemBesprechungsraum.setIcon(new ImageIcon(resource.getResource(Resource.BESPRECUNGSRAUM_35x26)));
    itemBesprechungsraum.setEnabled(false);
    menuChat.add(itemBesprechungsraum);
    createRoomListener = new PropertyChangeListener() {

      @Override
      public void propertyChange(PropertyChangeEvent event) {
        switch((Control) event.getNewValue()) {
          case CREATE:
            // Themenraum
            Runnable runnable = () -> {
              CREATETEMPROOM topic = new CREATETEMPROOM();
              topic.setCommand(Command.CREATETEMPROOM);
              topic.setHeader(REQUEST);
              topic.setDataset(Protocol.DATASET);
              topic.setRoom(event.getPropertyName());
              topic.setPseudoid(userdata.getPseudoid());
              topic.setNickname(userdata.getNickname());
              topic.setEntry(Entry.PROTECTED);
              topic.setLifetime(Lifetime.PERMANENT);
              topic.setRoomtype(Roomtype.BESPRECHUNGSRAUM);
              wsClient.sendMessage(topic);
            };
            executorChat.execute(runnable);
            break;
          case DELETE:
            DELETEROOM request = new DELETEROOM();
            request.setCommand(Command.DELETEROOM);
            request.setHeader(REQUEST);
            request.setDataset(Protocol.DATASET);
            request.setRoom(event.getPropertyName());
            request.setPseudoid(userdata.getPseudoid());
            executorChat.execute(() -> wsClient.sendMessage(request));
            break;
          case UPDATE_CHATUSER:
            List<String> myFriends = besprechungsraum.getNicknames();
            int count = myFriends.size();
            ChatUser[] chatuser = new ChatUser[count];
            for (int index = 0; index < count; index++) {
              chatuser[index] = new ChatUser();
              chatuser[index].setNickname(myFriends.get(index));
            }
            updateChatuser(besprechungsraum.getRoom(), Roomtype.BESPRECHUNGSRAUM, chatuser);
            // neue Referenzliste erstellen
            break;
          case CLOSE:
            if (launcher != null) launcher.setEnabledBesprechungsraum(true);
            itemBesprechungsraum.setEnabled(true);
            besprechungsraum.removeAllListener();
            babelfish.remove(besprechungsraum);
            break;
          case TOPICMEMBER:
            requestRaummitglieder(event.getPropertyName());
            break;
          default:
            break;
        }
      }
    };
    itemBesprechungsraum.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent arg0) {
        if (launcher != null) launcher.setEnabledBesprechungsraum(false);
        itemBesprechungsraum.setEnabled(false);
        besprechungsraumEinrichten(userdata.getPseudoid(), userdata.getNickname());
      }
    });

    itemPausenraum.setEnabled(false);
    itemPausenraum.setIcon(new ImageIcon(resource.getResource(Resource.PAUSENRAUM)));
    itemPausenraum.addActionListener(arg0 -> {
      if (launcher != null) launcher.setEnabledPausenraum(false);
      pausenraumEinrichten();
    });
    menuChat.add(itemPausenraum);

    itemGruppenraum.setIcon(new ImageIcon(resource.getResource(Resource.GRUPPENRAUM_25x26)));
    itemGruppenraum.setEnabled(false);
    itemGruppenraum.addActionListener(event -> {
      gruppenraumEinrichten();
    });
    menuChat.add(itemGruppenraum);

    menuChat.addSeparator();
    itemPrivateTalk.setEnabled(false);
    itemOneListener = new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent event) {

        if (launcher != null) launcher.setEnabled1OneChat(false);
        itemPrivateTalk.setEnabled(false); // muss später wieder aktiviert
        // werden
        personChat = new JPrivateChat();
//        windowAnrufen.addWindowAnrufenListener(personChat);
        anrufenEvent(personChat);
        personChat.setSize(JPrivateChat.WIDTH, JPrivateChat.HEIGHT);
        personChat.setMinimumSize(new Dimension(JPrivateChat.WIDTH, JPrivateChat.HEIGHT));
        personChat.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        Rectangle rect = getBounds();
        personChat
            .setLocation((rect.width - JPrivateChat.WIDTH) / 2, (rect.height - JPrivateChat.HEIGHT) / 4);
        desktop.add(personChat, JLayeredPane.DEFAULT_LAYER, 10);
        personChat.setMaximizable(false);
        personChat.setResizable(true);
        personChat.setFrameIcon(new ImageIcon(resource.getResource(Resource.Y2_KUSER)));
        personChat.setIconifiable(false);
        personChat.setClosable(true);
        personChat.addPersonListener(new PropertyChangeListener() {

          @Override
          public void propertyChange(PropertyChangeEvent event) {
            Control returncode = (Control) event.getNewValue();
            switch(returncode) {
              case QUIT:
                personChat.removePersonListener(this);
                itemPrivateTalk.setEnabled(true);
                if (launcher != null) launcher.setEnabled1OneChat(true);
                // log.info("personchat wird hier auf null gesetzt");
//                personChat = null;
                babelfish.remove(personChat);
//                windowAnrufen.removeWindowAnrufenListener(personChat);
                personChat = null;
                break;
              case HIT:
                // RemoteUID und IP sind ind jetzt nicht vorhanden
                // dafür RemeoteNickname
                // über den Nickname wird die Session ID ermittelt

                // der 1:1 Chat oder PrivateChat wird durch CALLPRIVATECHAT
                // eingeleitet
                callPrivateChat(userdata.getPseudoid(), userdata.getNickname(), event.getPropertyName());
                break;
              default:
                break;
            }
          }

        }); // muss später wieder aktiviert werden
        babelfish.add(personChat);
//        frameVector.add(personChat); darf nicht hinzugefüget werden, weil personChat kein JChatFrame ist
        personChat.setLanguage(ISO639.fromValue(rootLanguage.value()));
        personChat.show();
        personChat.setFocus();
        personChat.setUserOnlinelist(userOnlinelist);
      }

    };
    itemPrivateTalk.addActionListener(itemOneListener);
    itemPrivateTalk.setIcon(new ImageIcon(resource.getResource(Resource.PRIVAT_26x26)));
    menuChat.add(itemPrivateTalk);
    menuChat.addSeparator();
    itemChatanfragenAblehnen.setEnabled(false);
    restrictionListener = new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent event) {
        restrictionOn = itemChatanfragenAblehnen.isSelected();

        Thread.startVirtualThread(
            () -> saveChatdenied(root.getDomain(), userdata.getPseudoid(), restrictionOn)
        );

        // restrictionOn immer schreiben
        if (launcher != null) launcher.setRestriction(restrictionOn);
      }

    };
    itemChatanfragenAblehnen.addActionListener(restrictionListener);
    // itemRestriction.setIcon(new
    // ImageIcon(resource.getResource(Resource.PHONE)));
    itemChatanfragenAblehnen.setIcon(new ImageIcon(resource.getResource(Resource.NO_CELL_PHONE_25X25)));
    menuChat.add(itemChatanfragenAblehnen);

    // Menu FTS
    gridbag.setConstraints(menuOnlinetransfer, con);
    menubar.add(menuOnlinetransfer);

    itemServerKonfigurieren.setIcon(new ImageIcon(resource.getResource(Resource.CONFIGURE)));
    itemServerKonfigurieren.setEnabled(false);
    itemServerKonfigurieren.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent event) {
        FileTransferService fts = root.getFileTransferService();
        if (fts == null) {
          fts = new FileTransferService();
          root.setFileTransferService(fts);
        }
        itemServerKonfigurieren.setEnabled(false);
        serverKonfigurierenFrame = new JServerKonfigurierenFrame();
        serverKonfigurierenFrame.setPort(transferConfig.getPort());
        serverKonfigurierenFrame.setStarttype(transferConfig.getStarttype());
        serverKonfigurierenFrame.setFrameIcon(new ImageIcon(resource.getResource(Resource.CONFIGURE)));
        serverKonfigurierenFrame.setClosable(true);
        serverKonfigurierenFrame
            .setSize(new Dimension(JServerKonfigurierenFrame.WIDTH, JServerKonfigurierenFrame.HEIGHT));
        serverKonfigurierenFrame.setResizable(false);
        serverKonfigurierenFrame.setIconifiable(false);
        serverKonfigurierenFrame.addPeer2PeerListener(new PropertyChangeListener() {

          @Override
          public void propertyChange(PropertyChangeEvent event) {
            Control rc = (Control) event.getNewValue();
            switch(rc) {
              case START:

                if (userdata == null) {
                  Runnable runnable = () -> {
                    serverKonfigurierenFrame.startStop(false);
                    JOptionPane.showMessageDialog(
                        JChat.this,
                        "<html>" + "<head/>"
                            + "<body>"
                            + "<b><span style=\"color:red\">"
                            + "Du bist nicht angemeldet."
                            + "</span></b><br>"
                            + "</body>"
                            + "</html>",
                        "Onlinetransfer", JOptionPane.ERROR_MESSAGE
                    );
                  };
                  EventQueue.invokeLater(runnable);
                  return;
                }

                // startFTSErver
                final int port = serverKonfigurierenFrame.getPort();
                try {
                  if (ftserver.isAlive()) {
                    ftserver.shutdown();
                  }
                  ftserver = new FTServer(port);
                  executorFileTransferServer.execute(ftserver);
                  serverKonfigurierenFrame.startStop(true);
                }
                catch (PortException e1) {
                  log.error(e1.getMessage(), e1.getCause());
                  Runnable runnable = () -> {
                    JOptionPane.showMessageDialog(
                        JChat.this,
                        "<html>" + "<head/>"
                            + "<body>"
                            + "<b><span style=\"color:red\">"
                            + e1.getMessage()
                            + "</span></b><br>"
                            + "</body>"
                            + "</html>",
                        "Onlinetransfer", JOptionPane.ERROR_MESSAGE
                    );
                  };
                  EventQueue.invokeLater(runnable);
                  return;
                }
                catch (BindException e1) {
                  JOptionPane.showMessageDialog(
                      JChat.this,
                      "<html> Der Port <b>" + String.valueOf(port)
                          + "</b> ist von einem anderen Dienst belegt.</html>",
                      "Server konfigurieren", JOptionPane.ERROR_MESSAGE
                  );
                }
                catch (IOException e1) {
                  log.error(e1.getMessage(), e1.getCause());
                }

                UPDATEPORT updateport = new UPDATEPORT();
                updateport.setCommand(Command.UPDATEPORT);
                updateport.setHeader(REQUEST);
                updateport.setDataset(Protocol.DATASET);
                updateport.setIp(myIP);
                updateport.setPort(port);
                updateport.setPseudoid(userdata.getPseudoid());
                executorChat.execute(() -> wsClient.sendMessage(updateport));
                break;
              case STOP:
                shutdownFTServer(ftserver, serverKonfigurierenFrame);
                if (ftsFrame != null) {
                  ftsFrame.ftsIsDown();
                }
                serverKonfigurierenFrame.startStop(false);
                break;
              case CLOSE:
                itemServerKonfigurieren.setEnabled(true);
                serverKonfigurierenFrame.removeAllListener();
                serverKonfigurierenFrame.removePeer2PeerListener(this);
                babelfish.remove(serverKonfigurierenFrame);
                break;
              case SAVE:
                // root muss aktualisiert werden, sonst NullPointerException
                // weil zuerst JFTSErviceFrame aufgerufen wurde und danach
                // JPeer2PeerFrame(Port
                // setzen und Starttyp)

                // neue Werte unbedingt Speichern bzw aktualisieren

                final TransferFiletransferConfig help = new TransferFiletransferConfig();
                help.setPort(serverKonfigurierenFrame.getPort());
                help.setStarttype(Starttype.toTransferStarttype(serverKonfigurierenFrame.getStarttype()));
                help.setPseudoid(transferConfig.getPseudoid());

                Thread.startVirtualThread(() -> saveFiletransferConfig(help));

                itemServerKonfigurieren.setEnabled(true);
                serverKonfigurierenFrame.removeAllListener();
                serverKonfigurierenFrame.removePeer2PeerListener(this);
                babelfish.remove(serverKonfigurierenFrame);
                break;
              default:
                log.warn("not implemented yet");
                break;

            }

            // root Speichern
          }

        });
        babelfish.add(serverKonfigurierenFrame);
        serverKonfigurierenFrame.setLanguage(ISO639.fromValue(rootLanguage.value()));
        serverKonfigurierenFrame.init();
        serverKonfigurierenFrame.setEissorte(helado);
        Rectangle localRectangle = getBounds();
        serverKonfigurierenFrame.setLocation(
            (localRectangle.width - JServerKonfigurierenFrame.WIDTH) / 2,
            (localRectangle.height - JServerKonfigurierenFrame.HEIGHT) / 4
        );
        desktop.add(serverKonfigurierenFrame, JLayeredPane.MODAL_LAYER, 4);
        serverKonfigurierenFrame.show();
        serverKonfigurierenFrame.startStop(ftserver.isAlive());
      }

    });
    menuOnlinetransfer.add(itemServerKonfigurieren);
    itemHochladenRunterladen.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent event) {
        itemHochladenRunterladen.setEnabled(false);
        ftsFrame = new JFTServiceFrame(root);
        windowAnrufen.addWindowAnrufenListener(ftsFrame);
        anrufenEvent(ftsFrame);
        ftsFrame.setLanguage(ISO639.fromValue(rootLanguage.value()));
        ftsFrame.setFrameIcon(new ImageIcon(resource.getResource(Resource.FTP)));
        ftsFrame.addFileTransferListener(new PropertyChangeListener() {

          @Override
          public void propertyChange(PropertyChangeEvent event) {
            Control selector = (Control) event.getNewValue();
            switch(selector) {
              case UPLOADFILE:
                if (ftserver.isAlive()) {
                  // watch mit der Überwachung beginnen
                  log.info("mit der Überwachung beginnen, UPLOADFILES wird verzögert ausgeführt");
                  try {
                    watcher.setDirectory(Paths.get(root.getFileTransferService().getUpload()));
                  }
                  catch (org.nexuswob.util.NoDirectoryException e) {
                    log.error(e.getMessage(), e);
                  }
                }
                else {
                  ftsFrame.ftsIsDown();
                  JOptionPane.showMessageDialog(
                      JChat.this, serverpublic.toString(), menuOnlinetransfer.getText(),
                      JOptionPane.INFORMATION_MESSAGE
                  );

                  itemServerKonfigurieren.doClick();
                }
                break;
              case STOP:
                Runnable task1 = () -> {
                  DELETEUPLOADFILES deletefiles = new DELETEUPLOADFILES();
                  deletefiles.setCommand(Command.DELETEUPLOADFILES);
                  deletefiles.setHeader(REQUEST);
                  deletefiles.setDataset(Protocol.DATASET);
                  deletefiles.setIp(myIP);
                  deletefiles.setPseudoid(userdata.getPseudoid());
                  wsClient.sendMessage(deletefiles);
                };
                executorChat.execute(task1);
                break;
              case EXIT_ON_CLOSE:
                // log.info("alle Übertragungen werden abgebrochen; alle
                // FTClients werden
                // geschlossen");
                // log.info("das Hauptfenster wird geschlossen");

                // root muss in der config.xml aktualisiert werden!!!!!!!!!!
                // die Einstellungen können sich geändert haben
                // betroffen sind downloadDir und UploadDir

                // neue Werte in der Datenbank speichern!!!!!!!!!!!
                TransferFiletransferConfig config = new TransferFiletransferConfig();
                config.setPseudoid(userdata.getPseudoid());
                config.setDownloadDir(root.getFileTransferService().getDownload());
                config.setUploadDir(root.getFileTransferService().getUpload());

                Thread.startVirtualThread(() -> saveDirectories(config));

                removeWM(ftsFrame);
                frameVector.remove(ftsFrame);
                itemHochladenRunterladen.setEnabled(true);
                if (userdata == null) {
                  log.warn("userdata is null ---> user is not logged-in");
                  break;
                }
                DELETEUPLOADFILES request = new DELETEUPLOADFILES();
                request.setCommand(Command.DELETEUPLOADFILES);
                request.setHeader(REQUEST);
                request.setDataset(Protocol.DATASET);
                request.setIp(myIP);
                request.setPseudoid(userdata.getPseudoid());
                executorChat.execute(() -> wsClient.sendMessage(request));
                babelfish.remove(ftsFrame);
                windowAnrufen.removeWindowAnrufenListener(ftsFrame);
                break;
              case DELETEUPLOAD:
                deleteServerFiles();
                break;
              case SEARCH:
                @SuppressWarnings("unchecked")
                Enumeration<String> enums = (Enumeration<String>) event.getOldValue();
                ArrayList<String> enumlist = new ArrayList<>(4);
                while (enums.hasMoreElements()) {
                  enumlist.add(enums.nextElement());
                }
                Thread.startVirtualThread(() -> searchfiles(enumlist.toArray(new String[enumlist.size()])));
                break;
              case DISCONNECT:
                log.info("DISCONNECT");

                Transport transport = (Transport) event.getOldValue();
                log.info(transport.getSlot());

                // finde den Channel
                synchronized (this) {
                  for (Channel channel : channels.getChannels()) {
                    if (channel.getSlot().getSlotnumber() != transport.getSlot()) continue;
                    // channel gefunden
                    channel.softClosing();
                    break;
                  }
                }

                break;
              case TRANSMIT:
                transport = (Transport) event.getOldValue();
                boolean done = false;
                Channel occupiedChannel = null;
                synchronized (this) {
                  for (Channel channel : channels.getChannels()) {
                    done = done || channel.getSlot().getSlotnumber() == transport.getSlot();
                    if (done) {
                      // diesen Filename wieder eintragen
                      occupiedChannel = channel;
                      break;
                    }
                  }
                }
                if (done) {
                  JOptionPane.showMessageDialog(
                      JChat.this, "<html>" + derKanalIstBelegt.toString() + "</html>",
                      menuOnlinetransfer.getText(), JOptionPane.INFORMATION_MESSAGE
                  );
                  ftsFrame.setOccupied(occupiedChannel);
                  break;
                }
                DOWNLOAD download = new DOWNLOAD();
                download.setCommand(Command.DOWNLOAD);
                download.setHeader(REQUEST);
                download.setDataset(Protocol.DATASET);
                download.setFilename(transport.getFilename());
                download.setIndex(transport.getIndex());
                download.setSlot(transport.getSlot());
                download.setPseudoid(userdata.getPseudoid());
                executorChat.execute(() -> wsClient.sendMessage(download));
                break;
              case SELECTED:
                selectFrame(event.getPropertyName());
                break;
              default:
                log.info("unbekannt," + selector);
                break;
            }
          }

        });
        Rectangle localRectangle = getBounds();
        ftsFrame.setLocation(
            (localRectangle.width - JFTServiceFrame.SCREEN_WIDTH) / 2,
            (localRectangle.height - JFTServiceFrame.SCREEN_HEIGHT) / 4 - 18
        );
        desktop.add(ftsFrame, JLayeredPane.DEFAULT_LAYER, CHAT_LAYER);
        babelfish.add(ftsFrame);
        ftsFrame.show();
        frameVector.addElement(ftsFrame);
        insertWM(ftsFrame, ftsFrame.getFrameTitleId());
      }

    });
    itemHochladenRunterladen.setIcon(new ImageIcon(resource.getResource(Resource.FTP)));
    itemHochladenRunterladen.setEnabled(false);
    menuOnlinetransfer.add(itemHochladenRunterladen);

    itemSchwarzeListe.setIcon(new ImageIcon(resource.getResource(Resource.SCHWARZE_LISTE)));
    itemSchwarzeListe.setEnabled(false);
    itemSchwarzeListe.addActionListener(event -> {
      schwarzeliste = new JSchwarzeListeDialog();
      windowAnrufen.addWindowAnrufenListener(schwarzeliste);
      anrufenEvent(schwarzeliste);
      schwarzeliste.setFrameIcon(new ImageIcon(resource.getResource(Resource.SCHWARZE_LISTE)));
      schwarzeliste.setClosable(true);
      schwarzeliste.setResizable(true);
      schwarzeliste.setSize(new Dimension(JSchwarzeListeDialog.WIDTH, JSchwarzeListeDialog.HEIGHT));
      schwarzeliste.setFrameTitleId(Frames.SCHWARZE_LISTE.toString());
      schwarzeliste.setIconifiable(false);
      schwarzeliste.addBlacklistListener(propertyEvent -> {
        Control value = (Control) propertyEvent.getNewValue();
        switch(value) {
          case SAVE_AND_CLOSE:
            Runnable taskUpdate = () -> {
              UPDATEFILETYPES types = new UPDATEFILETYPES();
              types.setCommand(Command.UPDATEFILETYPES);
              types.setHeader(REQUEST);
              types.setDataset(Protocol.DATASET);
              types.setPseudoid(userdata.getPseudoid());
              types.setFiletypes(schwarzeliste.getSperrliste());
              wsClient.sendMessage(types);
            };
            executorChat.execute(taskUpdate);
            schwarzeliste.close();
            babelfish.remove(schwarzeliste);
            windowAnrufen.removeWindowAnrufenListener(schwarzeliste);
            break;
          case CLOSE:
            removeWM(schwarzeliste);
            frameVector.remove(schwarzeliste);
            schwarzeliste.removeAllListener();
            babelfish.remove(schwarzeliste);
            windowAnrufen.removeWindowAnrufenListener(schwarzeliste);
            break;
          case SELECTED:
            selectFrame(propertyEvent.getPropertyName());
            break;
          default:
            break;
        }
      });
      Rectangle localRectangle = getBounds();
      schwarzeliste.setLocation(
          (localRectangle.width - JSchwarzeListeDialog.WIDTH) / 2,
          (localRectangle.height - JSchwarzeListeDialog.HEIGHT) / 4
      );
      desktop.add(schwarzeliste, JLayeredPane.DEFAULT_LAYER, 4);
      babelfish.add(schwarzeliste);
      schwarzeliste.setLanguage(ISO639.fromValue(rootLanguage.value()));
      setBackgroundTreeview(helado, schwarzeliste);
      schwarzeliste.show();
      frameVector.add(schwarzeliste);
      insertWM(schwarzeliste, schwarzeliste.getFrameTitleId());

      Runnable taskSchwarzeliste = () -> {
        FILETYPES filetypes = new FILETYPES();
        filetypes.setCommand(Command.FILETYPES);
        filetypes.setHeader(REQUEST);
        filetypes.setDataset(Protocol.DATASET);
        filetypes.setPseudoid(userdata.getPseudoid());
        wsClient.sendMessage(filetypes);
      };
      executorChat.execute(taskSchwarzeliste);

    });
    menuOnlinetransfer.add(itemSchwarzeListe);

    // Menu Mediaplayer
    gridbag.setConstraints(menuMediaPlayer, con);
    menubar.add(menuMediaPlayer);
    itemTitel.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent event) {
        FileNameExtensionFilter mp3 = new FileNameExtensionFilter("MP3 Audio (*.mp3)", PlayerFilter.MP3);
        FileNameExtensionFilter mp4 = new FileNameExtensionFilter(
            "MP4 QuickTime Video (*.mov, *.mp4, *.3gp, *.3g2, *.mj2)", PlayerFilter.QUICKTIME
        );
        FileNameExtensionFilter avi = new FileNameExtensionFilter("AVI Video (*.avi)", PlayerFilter.AVI);
        FileNameExtensionFilter aac = new FileNameExtensionFilter("ADTS Audio (*.aac)", PlayerFilter.ADTS);
        FileNameExtensionFilter flac = new FileNameExtensionFilter("FLAC (*.flac)", PlayerFilter.FLAC);
        FileNameExtensionFilter dav = new FileNameExtensionFilter(
            "DVR365 Video Format (*.dav)", PlayerFilter.DAV
        );
        FileNameExtensionFilter webm = new FileNameExtensionFilter("WEBM Video (*.webm)", PlayerFilter.WEBM);
        FileNameExtensionFilter mpeg = new FileNameExtensionFilter(
            "MPEG-1/2 Video (*.mpg, *.mpeg)", PlayerFilter.MPEG
        );
        FileNameExtensionFilter matroska = new FileNameExtensionFilter(
            "Matroska Video (*.mkv)", PlayerFilter.MATROSKA
        );
        FileNameExtensionFilter mpeg4 = new FileNameExtensionFilter("MPEG4-Audio (*.m4a)", PlayerFilter.M4A);
        FileNameExtensionFilter ogg = new FileNameExtensionFilter(
            "Ogg Vorbis Audio (*.ogg)", PlayerFilter.OGG
        );
        FileNameExtensionFilter ogv = new FileNameExtensionFilter(
            "Ogg Vorbis Video (*.ogv)", PlayerFilter.OGV
        );
        FileNameExtensionFilter wave = new FileNameExtensionFilter("WAVE (*.wav)", PlayerFilter.WAVE);

        FileNameExtensionFilter wmv = new FileNameExtensionFilter(
            "Windows Media Video (*.wmv)", PlayerFilter.WMV
        );

        FileNameExtensionFilter alle = new FileNameExtensionFilter(alleFormate.toString(), PlayerFilter.ALLE);

        mediaplayerChooser = new MediaFileChooser();
        if (root.getMediaplayer().getSinglefile() == null) {
          mediaplayerChooser.setCurrentDirectory(chatDownloadDir != null ? new File(chatDownloadDir) : null);
        }
        else {
          mediaplayerChooser.setCurrentDirectory(new File(root.getMediaplayer().getSinglefile()));
        }
        mediaplayerChooser.setAcceptAllFileFilterUsed(false);
        mediaplayerChooser.setDialogTitle(titelMarkieren.toString());
        mediaplayerChooser.addChoosableFileFilter(alle);
        mediaplayerChooser.addChoosableFileFilter(aac);
        mediaplayerChooser.addChoosableFileFilter(avi);
        mediaplayerChooser.addChoosableFileFilter(dav);
        mediaplayerChooser.addChoosableFileFilter(flac);
        mediaplayerChooser.addChoosableFileFilter(matroska);
        mediaplayerChooser.addChoosableFileFilter(mp3);
        mediaplayerChooser.addChoosableFileFilter(mp4);
        mediaplayerChooser.addChoosableFileFilter(mpeg);
        mediaplayerChooser.addChoosableFileFilter(mpeg4);
        mediaplayerChooser.addChoosableFileFilter(ogg);
        mediaplayerChooser.addChoosableFileFilter(ogv);
        mediaplayerChooser.addChoosableFileFilter(wave);
        mediaplayerChooser.addChoosableFileFilter(webm);
        mediaplayerChooser.addChoosableFileFilter(wmv);
        if (root.getMediaplayer().getSinglefile() == null) {
          // kein config Eintrag
          root.getMediaplayer().setSinglefile(System.getProperty("user.home"));
          mediaplayerChooser.setFileFilter(alle);
        }
        else {
          Path mediaplayerPath = Paths.get(root.getMediaplayer().getSinglefile());
          if (PlayerFilter.isMP3(mediaplayerPath)) {
            mediaplayerChooser.setFileFilter(mp3);
          }
          else if (PlayerFilter.isOGG(mediaplayerPath)) {
            mediaplayerChooser.setFileFilter(ogg);
          }
          else if (PlayerFilter.isM4A(mediaplayerPath)) {
            mediaplayerChooser.setFileFilter(mpeg4);
          }
          else if (PlayerFilter.isADTS(mediaplayerPath)) {
            mediaplayerChooser.setFileFilter(aac);
          }
          else if (PlayerFilter.isAVI(mediaplayerPath)) {
            mediaplayerChooser.setFileFilter(avi);
          }
          else if (PlayerFilter.isFLAC(mediaplayerPath)) {
            mediaplayerChooser.setFileFilter(flac);
          }
          else if (PlayerFilter.isDAV(mediaplayerPath)) {
            mediaplayerChooser.setFileFilter(dav);
          }
          else if (PlayerFilter.isWEBM(mediaplayerPath)) {
            mediaplayerChooser.setFileFilter(webm);
          }
          else if (PlayerFilter.isMPEG(mediaplayerPath)) {
            mediaplayerChooser.setFileFilter(mpeg);
          }
          else if (PlayerFilter.isMATROSKA(mediaplayerPath)) {
            mediaplayerChooser.setFileFilter(matroska);
          }
          else if (PlayerFilter.isWAVE(mediaplayerPath)) {
            mediaplayerChooser.setFileFilter(wave);
          }
          else if (PlayerFilter.isWMV(mediaplayerPath)) {
            mediaplayerChooser.setFileFilter(wmv);
          }
          else {
            mediaplayerChooser.setFileFilter(mp4);
          }
        }
        UIManager.put("FileChooser.cancelButtonText", cancel.toString());
        UIManager.put("FileChooser.cancelButtonToolTipText", "Dialogfeld für Dateiauswahl schließen");
        SwingUtilities.updateComponentTreeUI(mediaplayerChooser);
        mediaplayerChooser.setApproveButtonToolTipText("Ausgewählte Datei öffnen");
        mediaplayerChooser.setPreferredSize(GUI.FILE_CHOOSER_SIZE);
        mediaplayerChooser.addPropertyChangeListener(propertyEvent -> {
          if ("SelectedFileChangedProperty".equals(propertyEvent.getPropertyName())) {
            mediaplayerChooser.highligthRow();
          }

          else if ("fileFilterChanged".equals(propertyEvent.getPropertyName())) {
            mediaplayerChooser.sort();
          }

        });

        Action details = mediaplayerChooser.getActionMap().get("viewTypeDetails");
        details.actionPerformed(null);
        mediaplayerChooser.adjustColumnWidth();
        mediaplayerChooser.sort();
        mediaplayerChooser.inputfield();

        int i = mediaplayerChooser.showOpenDialog(JChat.this);
        if (i == JFileChooser.APPROVE_OPTION) {
          try {
            String mediaformat = mediaplayerChooser.getSelectedFile().getAbsolutePath();
            Path pathMediaformat = Paths.get(mediaformat);
            if (!Files.exists(pathMediaformat)) {
              JOptionPane.showMessageDialog(
                  JChat.this,
                  "<html><b>" + pathMediaformat.toString()
                      + "</b> "
                      + istNichtVorhanden.toString()
                      + "</html>",
                  medienwiedergabe.toString(), JOptionPane.INFORMATION_MESSAGE
              );
              return;
            }
            boolean supported = PlayerFilter.isSupported(pathMediaformat);
            if (!supported) {
              JOptionPane.showMessageDialog(
                  JChat.this, "<html>" + medienformatWirdNichtUnterstuetzt.toString() + "</html>",
                  medienwiedergabe.toString(), JOptionPane.INFORMATION_MESSAGE
              );
              return;
            }
            // TODO ist der Frame schon geöffnet, Übersetzung fehlt
            if (isOpened(Paths.get(mediaformat).getFileName().toString())) {
              JOptionPane.showMessageDialog(
                  JChat.this, "<html>" + "Diese Mediendatei wird gerade verwendet." + "</html>",
                  "Duplicated Windows", JOptionPane.INFORMATION_MESSAGE
              );
              return;
            }

            alleHakenAusSpiellisteEntfernen();
            itemSpielliste.setSelected(false);
            removeAllPlayerFrames(Control.NULL);
            root.getMediaplayer().setSinglefile(mediaformat);
            // Normaler PlayerFrame
            playerInternalframe(mediaformat, PLAYERMODE.SINGLE);
          }
          catch (DvdException e) {
            log.warn(e.getMessage(), e.getCause());
            log.warn("Das Dvd-Laufwerk ist nicht bereit oder das Format wird nicht unterstützt.");
            log.warn(e.getMessage());
          }
          catch (MediaException e) {
            log.warn(e.getMessage(), e.getCause());
            JOptionPane.showMessageDialog(
                JChat.this,
                "<html>" + "<head/>"
                    + "<body>"
                    + "Die Datei <b>"
                    + e.getMessage()
                    + "</b> ist korrupt."
                    + "</body>"
                    + "</html>",
                "Video abspielen", JOptionPane.ERROR_MESSAGE
            );
          }
        }
      }

    });
    itemTitel.setIcon(new ImageIcon(resource.getResource(Resource.PACKAGE_MULTIMEDIA)));
    menuMediaPlayer.add(itemTitel);
    menuMediaPlayer.add(itemSpielliste);
    itemSpielliste.setIcon(new ImageIcon(resource.getResource(Resource.SHUFFLE)));
    itemSpielliste.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent event) {

        if (!itemSpielliste.isSelected()) {

          // Alle bisherigen Songs werden unterbrochen und die Frames gelöscht
          // ohne buttonClose oder Window Schließen
          removeAllPlayerFrames(Control.NULL);
          JOptionPane.showMessageDialog(
              JChat.this, "<html>" + zufuallsmodus.toString() + "</html>", "Shuffle",
              JOptionPane.INFORMATION_MESSAGE
          );
          itemSpielliste.setSelected(false);

          alleHakenAusSpiellisteEntfernen();
          return;
        }
        shuffle();
      }
    });

    menuMusikkiste.setIcon(new ImageIcon(resource.getResource(Resource.JUKEBOX)));
    menuMediaPlayer.add(menuMusikkiste);
    menuMusikkiste.add(itemKisteLeeren, 0);
    itemKisteLeeren.setIcon(new ImageIcon(resource.getResource(Resource.LEEREN)));
    itemKisteLeeren.setIconTextGap(4);
    itemKisteLeeren.addActionListener(event -> {
      Mediaplayer mediaplayer = root.getMediaplayer();
      if (mediaplayer.getDirectories() == null) {
        JOptionPane.showMessageDialog(
            JChat.this, musikboxLeer.toString(), wiedergabelisten.toString(), JOptionPane.INFORMATION_MESSAGE
        );

        return;
      }
      Component[] menuComponents = menuMusikkiste.getMenuComponents();
      for (Component tmp : menuComponents) {
        if (tmp instanceof MultilingualCheckBoxMenuItem) {
          tmp.getParent().remove(tmp);
        }
        else if (tmp instanceof JSeparator) {
          tmp.getParent().remove(tmp);
        }
      }
      // Konfigurationsdatei anpassen
      mediaplayer.setDirectories(null);
    });

    menuMediaPlayer.addSeparator();

    itemDVD.setIcon(new ImageIcon(resource.getResource(Resource.DVD_26x26)));
    menuMediaPlayer.add(itemDVD);
    itemDVD.addActionListener(event -> {
      dvdChooser = new MediaFileChooser(root.getMediaplayer().getSinglefile());
      SwingUtilities.updateComponentTreeUI(dvdChooser);
      dvdChooser.setDialogTitle("DVD markieren");
      dvdChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
      dvdChooser.setPreferredSize(GUI.FILE_CHOOSER_SIZE);
      dvdChooser.viewTypeDetails();
      dvdChooser.sort();
      int result = dvdChooser.showOpenDialog(JChat.this);
      if (result == JFileChooser.CANCEL_OPTION) return;
      Path rootPath = Paths.get(dvdChooser.getSelectedFile().toURI());
      try {
        if (PlayerFilter.isSupportedDvd(rootPath)) {
          alleHakenAusSpiellisteEntfernen();
          itemSpielliste.setSelected(false);
          removeAllPlayerFrames(Control.NULL);
          log.info(rootPath.toAbsolutePath().toString());
          // einstellen.
          playerInternalframe(
              rootPath.toAbsolutePath().toString(), PLAYERMODE.DVD, root.getDvd().getLand().getIsoCode6391()
          );
        }
        else {
          // keine DVD
          JOptionPane.showMessageDialog(
              JChat.this,
              "<html>" + "<head/>"
                  + "<body>"
                  + "<b>"
                  + rootPath.toAbsolutePath().toString()
                  + "</b> ist kein DVD / Blu-ray Laufwerk."
                  + "</body>"
                  + "</html>",
              "Falsches Laufwerk", JOptionPane.ERROR_MESSAGE
          );

        }
      }
      catch (DvdException e) {
        log.warn(e.getMessage(), e.getCause());
        // Das Gerät ist nicht bereit
        JOptionPane.showMessageDialog(
            JChat.this,
            "<html>" + "<head/>"
                + "<body>"
                + "<b>"
                + rootPath.toAbsolutePath().toString()
                + "</b> enthält keine Disc."
                + "</body>"
                + "</html>",
            "Das Laufwerk ist nicht bereit", JOptionPane.WARNING_MESSAGE
        );
      }
      catch (MediaException e) {
        log.error(e.getMessage(), e.getCause());
        JOptionPane.showMessageDialog(
            JChat.this,
            "<html>" + "<head/>" + "<body>" + "<b>" + e.getMessage() + "</span></b>" + "</body>" + "</html>",
            "Generischer Fehler", JOptionPane.ERROR_MESSAGE
        );
      }
    });

    menuInhaltsverzeichnis.setIcon(new ImageIcon(resource.getResource(Resource.TITEL)));
    menuInhaltsverzeichnis.setEnabled(false);
    menuMediaPlayer.add(menuInhaltsverzeichnis);
    menuMediaPlayer.add(menuFilmsprache);
    menuFilmsprache.setIcon(new ImageIcon(resource.getResource(Resource.SPRACHE_20x26)));

    menuFilmsprache.add(itemDeutsch);
    itemDeutsch.setIcon(new ImageIcon(resource.getResource(Resource.DEUTSCH_39x26)));
    itemDeutsch.addActionListener(event -> {
      if (internalplayer != null) {
        internalplayer.setAudioLanguage(Sprache.GERMAN);
      }
      Land deutschland = new Land();
      deutschland.setIsoCode6391(ISO6391.DE);
      deutschland.setValue(Sprache.GERMAN);
      root.getDvd().setLand(deutschland);
    });
    menuFilmsprache.add(itemEnglisch);
    itemEnglisch.setIcon(new ImageIcon(resource.getResource(Resource.ENGLISH_39x26)));
    itemEnglisch.addActionListener(event -> {
      if (internalplayer != null) {
        internalplayer.setAudioLanguage(Sprache.ENGLISH);
      }
      Land britain = new Land();
      britain.setIsoCode6391(ISO6391.EN);
      britain.setValue(Sprache.ENGLISH);
      root.getDvd().setLand(britain);
    });

    menuFilmsprache.add(itemSpanisch);
    itemSpanisch.setIcon(new ImageIcon(resource.getResource(Resource.SPAIN_39x26)));
    itemSpanisch.addActionListener(event -> {
      if (internalplayer != null) {
        internalplayer.setAudioLanguage(Sprache.SPANISH);
      }
      Land spanien = new Land();
      spanien.setIsoCode6391(ISO6391.ES);
      spanien.setValue(Sprache.SPANISH);
      root.getDvd().setLand(spanien);
    });

    menuFilmsprache.add(itemFranzoesisch);
    itemFranzoesisch.setIcon(new ImageIcon(resource.getResource(Resource.FRANCE_39x26)));
    itemFranzoesisch.addActionListener(event -> {
      if (internalplayer != null) {
        internalplayer.setAudioLanguage(Sprache.FRENCH);
      }
      Land frankreich = new Land();
      frankreich.setIsoCode6391(ISO6391.FR);
      frankreich.setValue(Sprache.FRENCH);
      root.getDvd().setLand(frankreich);
    });

    menuFilmsprache.add(itemItalienisch);
    itemItalienisch.setIcon(new ImageIcon(resource.getResource(Resource.ITALIENISCH_39x26)));
    itemItalienisch.addActionListener(event -> {
      if (internalplayer != null) {
        internalplayer.setAudioLanguage(Sprache.ITALIAN);
      }
      Land italien = new Land();
      italien.setIsoCode6391(ISO6391.IT);
      italien.setValue(Sprache.ITALIAN);
      root.getDvd().setLand(italien);
    });

    menuFilmsprache.add(itemPortugiesisch);
    itemPortugiesisch.setIcon(new ImageIcon(resource.getResource(Resource.PORTUGAL_39x26)));
    itemPortugiesisch.addActionListener(event -> {
      if (internalplayer != null) {
        internalplayer.setAudioLanguage(Sprache.PORTUGUESE);
      }
      Land portugal = new Land();
      portugal.setIsoCode6391(ISO6391.PT);
      portugal.setValue(Sprache.PORTUGUESE);
      root.getDvd().setLand(portugal);
    });

    menuFilmsprache.add(itemRussisch);
    itemRussisch.setIcon(new ImageIcon(resource.getResource(Resource.RUSIA_39x26)));
    itemRussisch.addActionListener(event -> {
      if (internalplayer != null) {
        internalplayer.setAudioLanguage(Sprache.RUSSIAN);
      }
      Land rusia = new Land();
      rusia.setIsoCode6391(ISO6391.RU);
      rusia.setValue(Sprache.RUSSIAN);
      root.getDvd().setLand(rusia);
    });

    menuFilmsprache.add(itemTuerkisch);
    itemTuerkisch.setIcon(new ImageIcon(resource.getResource(Resource.TURKEY_39x26)));
    itemTuerkisch.addActionListener(event -> {
      if (internalplayer != null) {
        internalplayer.setAudioLanguage(Sprache.TURKEY);
      }
      Land turkey = new Land();
      turkey.setIsoCode6391(ISO6391.TR);
      turkey.setValue(Sprache.TURKEY);
      root.getDvd().setLand(turkey);
    });

    sprachgroup.add(itemDeutsch);
    sprachgroup.add(itemEnglisch);
    sprachgroup.add(itemSpanisch);
    sprachgroup.add(itemFranzoesisch);
    sprachgroup.add(itemItalienisch);
    sprachgroup.add(itemRussisch);
    sprachgroup.add(itemTuerkisch);

    menuMediaPlayer.addSeparator();
    itemVollbildmodus.setIcon(new ImageIcon(resource.getResource(Resource.VORHANG)));
    menuMediaPlayer.add(itemVollbildmodus);
    itemVollbildmodus.addActionListener(event -> {

      // Bildschirm normalisieren, wenn Video auf maximal ist
      if (internalplayer == null) return;

      if (itemVollbildmodus.isSelected()) {
        // Bildschirm maximieren
        if (internalplayer.isShowing() && itemVollbildmodus.isSelected()) {
          setExtendedState(Frame.MAXIMIZED_BOTH);
          try {
            internalplayer.setMaximum(true);
            internalplayer.setPopupIconVollbildmodus(true);
          }
          catch (PropertyVetoException e) {
            log.error(e.getMessage(), e.getCause());
          }
        }
        else {
          // POPUP benachrichtigen
          setExtendedState(Frame.NORMAL);
          internalplayer.setPopupIconVollbildmodus(false);
        }
      }
      else {
        setExtendedState(Frame.NORMAL);
        internalplayer.setPopupIconVollbildmodus(false);
      }
      if (itemFensterImHintergrund.isSelected()) {
        internalplayer.toBack();
      }
    });

    itemFensterImHintergrund.setSelected(root.getMediaplayer().isWindowToBackground());
    itemFensterImHintergrund.setIcon(new ImageIcon(resource.getResource(Resource.FENSTER_IM_HINTERGRUND)));
    itemFensterImHintergrund.addActionListener(event -> {
      if (!itemFensterImHintergrund.isSelected() && internalplayer != null && internalplayer.isSelected()) {
        internalplayer.toFront();
      }
      root.getMediaplayer().setWindowToBackground(itemFensterImHintergrund.isSelected());
    });
    menuMediaPlayer.add(itemFensterImHintergrund);

    // Menu Phone
    gridbag.setConstraints(menuPhone, con);

    menubar.add(menuPhone);

    itemTelefonEinrichten.setIcon(new ImageIcon(resource.getResource(Resource.MICROPHONE)));
    itemTelefonEinrichten.setEnabled(false);
    menuPhone.add(itemTelefonEinrichten);
    itemTelefonEinrichten.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent event) {
        itemTelefonEinrichten.setEnabled(false);

        telefonFrame = new JTelefonEinrichtenFrame();
        windowAnrufen.addWindowAnrufenListener(telefonFrame);
        anrufenEvent(telefonFrame);

        // log.info("userdata=" + userdata);
        // log.info("userdata=" + userdata.getNickname());
        // log.info("userdata=" + userdata.getVolume());
        // log.info("userdata=" + userdata.isOncall());
        // log.info("userdata=" + userdata.getTelefonport());
        telefonFrame.init(voiceQuality, telefonLautsprecher, telefonMikrofon);
        Rectangle localRectangle = getBounds();
        telefonFrame.setLocation(
            (localRectangle.width - JTelefonEinrichtenFrame.SCREEN_WIDTH) / 2,
            (localRectangle.height - JTelefonEinrichtenFrame.SCREEN_HEIGHT) / 4
        );
        desktop.add(telefonFrame, JLayeredPane.DEFAULT_LAYER, PLAYER_LAYER);
        babelfish.add(telefonFrame);
        telefonFrame.setLanguage(ISO639.fromValue(rootLanguage.value()));
        setBackgroundTreeview(helado, telefonFrame);
        telefonFrame.show();
        frameVector.addElement(telefonFrame);
        insertWM(telefonFrame, telefonFrame.getFrameTitleId());
        telefonFrame.addTelefonListener(x -> {
          Control value = (Control) x.getNewValue();
          switch(value) {
            case SELECTED:
              selectFrame(x.getPropertyName());
              break;
            case CLOSE:
              removeWM(telefonFrame);
              frameVector.remove(telefonFrame);
              itemTelefonEinrichten.setEnabled(true);
              babelfish.remove(telefonFrame);
              windowAnrufen.removeWindowAnrufenListener(telefonFrame);
              telefonFrame.closing();
              telefonFrame.removeAllListener();
              break;
            case TELEFON_SPEICHERN:
              telefonFrame.removeAllListener();
              babelfish.remove(telefonFrame);
              telefonFrame.closing();
              telefonMikrofon = telefonFrame.getMikrofon();
              telefonLautsprecher = telefonFrame.getLautsprecher();
              voiceQuality = Sprachguete.toSprachguete(telefonFrame.getTelefonformat());

              TransferTelefon transferTelefon = new TransferTelefon();
              transferTelefon.setTelefonLautsprecher(telefonLautsprecher);
              transferTelefon.setTelefonMikrofon(telefonMikrofon);
              transferTelefon.setVoiceQuality(voiceQuality);

              // Lautprecher und Speaker setzen!!!!!!!!!!!!!!
              Thread.startVirtualThread(() -> saveTelefon(userdata.getPseudoid(), transferTelefon));
              break;
            default:
              break;
          }
        });
      }

    });

    itemAnrufen.setIcon(new ImageIcon(resource.getResource(Resource.ANRUFEN_37X25)));
    itemAnrufen.setEnabled(false);
    itemAnrufen.addActionListener(event -> {
      if (!isAudioAvailable()) return;
      itemTelkoTeilnehmen.setEnabled(false);

      callerframe = new JAnrufenFrame();
      windowAnrufen.addWindowAnrufenListener(callerframe);
      anrufenEvent(callerframe);
      callerframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
      callerframe.setVolume(userdata.getVolume());
      Rectangle localRectangle = getBounds();
      callerframe.setLocation(
          (localRectangle.width - JAnrufenFrame.SCREEN_WIDTH) / 2,
          (localRectangle.height - JAnrufenFrame.SCREEN_HEIGHT) / 4
      );
      desktop.add(callerframe, JLayeredPane.DEFAULT_LAYER, PLAYER_LAYER);
      babelfish.add(callerframe);
      callerframe.setLanguage(ISO639.fromValue(rootLanguage.value()));
      setBackgroundTreeview(helado, callerframe);
      callerframe.show();
      itemAnrufen.setEnabled(false);
      frameVector.addElement(callerframe);
      insertWM(callerframe, callerframe.getFrameTitleId());
      callerframe.addAnrufenListener(callerListener());
      callerframe.setUserOnlinelist(userOnlinelist);
    });
    menuPhone.add(itemAnrufen);

    telefonateAblehnen.setIcon(new ImageIcon(resource.getResource(Resource.NO_CELL_PHONE)));
    telefonateAblehnen.setEnabled(false);
    telefonateAblehnen.addActionListener(event -> {
      ONCALL oncall = new ONCALL();
      oncall.setHeader(REQUEST);
      oncall.setCommand(Command.ONCALL);
      oncall.setDataset(Protocol.DATASET);
      oncall.setPseudoid(userdata.getPseudoid());
      oncall.setOnCall(telefonateAblehnen.isSelected());
      executorChat.execute(() -> {
        wsClient.sendMessage(oncall);
      });

    });
    menuPhone.add(telefonateAblehnen);
    menuPhone.addSeparator();
    menuPhone.add(itemTelkoErstellen);
    itemTelkoErstellen.setEnabled(false);
    itemTelkoErstellen.setIcon(new ImageIcon(resource.getResource(Resource.TELEFONKONFERENZ_31X26)));
    itemTelkoErstellen.addActionListener(event -> {
      telefonErstellenFrame = new JTelefonkonferenzErstellenFrame();
      telefonErstellenFrame
          .setFrameIcon(new ImageIcon(resource.getResource(Resource.TELEFONKONFERENZ_31X26)));

      Rectangle localRectangle = getBounds();
      telefonErstellenFrame.setLocation(
          (localRectangle.width - JTelefonkonferenzErstellenFrame.SCREEN_WIDTH) / 2,
          (localRectangle.height - JTelefonkonferenzErstellenFrame.SCREEN_HEIGHT) / 4
      );
      desktop.add(telefonErstellenFrame, JLayeredPane.MODAL_LAYER, 0);
      babelfish.add(telefonErstellenFrame);
      telefonErstellenFrame.setLanguage(ISO639.fromValue(rootLanguage.value()));
      telefonErstellenFrame.show();
      itemTelkoErstellen.setEnabled(false);
      itemTelkoOrganisieren.setEnabled(false);

      telefonErstellenFrame.addErstellenListener(erstellenEvent -> {
        Control control = (Control) erstellenEvent.getNewValue();
        switch(control) {
          case CREATE:
            Thread.startVirtualThread(() -> {
              saveTelkoErstellen(
                  userdata.getPseudoid(), telefonErstellenFrame.getKonferenzname(),
                  telefonErstellenFrame.getBeschreibung()
              );

            });
            break;
          case CLOSE:
            telefonErstellenFrame.removeAllListener();
            itemTelkoErstellen.setEnabled(true);
            itemTelkoOrganisieren.setEnabled(true);
            babelfish.remove(telefonErstellenFrame);
            break;
          default:
            break;
        }
      });

    });

    menuPhone.add(itemTelkoOrganisieren);
    itemTelkoOrganisieren.setIcon(new ImageIcon(resource.getResource(Resource.TELKO_ORGANISIEREN_23X26)));
    itemTelkoOrganisieren.setEnabled(false);
    itemTelkoOrganisieren.addActionListener(event -> {

      telefonkonferenzframe = new JTelefonkonferenzOrganisierenFrame();
      telefonkonferenzframe.setOrganisatorNickname(userdata.getNickname());
      telefonkonferenzframe
          .setFrameIcon(new ImageIcon(resource.getResource(Resource.TELKO_ORGANISIEREN_23X26)));

      telefonkonferenzframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
      Rectangle localRectangle = getBounds();
      telefonkonferenzframe.setLocation(
          (localRectangle.width - JTelefonkonferenzOrganisierenFrame.SCREEN_WIDTH) / 2,
          (localRectangle.height - JTelefonkonferenzOrganisierenFrame.SCREEN_HEIGHT) / 4 - 18
      );
      desktop.add(telefonkonferenzframe, JLayeredPane.MODAL_LAYER, -1);
      babelfish.add(telefonkonferenzframe);
      telefonkonferenzframe.setLanguage(ISO639.fromValue(rootLanguage.value()));

      // hole alle Konferenzen für den Anwender
      // hole alle restlichen Anwender für (UID/Konferenzname)

      telefonkonferenzframe.addTelefonkonferenzListener(new PropertyChangeListener() {

        @Override
        public void propertyChange(PropertyChangeEvent event) {

          Control control = (Control) event.getNewValue();
          switch(control) {
            case INVITE:
              //
              // die Konferenz und Teilnehmerliste speichern

              Thread.startVirtualThread(() -> {
                try {
                  final List<String> alleMitglieder = telefonkonferenzframe.getTeilnehmerliste();
                  saveTeilnehmerliste(
                      userdata.getPseudoid(), telefonkonferenzframe.getSelectedKonferenzname(), alleMitglieder
                  );
                }
                catch (KonferenznameException e) {
                  log.info(e.getMessage(), e);
                }

              });

              break;
            case REMAINING_MEMBERS:

              Thread.startVirtualThread(() -> {
                try {
                  readRestlicheMitgliederSync(
                      telefonkonferenzframe.getSelectedKonferenzname(), userdata.getPseudoid()
                  );
                }
                catch (KonferenznameException e) {
                  log.info(e.getMessage(), e);
                }

              });

              break;
            case CONFIRM:

              Thread.startVirtualThread(() -> {
                try {
                  saveVerfallsdatum(
                      userdata.getPseudoid(), telefonkonferenzframe.getSelectedKonferenzname(),
                      telefonkonferenzframe.getVerfallsdatum()
                  );
                }
                catch (KonferenznameException e) {
                  log.error(e.getMessage(), e);
                }

              });

              break;
            case MODIFY:
              Thread.startVirtualThread(() -> {
                try {
                  saveKonferenzbeschreibung(
                      userdata.getPseudoid(), telefonkonferenzframe.getSelectedKonferenzname(),
                      telefonkonferenzframe.getBeschreibung()
                  );
                }
                catch (KonferenznameException e) {
                  log.info(e.getMessage(), e);
                }
              });
              break;
            case CLOSE:
              telefonkonferenzframe.removeAllListener();
              itemTelkoOrganisieren.setEnabled(true);
              itemTelkoErstellen.setEnabled(true);
              babelfish.remove(telefonkonferenzframe);
              break;
            default:
              break;
          }
        }
      });
      Thread.startVirtualThread(() -> organizeTelefonkonferenzSync(userdata.getPseudoid()));
    });
    menuPhone.add(itemTelkoTeilnehmen);
    itemTelkoTeilnehmen.setEnabled(false);
    itemTelkoTeilnehmen.setIcon(new ImageIcon(resource.getResource(Resource.CONFERENCE_23x26)));

    itemTelkoTeilnehmen.addActionListener(event -> {

      if (!isAudioAvailable()) return;
      itemAnrufen.setEnabled(false);

      telefonTeilnehmenFrame = new JTelefonkonferenzTeilnehmenFrame();
      telefonTeilnehmenFrame.setPseudoid(userdata.getPseudoid(), userdata.getNickname());

      // alle Konferenzräume
      readJoinedConferences(userdata.getPseudoid()); // Hier erfährt der Anwender seinen Conferencekey

      telefonTeilnehmenFrame.setClosable(false);
      telefonTeilnehmenFrame.setResizable(true);
      telefonTeilnehmenFrame.setIconifiable(true);
      telefonTeilnehmenFrame.setLanguage(ISO639.fromValue(rootLanguage.value()));
      telefonTeilnehmenFrame.setFrameIcon(new ImageIcon(resource.getResource(Resource.CONFERENCE_23x26)));
      telefonTeilnehmenFrame.setVolume(userdata.getVolume());

      babelfish.add(telefonTeilnehmenFrame);

      Rectangle localRectangle = getBounds();
      telefonTeilnehmenFrame.setLocation(
          (localRectangle.width - JTelefonkonferenzTeilnehmenFrame.PREFERRED_NORMAL.width) / 2,
          (localRectangle.height - JTelefonkonferenzTeilnehmenFrame.PREFERRED_NORMAL.height) / 4
      );
      desktop.add(telefonTeilnehmenFrame, JLayeredPane.DEFAULT_LAYER, PLAYER_LAYER);
      itemTelkoTeilnehmen.setEnabled(false);
      itemTeilenframe.setEnabled(false);

      setBackgroundTreeview(helado, telefonTeilnehmenFrame);
      telefonTeilnehmenFrame.show();
      frameVector.addElement(telefonTeilnehmenFrame);
      insertWM(telefonTeilnehmenFrame, telefonTeilnehmenFrame.getBaumansicht());

      telefonTeilnehmenFrame.addKonferenzListener(new PropertyChangeListener() {

        @Override
        public void propertyChange(PropertyChangeEvent event) {
          Control value = (Control) event.getNewValue();
          switch(value) {
            case PRIVATECHAT:
              callPrivatechat(event);
              break;
            case SHARING:
              // Permission prüfen
              if (telefonTeilnehmenFrame.isShared()) {
                ImageCaster.getInstance()
                    .recordScreenshots(userdata.getPseudoid(), telefonTeilnehmenFrame.getConferenceKey());
              }
              else {
                log.info("stop Recording");
                ImageCaster.getInstance().stopRecording();
              }

              onConferenceVideo(
                  userdata.getNickname(), telefonTeilnehmenFrame.getKonferenzname(),
                  telefonTeilnehmenFrame.getConferenceKey(), telefonTeilnehmenFrame.isShared()
              );
              break;
            case TELEFON_VOLUME:
              conferenceSpeakerlist.forEach(speaker -> speaker.setVolume(telefonTeilnehmenFrame.getVolume()));
              break;
            case MIKRO_AUFZEICHNEN:
              // AWT
              // Mikroaufnahme wird stumm geschaltet
              if (micConsumer == null) break;
              if (telefonTeilnehmenFrame.isMuted()) {
                micConsumer.mute();
              }
              else {
                micConsumer.unmute();
              }

              CONFERENCEMUTE conferencemute = new CONFERENCEMUTE();
              conferencemute.setCommand(Command.CONFERENCEMUTE);
              conferencemute.setHeader(REQUEST);
              conferencemute.setDataset(Protocol.DATASET);
              conferencemute.setPseudoid(telefonTeilnehmenFrame.getPseudoid());
              conferencemute.setConferencekey(telefonTeilnehmenFrame.getConferenceKey());
              conferencemute.setMute(telefonTeilnehmenFrame.isMuted());
              conferencemute.setKonferenzraum(telefonTeilnehmenFrame.getKonferenzname());
              executorChat.execute(() -> wsClient.sendMessage(conferencemute));

              Thread.startVirtualThread(
                  () -> readPresence(
                      telefonTeilnehmenFrame.getKonferenzname(), telefonTeilnehmenFrame.getConferenceKey(),
                      userdata.getWebsocketSession()
                  )
              );

              break;
            case TELEFON_AUFLEGEN:
              // AWT erhalte aber immer noch Nachrichten, Netzwerk
              // Alle Konferenzspeaker werden geschlossen.
              speakerTerminate();
              try {
                conference(false);
              }
              catch (Exception e) {}
              break;
            case TELEFON_ABNEHMEN:
              speakerTerminate();
//              log.info("speaker Terminate, muss neue Leitung öffnen, alles tot");
              micStart(Sprachguete.MEDIUM, telefonTeilnehmenFrame.getConferenceKey());
              if (telefonTeilnehmenFrame.isMuted()) {
                micConsumer.mute();
              }
              else {
                micConsumer.unmute();
              }
              conference(true);
              break;
            case REQUEST:
              // Einstieg in die Telefonkonferenz
              Thread.startVirtualThread(
                  () -> readPresence(
                      telefonTeilnehmenFrame.getKonferenzname(), telefonTeilnehmenFrame.getConferenceKey(),
                      userdata.getWebsocketSession()
                  )
              );
              break;
            case SELECTED:
              selectFrame(event.getPropertyName());
              break;
            case CLOSE:
              micTerminate();
              speakerTerminate(); // in der Conference
              telefonTeilnehmenFrame.removeAllListener();
              removeWM(telefonTeilnehmenFrame);
              frameVector.remove(telefonTeilnehmenFrame);
              babelfish.remove(telefonTeilnehmenFrame);
              itemTelkoTeilnehmen.setEnabled(true);
              try {
                conference(false);
              }
              catch (Exception e) {}
              itemAnrufen.setEnabled(true);
              itemTeilenframe.setEnabled(true);
              userdata.setVolume(telefonTeilnehmenFrame.getVolume());
              Thread
                  .startVirtualThread(() -> saveTelefonvolume(userdata.getPseudoid(), userdata.getVolume()));
              telefonTeilnehmenFrame = null;
              break;
            default:
              throw new UnsupportedOperationException();
          }

        }

      });
    });

    gridbag.setConstraints(menuBildschirm, con);

    menubar.add(menuBildschirm);

    menuBildschirm.add(itemBildschirmEmpfangen);
    itemBildschirmEmpfangen.setIcon(new ImageIcon(resource.getResource(Resource.CONSENT_39x26)));
    itemBildschirmEmpfangen.setEnabled(false);
    itemBildschirmEmpfangen.addActionListener(event -> {
      itemBildschirmEmpfangen.setSelected(itemBildschirmEmpfangen.isSelected());
      einwilligungFrame = new EinwilligungInternalFrame();
      windowAnrufen.addWindowAnrufenListener(einwilligungFrame);
      anrufenEvent(einwilligungFrame);
      einwilligungFrame.setOnrekorder(onrekorder);

      Rectangle localRectangle = getBounds();
      einwilligungFrame.setLocation(
          (localRectangle.width - EinwilligungInternalFrame.SCREEN_WIDTH) / 2,
          (localRectangle.height - EinwilligungInternalFrame.SCREEN_HEIGHT) / 4
      );
      desktop.add(einwilligungFrame, JLayeredPane.DEFAULT_LAYER, PLAYER_LAYER);
      babelfish.add(einwilligungFrame);
      einwilligungFrame.setLanguage(ISO639.fromValue(rootLanguage.value()));
      setBackgroundTreeview(helado, einwilligungFrame);
      einwilligungFrame.show();
      itemBildschirmEmpfangen.setEnabled(false);
      frameVector.addElement(einwilligungFrame);
      insertWM(einwilligungFrame, einwilligungFrame.getFrameTitleId());
      einwilligungFrame.addScreensaverListener(screenEvent -> {
        Control value = (Control) screenEvent.getNewValue();
        switch(value) {
          case SAVE_AND_CLOSE:
            onrekorder = einwilligungFrame.isOnrekorder();
            Thread.startVirtualThread(() -> saveRekorder(userdata.getPseudoid(), onrekorder));
            break;
          case CLOSE:
            removeWM(einwilligungFrame);
            frameVector.remove(einwilligungFrame);
            einwilligungFrame.removeAllListener();
            itemBildschirmEmpfangen.setEnabled(true);
            babelfish.remove(einwilligungFrame);
            windowAnrufen.removeWindowAnrufenListener(einwilligungFrame);
            break;
          case SELECTED:
            selectFrame(screenEvent.getPropertyName());
            break;
          default:
            break;
        }
      });
    });

    menuBildschirm.add(itemTeilenframe);
    itemTeilenframe.setIcon(new ImageIcon(resource.getResource(Resource.SHARE_36x26)));
    itemTeilenframe.setEnabled(false);
    itemTeilenframe.addActionListener(event -> {
      teilenframe = new TeilenInternalFrame();
      windowAnrufen.addWindowAnrufenListener(teilenframe);
      anrufenEvent(teilenframe);
      teilenframe.startStop(false);
      teilenframe.setEissorte(root.getEis());
      Rectangle localRectangle = getBounds();
      teilenframe.setLocation(
          (localRectangle.width - TeilenInternalFrame.SCREEN_WIDTH) / 2,
          (localRectangle.height - TeilenInternalFrame.SCREEN_HEIGHT) / 4
      );
      desktop.add(teilenframe, JLayeredPane.DEFAULT_LAYER, PLAYER_LAYER);
      babelfish.add(teilenframe);
      teilenframe.setLanguage(ISO639.fromValue(rootLanguage.value()));
      setBackgroundTreeview(helado, teilenframe);
      teilenframe.show();
      itemTeilenframe.setEnabled(false);
      itemTelkoTeilnehmen.setEnabled(false);

      frameVector.addElement(teilenframe);
      insertWM(teilenframe, teilenframe.getFrameTitleId());
      teilenframe.addTeilenFrameListener(screenEvent -> {
        Control value = (Control) screenEvent.getNewValue();
        switch(value) {
          case START:

            // möchte eine 1:1 Bildschirmübertragung aufbauen
            // frgae beim Server an ob der Empfänger eine Übertragung am Laufen hat

            IMAGE image = new IMAGE();
            image.setCommand(Command.IMAGE);
            image.setHeader(REQUEST);
            image.setDataset(Protocol.DATASET);
            image.setSenderPseudoid(userdata.getPseudoid());
            image.setSenderNickname(userdata.getNickname());
            image.setReceiverNickname(
                teilenframe.getNickname().equals("Test") ? userdata.getNickname() : teilenframe.getNickname()
            );
            executorChat.execute(() -> {
              wsClient.sendMessage(image);
            });

            break;
          case CANCEL:

            ImageCaster.getInstance().stopRecording();

            // der Sender schaltet den Projektor ab bzw. hat auf Abbrechen gedrückt
            PROYECTORCLOSING proyector = new PROYECTORCLOSING();
            proyector.setCommand(Command.PROYECTORCLOSING);
            proyector.setHeader(REQUEST);
            proyector.setDataset(Protocol.DATASET);
            proyector.setPseudoid(userdata.getPseudoid());

            executorChat.execute(() -> {
              try {
                wsClient.sendMessage(proyector);
              }
              catch (Exception e) {
                log.info("Verbindung");
              }
            });
            teilenframe.startStop(false);
            teilenframe.clearFps(0d);

            break;
          case CLOSE:
            ImageCaster.getInstance().stopRecording();

            // der Sender schaltet den Projektor ab bzw. hat auf Abbrechen gedrückt
            proyector = new PROYECTORCLOSING();
            proyector.setCommand(Command.PROYECTORCLOSING);
            proyector.setHeader(REQUEST);
            proyector.setDataset(Protocol.DATASET);
            proyector.setPseudoid(userdata.getPseudoid());
            executorChat.execute(() -> {
              wsClient.sendMessage(proyector);
            });

            removeWM(teilenframe);
            frameVector.remove(teilenframe);
            teilenframe.removeAllListener();
            itemTeilenframe.setEnabled(true);
            itemTelkoTeilnehmen.setEnabled(true);
            babelfish.remove(teilenframe);
            windowAnrufen.removeWindowAnrufenListener(teilenframe);
            break;
          case SELECTED:
            selectFrame(screenEvent.getPropertyName());
            break;
          default:
            break;
        }
      });
      teilenframe.setUserOnlinelist(userOnlinelist);

    });

    // IPTV
    gridbag.setConstraints(menuStreams, con);
    menubar.add(menuStreams);
    menuStreams.add(itemLiveStreams);

    if (senderliste != null) {
      log.info("Senderliste      -----> ok");

      itemLiveStreams.setEnabled(senderliste.size() > 0);
      itemLiveStreams.setIcon(new ImageIcon(resource.getResource(Resource.LIVESTREAM_25x25)));
      itemLiveStreams.addActionListener(event -> {
        setExtendedState(Frame.MAXIMIZED_BOTH);

        iptvframe = new StreamFrame(); // slots werden definiert
        net.javacomm.client.config.schema.Stream stream = root.getStream();
        Livestreams livestreams = stream.getLivestreams();
        iptvframe.setLivestreams(livestreams);

        windowAnrufen.addWindowAnrufenListener(iptvframe);
        anrufenEvent(iptvframe);
        babelfish.add(iptvframe);
        iptvframe.setLanguage(ISO639.fromValue(rootLanguage.value()));

        try {
          iptvframe.setMaximum(true);
        }
        catch (PropertyVetoException e1) {
          log.error(e1.getMessage(), e1);
        }
        iptvframe.setVerticalDivider(StreamFrame.TOP_BAR);

        iptvframe.setIptvvolume(iptvvolume);

        Rectangle localRectangle = getBounds();
        iptvframe.setLocation(
            (localRectangle.width - StreamFrame.SCREEN_WIDTH) / 2,
            (localRectangle.height - StreamFrame.SCREEN_HEIGHT) / 4
        );

        // TODO Senderliste eventuell neu abfragen
        iptvframe.setSenderliste(senderliste);

        // Die Liste muss da sein
        desktop.add(iptvframe, JLayeredPane.DEFAULT_LAYER, PLAYER_LAYER);
        setBackgroundTreeview(helado, iptvframe);
        iptvframe.show();
        frameVector.addElement(iptvframe);
        insertWM(iptvframe, iptvframe.getFrameTitleId());
        itemLiveStreams.setEnabled(false);
        iptvframe.addIptvListener(testevent -> {
          Control value = (Control) testevent.getNewValue();
          switch(value) {
            case IPTV_FRAME_ACTIVATED:
              break;
            case FULLSIZE:
              try {
                if (getExtendedState() == Frame.NORMAL) {
                  setExtendedState(Frame.MAXIMIZED_BOTH);
                  iptvframe.setMaximum(true);
                }
                else {
                  setExtendedState(Frame.NORMAL);
                  iptvframe.setMaximum(false);
                }
              }
              catch (PropertyVetoException e) {}
              break;
            case IPTV_VOLUME:
              iptvvolume = iptvframe.getVolume();
              break;
            case CLOSE:
              removeWM(iptvframe);
              frameVector.remove(iptvframe);
              iptvframe.removeAllListener();
              itemLiveStreams.setEnabled(true);
              // iptavframe auf null setzten, damit bei der Programmbeendigung in
              // windowClosing kein InvalidMemory access kommt.
              babelfish.remove(iptvframe);
              windowAnrufen.removeWindowAnrufenListener(iptvframe);
              iptvframe = null;
              break;
            case SELECTED:
              selectFrame(testevent.getPropertyName());
              break;
            default:
              break;
          }
        });

      });

    }
    else {
      itemLiveStreams.setEnabled(false);
    }

    // Gaming
    gridbag.setConstraints(menuGaming, con);
//    menubar.add(menuGaming);

//    ClassLoader clazzloader = getClass().getClassLoader();

    // Menu Wallpaper
    gridbag.setConstraints(menuHintergrundbild, con);

    menubar.add(menuHintergrundbild);
    menuHintergrundbild.add(itemTapeteAbreissen);

    itemTapeteAnkleben.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent event) {
        try {
          wallpaper();
        }
        catch (FileNotFoundException | ValidationException | SAXException e) {
          log.warn(e.getMessage(), e.getCause());
        }
      }

    });

    itemTapeteAnkleben.setIcon(new ImageIcon(resource.getResource(Resource.TAPETE_ANKLEBEN_25x25)));
    menuHintergrundbild.add(itemTapeteAnkleben);

    itemTapeteAbreissen.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent arg0) {
        desktop.clear();
        // config schreiben
        root.setWallpaper(null);
      }

    });
    itemTapeteAbreissen.setIcon(new ImageIcon(resource.getResource(Resource.TAPETE_ABREISSEN_24x26)));

    // Menu Lizenz

    con.weightx = 0.0;
    gridbag.setConstraints(menuInfo, con);
    menubar.add(menuInfo);
    menuInfo.add(itemLizenz);
    itemLizenz.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent arg0) {
        itemLizenz.setEnabled(false);
        infoPane = new JInfoPane();
        infoPane.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        infoPane.setFrameIcon(new ImageIcon(resource.getResource(Resource.INFO_16X16)));
        infoPane.setClosable(true);
        infoPane.setSize(JInfoPane.PANEL_SIZE);
        infoPane.setResizable(true);
        infoPane.setIconifiable(true);
        infoPane.addInfoListener(event -> {
          itemLizenz.setEnabled(true);
          removeWM(infoPane);
          frameVector.remove(infoPane);
          babelfish.remove(infoPane);
        });

        babelfish.add(infoPane);
        infoPane.setLanguage(ISO639.fromValue(rootLanguage.value()));
        // setLanguage und babelfish müssen vor frameVector.addElement und insertWM()
        // gesetzt werden.

        frameVector.addElement(infoPane);
        insertWM(infoPane, infoPane.getFrameTitleId());
        Rectangle localRectangle = getBounds();
        infoPane.setLocation(
            (localRectangle.width - JInfoPane.PANEL_SIZE.width) / 2,
            (localRectangle.height - JInfoPane.PANEL_SIZE.height) / 4
        );

        desktop.add(infoPane, JLayeredPane.DEFAULT_LAYER, 20);
        infoPane.show();
        if (outdated) {
          // Menu Lizenz
          String neuerText = veralteteProgrammversion.getText()
              .replace("XXX", String.valueOf(Constants.VERSION));
          veralteteProgrammversion.setText(neuerText);
          JOptionPane.showMessageDialog(
              JChat.this, veralteteProgrammversion, checkVersion.toString(), JOptionPane.WARNING_MESSAGE
          );
        }
      }

    });
    itemLizenz.setIcon(new ImageIcon(resource.getResource(Resource.MPL2_ICON)));
    menuInfo.add(itemIcons);
    itemIcons.addActionListener(event -> {
      try {
        java.awt.Desktop.getDesktop().browse(new URI("https://icons8.de/"));
      }
      catch (IOException | URISyntaxException e) {
        log.warn(e.getMessage(), e);
      }
    });
    itemIcons.setIcon(new ImageIcon(resource.getResource(Resource.ICONS8_25x25)));

    // Sprache
    con.weightx = 1.0;
    con.anchor = GridBagConstraints.EAST;
    gridbag.setConstraints(menuFlag, con);
    menubar.add(menuFlag);

    menuFlag.add(spracheDeutsch);
    menuFlag.add(spracheEnglisch);
    menuFlag.add(spracheSpanisch);

    buttonGroupSprache.add(spracheDeutsch);
    buttonGroupSprache.add(spracheEnglisch);
    buttonGroupSprache.add(spracheSpanisch);
    spracheSpanisch.setIcon(new ImageIcon(resource.getResource(Resource.SPAIN_39x26)));
    spracheEnglisch.setIcon(new ImageIcon(resource.getResource(Resource.ENGLISH_39x26)));
    spracheDeutsch.setIcon(new ImageIcon(resource.getResource(Resource.DEUTSCH_39x26)));

    spracheSpanisch.addActionListener(event -> {
      switchLanguage(ISO639.ES);
      rootLanguage = ISO6391.ES;
      root.setLanguage(rootLanguage);
      language = Language.es;
      if (userdata != null) setLanguageAsync(root.getDomain(), language, userdata.getPseudoid());
    });
    spracheEnglisch.addActionListener(event -> {
      switchLanguage(ISO639.EN);
      rootLanguage = ISO6391.EN;
      root.setLanguage(rootLanguage);
      language = Language.en;
      if (userdata != null) setLanguageAsync(root.getDomain(), language, userdata.getPseudoid());
    });
    spracheDeutsch.addActionListener(event -> {
      switchLanguage(ISO639.DE);
      rootLanguage = ISO6391.DE;
      root.setLanguage(rootLanguage);
      language = Language.de;
      if (userdata != null) setLanguageAsync(root.getDomain(), language, userdata.getPseudoid());
    });

    List<String> domains = Environment.getInstance().createDomainlist();
    jLogon = new JLogon(
        JChat.this, "Logon", root.getDomain(), domains, root.getEis(), root.getLogin().getPassword(),
        root.getLogin().getEmail()
    );
    jLogon.setKey(KEY.FRAME_LOGIN);
    jLogon.addLanguageListener(event -> {
      switchLanguage((ISO639) event.getNewValue());
      rootLanguage = ISO6391.fromValue(((ISO639) event.getNewValue()).value());

      log.info(root.getEis());

      switch(rootLanguage) {
        case DE:
          try {
            switch(root.getEis()) {
              case BLAUBEERE:
                UIManager.setLookAndFeel(GUI.blaubeere.getLookAndFeel());
                break;
              case ERDBEERE:
                UIManager.setLookAndFeel(GUI.erdbeere.getLookAndFeel());
                break;
              case JOGHURT:
                UIManager.setLookAndFeel(GUI.joghurt.getLookAndFeel());
                break;
              case MOKKA:
                UIManager.setLookAndFeel(GUI.mokka.getLookAndFeel());
                break;
              case VANILLE:
                UIManager.setLookAndFeel(GUI.vanille.getLookAndFeel());
                break;
              case ZITRONE:
                UIManager.setLookAndFeel(GUI.zitrone.getLookAndFeel());
                break;
              default:
                break;
            }
          }
          catch (UnsupportedLookAndFeelException e) {
            log.error(e.getMessage(), e);
          }
          SwingUtilities.updateComponentTreeUI(jLogon);
          SwingUtilities.updateComponentTreeUI(this);
          switchColorTheme(root.getEis());
          break;
        case EN:
          try {
            switch(root.getEis()) {
              case BLAUBEERE:
                UIManager.setLookAndFeel(GUI.blaubeere.getLookAndFeel());
                break;
              case ERDBEERE:
                UIManager.setLookAndFeel(GUI.erdbeere.getLookAndFeel());
                break;
              case JOGHURT:
                UIManager.setLookAndFeel(GUI.joghurt.getLookAndFeel());
                break;
              case MOKKA:
                UIManager.setLookAndFeel(GUI.mokka.getLookAndFeel());
                break;
              case VANILLE:
                UIManager.setLookAndFeel(GUI.vanille.getLookAndFeel());
                break;
              case ZITRONE:
                UIManager.setLookAndFeel(GUI.zitrone.getLookAndFeel());
                break;
              default:
                break;
            }
          }
          catch (UnsupportedLookAndFeelException e) {
            log.error(e.getMessage(), e);
          }
          SwingUtilities.updateComponentTreeUI(jLogon);
          SwingUtilities.updateComponentTreeUI(this);
          switchColorTheme(root.getEis());
          break;
        case ES:
          try {
            switch(root.getEis()) {
              case BLAUBEERE:
                UIManager.setLookAndFeel(GUI.blaubeere.getLookAndFeel());
                break;
              case ERDBEERE:
                UIManager.setLookAndFeel(GUI.erdbeere.getLookAndFeel());
                break;
              case JOGHURT:
                UIManager.setLookAndFeel(GUI.joghurt.getLookAndFeel());
                break;
              case MOKKA:
                UIManager.setLookAndFeel(GUI.mokka.getLookAndFeel());
                break;
              case VANILLE:
                UIManager.setLookAndFeel(GUI.vanille.getLookAndFeel());
                break;
              case ZITRONE:
                UIManager.setLookAndFeel(GUI.zitrone.getLookAndFeel());
                break;
              default:
                break;
            }
          }
          catch (UnsupportedLookAndFeelException e) {
            log.error(e.getMessage(), e);
          }
          SwingUtilities.updateComponentTreeUI(jLogon);
          SwingUtilities.updateComponentTreeUI(this);
          switchColorTheme(root.getEis());

          break;
        case FR:
          break;
        case IT:
          break;
        case PT:
          break;
        case RU:
          break;
        case TR:
          break;
        default:
          break;

      }

    });
    babelfish.add(jLogon);
    initLanguage(root);

    jLogon.init();
//    Dieser Modaldialog wird eingeblendet
    jLogon.setVisible(true);

    STATUS statusAfterLogon = afterJLogon(jLogon.getStatus());
    if (root.getWallpaper() != null) {
      try {
        File picture = new File(root.getWallpaper().getPathToFile());
        desktop.setPicture(picture.toURI().toURL());
      }
      catch (MalformedURLException e) {
        log.error(e.getMessage(), e.getCause());
      }
    }
    // MainPanel auslegen
    setContentPane(desktop);
    desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
    // MainFrame visible

    pack();
    setVisible(true);
    createBufferStrategy(2);

    // Das Hauptfenster wurde geöffnet
    if (statusAfterLogon == STATUS.DISMISSED) {
      nichtAngemeldet();
    }
    else if (statusAfterLogon == STATUS.WITHOUT_REGISTRATION) {
      nichtAngemeldet();
    }
  }



  /**
   * Sollen Chatanfragen abgelehnt werden?
   *
   *
   * @param domain
   *                   an diese Domäne wird gesendet
   * @param pseudoid
   *                   eine Benutzer-ID
   * @param chatdenied
   *                   {@code true}, Chatanfragen werden abgelehnt
   */
  void saveChatdenied(String domain, String pseudoid, boolean chatdenied) {

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
          .append("/javacommserver/user/write/chatdenied");
      WebTarget target = client.target(url.toString());
      Form form = new Form();
      form.param("pseudoid", pseudoid).param("chatdenied", String.valueOf(chatdenied));
      target.request(MediaType.TEXT_PLAIN).post(Entity.form(form));
    }
  }



  /**
   * Der Tooltip im Chateingabefeld kann eingeschaltet oder ausgeschaltet werden.
   * 
   * 
   * @param pseudoUserid
   *                       dieser Anwender
   * @param pseudoPassword
   *                       dieses Pseudopasswort
   * @param chattooltip
   *                       Tooltip einschalten oder ausschalten
   * @param drift
   *                       die Zeitverschiebung zwischen Server und Client
   */
  void saveChattooltip(String pseudoUserid, String pseudoPassword, boolean chattooltip, long drift) {

    TransferTooltip tooltip = new TransferTooltip();
    tooltip.setPseudoid(pseudoUserid);
    tooltip.setTooltip(chattooltip);
    CBlock cblock = standardBlock.create(pseudoPassword, drift);
    tooltip.setCblock(cblock);
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      WebTarget target = client
          .target(Constants.PROTOCOL + Constants.DOMAIN + "/javacommserver/user/write/chattooltip");

      try(
        Response response = target.request(MediaType.TEXT_PLAIN)
            .put(Entity.entity(tooltip.toString(), MediaType.APPLICATION_JSON))
      ) {
        if (response.getStatus() != 204) {
          log.warn(
              "Tooltip einschalten oder ausschalten konnte nicht ausgeführt werden - HTTP {}",
              response.getStatus()
          );
          return;
        }
      }

    }
    catch (Exception e) {
      log.warn(e);
      return;
    }
    EventQueue.invokeLater(() -> {
      // alle Chatframes durchlaufen
      for (JChatFrame chatframe : chatVector) {
        // chattooltip wird in saveChattooltip verändert
        chatframe.chatTooltip(chattooltip);
      }
    });

  }



  void saveDirectories(TransferFiletransferConfig transfer) {
    Form form = new Form();
    form.param("config", transfer.toString());
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      WebTarget target = client
          .target(Constants.PROTOCOL + Constants.DOMAIN + "/javacommserver/filetransfer/write/directories");
      target.request(MediaType.TEXT_PLAIN).post(Entity.form(form), Boolean.class);
    }
    catch (Exception e) {
      log.error(e.getMessage());
    }
  }



  public void saveFiletransferConfig(TransferFiletransferConfig transfer) {
    Form form = new Form();
    form.param("config", transfer.toString());
    Boolean result = false;
    ClientConfig bla = new ClientConfig();
    bla.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(bla)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/filetransfer/write");

      WebTarget target = client.target(url.toString());
      result = target.request(MediaType.TEXT_PLAIN).post(Entity.form(form), Boolean.class);

    }
    catch (Exception e) {
      log.error(e.getMessage());
    }
    if (result) {
      transferConfig.setPort(transfer.getPort());
      transferConfig.setStarttype(transfer.getStarttype());
    }
    else {
      // TODOfehlermeldung
      EventQueue.invokeLater(
          () -> JOptionPane.showMessageDialog(
              JChat.this,
              "<html>"
                  + "Die Einstellungen für den Serverstart und den Port konnten nicht gespeichert werden."
                  + "<br>"
                  + "</html>",
              menuOnlinetransfer.getText(), JOptionPane.WARNING_MESSAGE
          )
      );
    }
  }



  /**
   * Speichere vor dem Programmende globale Benutzerattribute ab.
   *
   *
   * @param domain
   *                        an diese Domäne wird gesendet
   * @param pseudoid
   *                        die Pseudoid von einem Anwender für das Routing
   * @param pseudoPassword
   *                        das verschlüsselte Passwort
   * @param drift
   *                        Zeitabweichung zwischen Server und Client
   * @param playervolume
   *                        die Lautstärke vom Mediaplayer
   * @param iptvvolume
   *                        die Lautstärke vom Fernseher
   * @param eis
   *                        das Farbschema der Benutzeroberfläche
   * @param language
   *                        die gewählte Oberflächensprache
   * @param chatDownloadDir
   *                        eine Anlage wird in dieses Verzeichnis kopiert
   * @param chatUploadDir
   *                        ein Anlage wird aus diesem Verzeichnis hochgeladen
   *
   * @return der Rückgabewert kann {@code null sein}
   */
  public void saveGlobalAttributes(String domain, String pseudoid, String pseudoPassword, long drift,
      int playervolume, int iptvvolume, Helado helado, Language language, String downloadDir,
      String chatUploadDir) {

    CBlock cblock = standardBlock.create(pseudoPassword, drift);

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {

      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
          .append("/javacommserver/user/write/global/attributes");
      Form form = new Form();
      form.param("pseudoid", pseudoid).param("cblock", cblock.toString())
          .param("playervolume", String.valueOf(playervolume)).param("iptvvolume", String.valueOf(iptvvolume))
          .param("eis", helado.toString()).param("language", language.toString())
          .param("chatDownloadDir", downloadDir).param("chatUploadDir", chatUploadDir);
      try(
        Response result = client.target(url.toString()).request(MediaType.TEXT_PLAIN).post(Entity.form(form))
      ) {

      }
    }
    catch (Exception e) {
      // Für veraltete Versionen vor 260501
      log.warn("Exception wegen einer veralteten Version {}", e.getMessage());
    }
  }



  /**
   * Die Beschreibung für eine Telefonkonferenz wird gespeichert.
   *
   *
   * @param domain
   *                      an dies Domäne wird gesendet
   * @param userid
   *                      der Konferenzorganisator
   * @param konferenzname
   *                      der Konferenzname
   * @param beschreibung
   *                      die Konferenzbeschreibung
   *
   * @return {@code true}, wenn die Beschreibung gespeichert werden konnte
   */
  public void saveKonferenzbeschreibung(String pseudoid, String konferenzname, String beschreibung) {

    Boolean response = false;
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/telko/write/description/");
      WebTarget webtarget = client.target(url.toString());
      Form form = new Form().param("konferenzname", konferenzname)
          .param("description", beschreibung.stripTrailing()).param("pseudoid", pseudoid);
      response = webtarget.request(MediaType.TEXT_PLAIN).post(Entity.form(form), Boolean.class);
    }
    catch (Exception e) {
      log.warn(e.getMessage());
    }

    final boolean result = response;

    EventQueue.invokeLater(() -> {
      if (result) {
        telefonkonferenzframe.setBesprechung(konferenzname, beschreibung.stripTrailing());
      }
      else {
        JOptionPane.showMessageDialog(
            JChat.this, leitungGebrochen.toString(), beschreibung.toString(), JOptionPane.WARNING_MESSAGE
        );

      }
    });

  }



  /**
   *
   * Speichert die Konfigurationseinstellungen für die Domäne und den Mailserver.
   *
   *
   * @param pseudoid
   *                       die Userid von einem Administrator
   * @param block
   *                       enthält Pseudopasswort und Timestamp
   * @param transferConfig
   *                       die zu speichernden Einstellungen
   */
  public void saveKonfigurationsdaten(String pseudoid, CBlock block, TransferConfig transferConfig) {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/administrator/write/config");

      WebTarget target = client.target(url.toString());
      Builder builder = target.request(MediaType.TEXT_PLAIN);
      Form form = new Form();
      form.param("pseudoid", pseudoid).param("cblock", block.toString())
          .param("config", transferConfig.toString());
      final boolean result = builder.post(Entity.form(form), Boolean.class);
      EventQueue.invokeLater(() -> {
        if (result) {
          JOptionPane.showMessageDialog(
              JChat.this, configSaved.toString(), menuAdministrator.getText(), JOptionPane.INFORMATION_MESSAGE
          );
        }
        else {
          JOptionPane.showMessageDialog(
              JChat.this, configNotSaved, menuAdministrator.getText() + " - " + saveFailed.toString(),
              JOptionPane.ERROR_MESSAGE
          );
        }
      });
    }
  }



  /**
   * Ein Benutzerkonto wird logisch gelöscht oder zurückgeholt.
   *
   * @param admin
   *               die Pseudoid von einem Administrator
   * @param cblock
   *               Admins cblock
   * @param konto
   *               in {@code konto} ist die UID und das Gelöscht-Flag enthalten
   */
  public void saveLogicallyDelete(String admin, CBlock cblock, TransferBenutzerkonto konto) {

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    boolean response[] = new boolean[] {false};
    try(
      Client client = ClientBuilder.newClient(config);
      FormDataMultiPart multipart = new FormDataMultiPart()
    ) {
      multipart.field("pseudoid", admin);
      multipart.field("cblock", cblock.toString());
      multipart.field("userid", konto.getUid());
      multipart.field("deleted", konto.isLdelete(), MediaType.TEXT_PLAIN_TYPE);

      client.register(MultiPartFeature.class);
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/administrator/delete/logically");
      WebTarget webtarget = client.target(url.toString());
      response[0] = webtarget.request(MediaType.TEXT_PLAIN)
          .post(Entity.entity(multipart, multipart.getMediaType()), Boolean.class);

    }
    catch (Exception e) {
      log.error(e.getMessage());
      EventQueue.invokeLater(() -> {
        JOptionPane.showMessageDialog(
            JChat.this, leitungGebrochen.toString(), verbindungsproblem.toString(),
            JOptionPane.WARNING_MESSAGE
        );
        administratorFrame.flipLogicallyDelete();
      });
      return;
    }
    EventQueue.invokeLater(() -> {
      if (response[0]) {
        JOptionPane.showMessageDialog(
            administratorFrame, logischGeloescht.toString(), menuKonto.getText(),
            JOptionPane.INFORMATION_MESSAGE
        );
      }
      else {
        JOptionPane.showMessageDialog(
            administratorFrame, logischAktiviert.toString(), menuKonto.getText(),
            JOptionPane.INFORMATION_MESSAGE
        );
      }
    });

  }



  /**
   * Alle verbotenen Spitznamen werden gespeichert.
   *
   *
   * @param userid
   *                  die Userid von einem Admin
   * @param password
   *                  das zugehörige Passwort
   * @param domain
   *                  an diese Domäne wird gesendet
   * @param nicknames
   *                  verbotene Spitznamen
   * @return
   */
  public boolean saveNicknames(String pseudoid, CBlock cblock, List<String> nicknames) {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    boolean result = false;
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/administrator/write/nicknames");
      WebTarget target = client.target(url.toString());
      Form form = new Form();
      form.param("pseudoid", pseudoid).param("cblock", cblock.toString())
          .param("nicknames", Wrapper.toString(nicknames));
      result = target.request(MediaType.TEXT_PLAIN).post(Entity.form(form), Boolean.class);
      return result;
    }
    catch (Exception e) {
      log.error(e.getMessage());
      return false;
    }
  }



  /**
   * Alle veralteten Programmversionen werden gespeichert.
   *
   *
   * @param userid
   *                  die Userid von einem Admin
   * @param password
   *                  das zugehörige Passwort
   * @param domain
   *                  an diese Domäne wird gesendet
   * @param versionen
   *                  alle veralteten Versionen
   *
   * @return {@code true}, die Versionen urden gespeichert
   */
  public boolean saveProgrammversionen(String pseudoid, CBlock cblock, List<String> versionen) {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    boolean result = false;
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/administrator/write/outdated");
      WebTarget target = client.target(url.toString());
      ArrayList<TransferOutdated> list = new ArrayList<>();
      versionen.forEach(version -> {
        TransferOutdated transferOutdated = new TransferOutdated();
        transferOutdated.setProgrammversion(version);
        list.add(transferOutdated);
      });
      Form form = new Form();
      form.param("pseudoid", pseudoid).param("cblock", cblock.toString())
          .param("outdated", Wrapper.toString(list));
      result = target.request(MediaType.TEXT_PLAIN).post(Entity.form(form), Boolean.class);
    }
    catch (Exception e) {
      log.error(e.getMessage());
    }
    return result;
  }



  /**
   * Alle Rekorderdaten werden gespeichert.
   *
   *
   * @param domain
   *                     an diese Domäne wird gesendet
   * @param transferuser
   *                     enthält die Rekorderdaten
   */
  public void saveRekorder(String pseudoid, boolean onRekorder) {
    Boolean response[] = new Boolean[] {false};

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/user/write/rekorder");

      Form form = new Form();
      form.param("pseudoid", pseudoid);
      form.param("rekorder", String.valueOf(onRekorder));
      response[0] = client.target(url.toString()).request(MediaType.TEXT_PLAIN)
          .post(Entity.form(form), Boolean.class);

    }
    catch (Exception e) {
      log.error(e.getMessage());
    }

    EventQueue.invokeLater(() -> {
      if (!response[0]) {
        JOptionPane.showMessageDialog(
            JChat.this, "Die Konfigurationseinstellungen wurden nicht gespeichert.",
            "Bildschirmempfang - " + saveFailed.toString(), JOptionPane.ERROR_MESSAGE
        );
      }
      removeWM(einwilligungFrame);
      frameVector.remove(einwilligungFrame);
      einwilligungFrame.removeAllListener();
      itemBildschirmEmpfangen.setEnabled(true);
      babelfish.remove(einwilligungFrame);
      windowAnrufen.removeWindowAnrufenListener(einwilligungFrame);
    });

  }



  /**
   * Die Teilnehmerliste einer Konferenz wird gespeichert.
   *
   *
   * @param pseudoid
   *                         der Konferenzorganisator
   * @param konferenzname
   *                         der Konferenzname
   * @param nicknamePseudoid
   *                         die Positivliste enthält alle Teilnehmer, die an der
   *                         Konferenz teilnehmen.
   */
  public void saveTeilnehmerliste(String pseudoid, String konferenzname, List<String> nicknames) {

    StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
        .append("/javacommserver/telko/write/participants/");

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    int result;
    String errorText = null;
    try(Client client = ClientBuilder.newClient(config)) {

      WebTarget webtarget = client.target(url.toString());
      Form form = new Form();
      form.param("pseudoid", pseudoid);
      form.param("konferenzname", konferenzname);

      for (String entity : nicknames) {
        form.param("positivliste", entity);
      }

      try(Response response = webtarget.request(MediaType.TEXT_PLAIN).post(Entity.form(form))) {
        result = response.getStatus();
        if (result == 400) {
          errorText = response.readEntity(String.class);
        }
      }
    }

    final String error = errorText;

    EventQueue.invokeLater(() -> {
      if (204 == result) {
        // an die GUI senden
        telefonkonferenzframe.setInviteButton(false);
        // die Refernzliste hat sich geändert!!!!!!!
        // nach dem Speichern muss die Referenzliste die neue Teilenehmerliste sein
        // was im obigen Parameter nicknameUSerid ist, muss referenzliste werden.
        telefonkonferenzframe.setReferenzliste(konferenzname, nicknames);
      }
      else if (400 == result) {
        // Parameter is null
        log.warn("bad request, {}", error);
      }
      else {
        JOptionPane.showMessageDialog(
            JChat.this, leitungGebrochen.toString(), verbindungsproblem.toString(),
            JOptionPane.WARNING_MESSAGE
        );
        telefonkonferenzframe.setInviteButton(false);
        List<String> referencelist = new ArrayList<>(telefonkonferenzframe.getReferenceList());
        List<String> restlicheBenutzer = new ArrayList<>(telefonkonferenzframe.getRestlicheMitglieder());
        telefonkonferenzframe.setReferenzliste(konferenzname, referencelist);
        telefonkonferenzframe.setRestlicheMitglieder(konferenzname, restlicheBenutzer);
      }

    });

  }



  /**
   * Alle Telefondaten werden an den Server gesendet.
   *
   * @param domain
   *                     an diese Domäne wird gesendet
   * @param transferuser
   *                     enthält die Telefondaten
   */
  void saveTelefon(String pseudoid, TransferTelefon transferTelefon) {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    boolean result[] = new boolean[] {false};
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/user/write/telefon");
      Form form = new Form();
      form.param("pseudoid", pseudoid);
      form.param("telefon", transferTelefon.toString());
      result[0] = client.target(url.toString()).request(MediaType.TEXT_PLAIN)
          .post(Entity.form(form), Boolean.class);
    }
    catch (Exception e) {
      log.warn(e.getMessage());
    }
    EventQueue.invokeLater(() -> {
      if (!result[0]) {
        // Meldung an den Anwender
        JOptionPane.showMessageDialog(
            JChat.this, "<html>" + "Die Telefoneinstellungen konnten nicht gespeichert werden." + "</html>",
            "Telefonport speichern", JOptionPane.ERROR_MESSAGE
        );
      }
      removeWM(telefonFrame);
      frameVector.remove(telefonFrame);
      itemTelefonEinrichten.setEnabled(true);
    });
  }



  /**
   * Die Lautstärke wird eingestellt und an den Server gesendet.
   *
   *
   * @param pseudoid
   *                 eine Benutzer-ID
   * @param volume
   *                 die Lautstärke
   */
  void saveTelefonvolume(String pseudoid, int volume) {

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/user/volume");
      Form form = new Form();
      form.param("pseudoid", pseudoid).param("volume", String.valueOf(volume));
      client.target(url.toString()).request(MediaType.TEXT_PLAIN).post(Entity.form(form));
    }
    catch (Exception e) {
      log.warn(e.getMessage());
      log.warn("Telefonlautstärke konnte nicht gespeichert werden.");
    }

  }



  /**
   * Eine Telefonkonferenz wird erstellt.
   *
   *
   * @param domain
   *                      an diese Domäne wird gesendet
   * @param userid
   *                      dieser Anwender hat die Konferenz erstellt
   *
   * @param body
   *                      eine Beschreibung für die Konferenz
   * @param konferenzname
   *                      der Konferenzname
   */
  public void saveTelkoErstellen(String pseudoid, String konferenzname, String texto) {
    Boolean response = false;
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/telko/erstellen");
      WebTarget target = client.target(url.toString());
      Form form = new Form();
      form.param("pseudoid", pseudoid);
      form.param("konferenzname", konferenzname);
      form.param("texto", texto);
      response = target.request(MediaType.TEXT_PLAIN).post(Entity.form(form), Boolean.class);
    }
    catch (Exception e) {}

    final boolean result = response;

    EventQueue.invokeLater(() -> {
      if (result) {
        telefonErstellenFrame.doDefaultCloseAction();
        itemTelkoOrganisieren.doClick();
        telefonkonferenzframe.selectKonferenz(konferenzname);
      }
      else {
        log.warn("Die Konferenz konnte nicht angelegt werden.");
      }

    });

  }



  /**
   * Ein Verfallsdatum für eine Konferenz wird gespeichert.
   *
   *
   * @param domain
   *                      an diese Domäne wird gesendet
   * @param userid
   *                      der Konferenzorganisator
   * @param konferenzname
   *                      der Konferenzname
   * @param expirydate
   *                      das Verfallsdatum
   * @return
   */
  public void saveVerfallsdatum(String pseudoid, String konferenzname, ZonedDateTime expirydate) {
    Boolean response = false;
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/telko/write/expirydate");
      WebTarget webtarget = client.target(url.toString());
      Form form = new Form();
      form.param("pseudoid", pseudoid);
      form.param("konferenzname", konferenzname);
      long datetime = expirydate.toInstant().toEpochMilli();
      form.param("expirydate", String.valueOf(datetime));
      response = webtarget.request(MediaType.TEXT_PLAIN).post(Entity.form(form), Boolean.class);

    }
    catch (Exception e) {
      log.warn(e);
    }
    final boolean result = response;

    EventQueue.invokeLater(() -> {
      if (result) {
        telefonkonferenzframe.setVerfallsdatum(konferenzname, expirydate);
      }
      else {
        log.warn("Verfallsdatum konnte nicht gespeichert werden");
        JOptionPane.showMessageDialog(
            JChat.this, leitungGebrochen.toString(), beschreibung.toString(), JOptionPane.WARNING_MESSAGE
        );
      }

    });

  }



  /**
   * Ein Anwender sucht nach Dateien.
   *
   *
   * @param domain
   *                an diese Domäne wird gesendet
   * @param pattern
   *                verschiedene Suchmuster
   */
  void searchfiles(String[] pattern) {

    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);

    try(Client client = ClientBuilder.newClient(config)) {
      client.register(MessageBodySEARCHFILES.class);
      StringBuilder searchfilesURL = new StringBuilder(Constants.PROTOCOL).append(Constants.DOMAIN)
          .append("/javacommserver/protocol/searchfiles");
      WebTarget webtarget = client.target(searchfilesURL.toString());

      try(Stream<String> stream = Arrays.stream(pattern)) {
        Object[] values = stream.toArray();
        webtarget = webtarget.queryParam("pattern", values);

        SEARCHFILES response = webtarget.request(MediaType.APPLICATION_JSON).get(SEARCHFILES.class);
        EventQueue
            .invokeLater(() -> ftsFrame.addFoundFiles(response.getSearchfiles(), response.hasStarted()));
      }
    }
    catch (Exception e) {
      log.warn(e.getMessage());
    }

  }



  /**
   * Schiebe das Fenster mit dem Knotennamen {@code baumansicht} in den
   * Vordergrund.
   *
   *
   * @param baumansicht
   *                    der Knotenname in der Baumansicht
   */
  private void selectFrame(String baumansicht) {
    for (WM localWM : frameVector) {
      if (localWM.getBaumansicht().equals(baumansicht)) {
        if (localWM instanceof JInternalFrame localJInternalFrame) {
          if (localJInternalFrame.isIcon()) {
            try {
              localJInternalFrame.setIcon(false);
            }
            catch (PropertyVetoException localPropertyVetoException) {
              log.info(localPropertyVetoException);
            }
          }
          localWM.activated();
          break;
        }
        else if (localWM instanceof JFrame localJFrame) {
          if (localJFrame.getState() == 1) {
            localJFrame.setState(0);
          }
          localWM.activated();
          localJFrame.toFront();
          break;
        }
      }
    }
  }



  /**
   * Der Hintergrundfarbverlauf in der Baumansicht wird gesetzt.
   * 
   * @param helado
   *               diese Eissorte
   * @param frame
   *               dieses Fenster in der Baumansicht
   */
  public void setBackgroundTreeview(Helado helado, WindowManagerInternalFrame frame) {
    Color top, bottom;
    switch(helado) {
      case Arando:
        top = (Color) GUI.blaubeere.getLookAndFeel().getDefaults().get("menubar.top");
        bottom = (Color) GUI.blaubeere.getLookAndFeel().getDefaults().get("menubar.bottom");
        frame.setBackgroundGradient(top, bottom);
        break;
      case Fresa:
        top = (Color) GUI.erdbeere.getLookAndFeel().getDefaults().get("menubar.top");
        bottom = (Color) GUI.erdbeere.getLookAndFeel().getDefaults().get("menubar.bottom");
        frame.setBackgroundGradient(top, bottom);
        break;
      case Moca:
        top = (Color) GUI.mokka.getLookAndFeel().getDefaults().get("menubar.top");
        bottom = (Color) GUI.mokka.getLookAndFeel().getDefaults().get("menubar.bottom");
        frame.setBackgroundGradient(top, bottom);
        break;
      case Vainilla:
        top = (Color) GUI.vanille.getLookAndFeel().getDefaults().get("menubar.top");
        bottom = (Color) GUI.vanille.getLookAndFeel().getDefaults().get("menubar.bottom");
        frame.setBackgroundGradient(top, bottom);
        break;
      case Yogur:
        top = (Color) GUI.joghurt.getLookAndFeel().getDefaults().get("menubar.top");
        bottom = (Color) GUI.joghurt.getLookAndFeel().getDefaults().get("menubar.bottom");
        frame.setBackgroundGradient(top, bottom);
        break;
      case Limón:
        top = (Color) GUI.zitrone.getLookAndFeel().getDefaults().get("menubar.top");
        bottom = (Color) GUI.zitrone.getLookAndFeel().getDefaults().get("menubar.bottom");
        frame.setBackgroundGradient(top, bottom);
        break;
      default:
        throw new UnsupportedOperationException("Helado");
    }
  }



  /**
   * Das Farbmuster wird nach einem Wechsel neu eingestellt.
   * 
   * @param sorte
   *              diese Eissorte
   * @throws UnsupportedLookAndFeelException
   */
  private void setColorSample(Sorte sorte) throws UnsupportedLookAndFeelException {

    Color color = null;
    switch(sorte) {
      case BLAUBEERE:
        helado = Helado.Arando;
        UIManager.setLookAndFeel(GUI.blaubeere.getLookAndFeel());
        SwingUtilities.updateComponentTreeUI(JChat.this);
        color = (Color) GUI.blaubeere.getLookAndFeel().getDefaults().get("nimbusSelectionBackground");
        break;
      case ERDBEERE:
        helado = Helado.Fresa;
        UIManager.setLookAndFeel(GUI.erdbeere.getLookAndFeel());
        SwingUtilities.updateComponentTreeUI(JChat.this);
        color = (Color) GUI.erdbeere.getLookAndFeel().getDefaults().get("nimbusSelectionBackground");
        break;
      case JOGHURT:
        helado = Helado.Yogur;
        UIManager.setLookAndFeel(GUI.joghurt.getLookAndFeel());
        SwingUtilities.updateComponentTreeUI(JChat.this);
        color = (Color) GUI.joghurt.getLookAndFeel().getDefaults().get("nimbusSelectionBackground");
        break;
      case MOKKA:
        helado = Helado.Moca;
        UIManager.setLookAndFeel(GUI.mokka.getLookAndFeel());
        SwingUtilities.updateComponentTreeUI(JChat.this);
        color = (Color) GUI.mokka.getLookAndFeel().getDefaults().get("nimbusSelectionBackground");
        break;
      case VANILLE:
        helado = Helado.Vainilla;
        UIManager.setLookAndFeel(GUI.vanille.getLookAndFeel());
        SwingUtilities.updateComponentTreeUI(JChat.this);
        color = (Color) GUI.vanille.getLookAndFeel().getDefaults().get("nimbusSelectionBackground");
        break;
      case ZITRONE:
        helado = Helado.Limón;
        UIManager.setLookAndFeel(GUI.zitrone.getLookAndFeel());
        SwingUtilities.updateComponentTreeUI(JChat.this);
        color = (Color) GUI.zitrone.getLookAndFeel().getDefaults().get("nimbusSelectionBackground");
        break;
      default:
        throw new UnsupportedOperationException(
            sorte.toString() + " - diese Eissorte wird nicht unterstützt"
        );
    }

    desktop.setSorte(sorte);
    if (internalplayer != null) internalplayer.corner(color);
    if (iptvframe != null) iptvframe.corner(color);
    if (teilenframe != null) teilenframe.setEissorte(sorte);
    if (serverKonfigurierenFrame != null) serverKonfigurierenFrame.setEissorte(helado);
    if (ftsFrame != null) ftsFrame.corner(color);
    if (schwarzeliste != null) schwarzeliste.centerHeader();
    if (administratorFrame != null) administratorFrame.centerHeader();
    for (JChatFrame frame : chatVector) {
      frame.centerHeader();
      frame.setTickerBackground(sorte);
      setBackgroundTreeview(helado, frame);
    }
    if (launcher != null) setBackgroundTreeview(helado, launcher);
    if (callerframe != null) setBackgroundTreeview(helado, callerframe);
    if (empfaengerFrame != null) setBackgroundTreeview(helado, empfaengerFrame);
    if (telefonFrame != null) setBackgroundTreeview(helado, telefonFrame);
    if (telefonTeilnehmenFrame != null) setBackgroundTreeview(helado, telefonTeilnehmenFrame);
    if (einwilligungFrame != null) setBackgroundTreeview(helado, einwilligungFrame);
    if (teilenframe != null) setBackgroundTreeview(helado, teilenframe);
    if (projektorframe != null) setBackgroundTreeview(helado, projektorframe);
    if (iptvframe != null) setBackgroundTreeview(helado, iptvframe);
    if (internalplayer != null) setBackgroundTreeview(helado, internalplayer);
    if (ftsFrame != null) setBackgroundTreeview(helado, ftsFrame);
    if (schwarzeliste != null) setBackgroundTreeview(helado, schwarzeliste);

  }



  /**
   * Der User ändert seine Spracheinstellung.
   * 
   * @param domain
   *                 an diese Domäne wird gesendet
   * @param idioma
   *                 diese Spracheinstellung
   * @param pseudoid
   *                 dieser User
   */
  private void setLanguageAsync(String domain, Language idioma, String pseudoid) {

    Thread.startVirtualThread(() -> {
      ClientConfig config = new ClientConfig();
      config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
          .append("/javacommserver/user/write/language");
      try(Client client = ClientBuilder.newClient(config)) {
        WebTarget target = client.target(url.toString()).path(idioma.toString());
        target.request().put(Entity.entity(pseudoid, MediaType.TEXT_PLAIN), Boolean.class);

      }
      catch (Exception e) {
        // TODO eventuell Fehlerdialog
        log.warn(e);
      }
    });

  }



  /**
   * Screenshot mit Mauspointer wird angezeigt.
   * 
   * @param finalImg
   *                 neues Bild
   */
  private void showScreenshot(BufferedImage finalImg) {
    if (frameVector.stream().anyMatch(wm -> wm.getType() == Frames.PROJEKTOR)) {
      projektorframe.setImage(finalImg);
    }
    if (frameVector.stream().anyMatch(wm -> wm.getType() == Frames.TELKOPROJEKTOR)) {
      telkoprojektorframe.setImage(finalImg);
    }
  }



  /**
   * Öffne eine Datei und spiele weitere Dateien in demselben Verzeichnis ab.
   */
  protected void shuffle() {
    shuffleChooser = new MediaFileChooser(root.getMediaplayer().getSinglefile());
    shuffleChooser.setAcceptAllFileFilterUsed(false);

    UIManager.put("FileChooser.cancelButtonText", cancel.toString());
//    UIManager.put("FileChooser.cancelButtonToolTipText", "Verzeichnis bestätigen");
    SwingUtilities.updateComponentTreeUI(shuffleChooser);

    shuffleChooser.setDialogTitle("Musikverzeichnis markieren");
    shuffleChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
    FileNameExtensionFilter mp4 = new FileNameExtensionFilter("*.mp4", PlayerFilter.MP4);
    FileNameExtensionFilter mp3 = new FileNameExtensionFilter("*.mp3", PlayerFilter.MP3);
    FileNameExtensionFilter m4a = new FileNameExtensionFilter("*.m4a", PlayerFilter.M4A);
    FileNameExtensionFilter aac = new FileNameExtensionFilter("*.aac", PlayerFilter.ADTS);
    FileNameExtensionFilter vorbis = new FileNameExtensionFilter("*.ogg", PlayerFilter.OGG);
    FileNameExtensionFilter vorbisVideo = new FileNameExtensionFilter("*.ogv", PlayerFilter.OGV);
    FileNameExtensionFilter song = new FileNameExtensionFilter(
        "*.aac, *.m4a, *.mp3, *.mp4, *.ogg", PlayerFilter.SONG
    );
    shuffleChooser.setApproveButtonText(open.toString());
    shuffleChooser.setApproveButtonToolTipText("Datei oder Verzeichnis klicken");
    shuffleChooser.addChoosableFileFilter(song);
    shuffleChooser.addChoosableFileFilter(aac);
    shuffleChooser.addChoosableFileFilter(m4a);
    shuffleChooser.addChoosableFileFilter(mp3);
    shuffleChooser.addChoosableFileFilter(mp4);
    shuffleChooser.addChoosableFileFilter(vorbis);
    shuffleChooser.addChoosableFileFilter(vorbisVideo);
    shuffleChooser.setPreferredSize(GUI.FILE_CHOOSER_SIZE);

    shuffleChooser.viewTypeDetails();
    shuffleChooser.addPropertyChangeListener(propertyEvent -> {
      if ("fileFilterChanged".equals(propertyEvent.getPropertyName())) {
        shuffleChooser.sort();
      }
    });
    shuffleChooser.sort();
    shuffleChooser.inputfield();

    int i = shuffleChooser.showOpenDialog(JChat.this);
    if (i != JFileChooser.APPROVE_OPTION) return;

    FileNameExtensionFilter filefilter = (FileNameExtensionFilter) shuffleChooser.getFileFilter();
    try {

      Mediaplayer mediaplayer = root.getMediaplayer();
      Directories directories = mediaplayer.getDirectories();
      if (mediaplayer.getDirectories() == null) {
        directories = new Directories();
        mediaplayer.setDirectories(directories);
      }
      Directory directory = new Directory();
      directory.setPathToDirectory(shuffleChooser.getCurrentDirectory().getAbsolutePath());
      directories.getDirectory().add(directory);
      for (String extension : filefilter.getExtensions()) {
        directory.getExtensions().add(extension);
      }
      playlist = PlayerFilter.audiofiles(PlayerFilter.toPath(directory.getPathToDirectory()), filefilter);

      // log.info("Anzahl Musikkiste=" +
      // menuMusikkiste.getMenuComponents().length);

      if (menuMusikkiste.getMenuComponents().length == 1) {
        // Es gibt bisher nur den Eintrag Leeren
        menuMusikkiste.addSeparator();
      }

      // die alten Einstellungen immer löschen
      alleHakenAusSpiellisteEntfernen();

      // Ist die Liste neu?
      boolean found = false;
      Component[] menuComponents = menuMusikkiste.getMenuComponents();
      for (Component tmp : menuComponents) {
        if (tmp instanceof MultilingualCheckBoxMenuItem item) {
          found = found || item.getText().equals(directory.getPathToDirectory());
          if (found) {
            item.setSelected(true);
            break;
          }
        }
      }

      if (!found) {
        // Die Spielliste ist neu
        // log.info("Spielliste ist neu");
        randomItem = new MultilingualCheckBoxMenuItem(shuffleChooser.getCurrentDirectory().getAbsolutePath());
        randomItem.setSelected(true);
        randomItem.setHorizontalTextPosition(SwingConstants.LEFT);
        randomItem.setMargin(new Insets(1, 42, 2, 43)); // 42 =
        // 38(Bild)+4(Abstand)
        randomItem.addActionListener(arg -> {

          MultilingualCheckBoxMenuItem source = (MultilingualCheckBoxMenuItem) arg.getSource();
          // source ist entscheidend
          if (source.isSelected()) {
            alleHakenAusSpiellisteEntfernen();
            source.setSelected(true);
            // ohne Window Scclie�en und buttonClose
            removeAllPlayerFrames(Control.NULL);
            try {
              itemSpielliste.setSelected(true);
              FileNameExtensionFilter extensionsFilter = null;
              try {
                for (Directory dir : root.getMediaplayer().getDirectories().getDirectory()) {
                  if (!dir.getPathToDirectory().equals(source.getText())) continue;
                  List<String> extensions = dir.getExtensions();
                  extensionsFilter = new FileNameExtensionFilter(
                      null, extensions.toArray(new String[extensions.size()])
                  );
                  break;
                }
                playlist = PlayerFilter.audiofiles(PlayerFilter.toPath(source.getText()), extensionsFilter);
                playShuffle(playlist);
              }
              catch (FilterException | NoAudiofileException | NoDirectoryException | NullPathException e) {
                log.error(e.getMessage(), e.getCause());
                itemSpielliste.setSelected(false);
                source.setSelected(false);
              }
            }
            catch (ShuffleException e) {
              JOptionPane.showMessageDialog(
                  JChat.this, "<html>" + alleMusikstuecke.toString() + "</html>", "Shuffle",
                  JOptionPane.INFORMATION_MESSAGE
              );
              itemSpielliste.setSelected(false);
            }
          }
          else {
            // Alle bisherigen Songs werden unterbrochen und die Frames
            // gelöscht
            // ohne buttonClose oder Window Schließen
            removeAllPlayerFrames(Control.NULL);
            JOptionPane.showMessageDialog(
                JChat.this, "<html>" + zufuallsmodus.toString() + "</html>", "Shuffle",
                JOptionPane.INFORMATION_MESSAGE
            );
            itemSpielliste.setSelected(false);
          }
        });
        menuMusikkiste.add(randomItem);
      }
      else {
        // log.info("2222222222222222222222222222Spielliste ist nicht neu");

      }
    }
    catch (FilterException e) {
      JOptionPane.showMessageDialog(
          JChat.this,
          "<html>" + "<head/>"
              + "<body>"
              + "Im übergebenen Verzeichnis kann nicht gefiltert werden.<br>Der Zufallsmodus wird abgeschaltet."
              + "</body>"
              + "</html>",
          "Shuffle", JOptionPane.ERROR_MESSAGE
      );
      itemSpielliste.setSelected(false);
      return;
    }
    catch (NoAudiofileException e) {
      JOptionPane.showMessageDialog(
          JChat.this, keineAbspielbarenLieder.toString(), "Shuffle", JOptionPane.INFORMATION_MESSAGE
      );
      itemSpielliste.setSelected(false);
      return;
    }
    catch (NoDirectoryException e) {
      JOptionPane.showMessageDialog(
          JChat.this, "<html>" + e.getMessage() + "<br>" + zufuallsmodus.toString() + "</html>", "Shuffle",
          JOptionPane.ERROR_MESSAGE
      );
      itemSpielliste.setSelected(false);
      return;
    }
    catch (NullPathException e) {
      log.error(e.getMessage(), e.getCause());
      return;
    }

    // ohne Window Sccließen und buttonClose
    removeAllPlayerFrames(Control.NULL);

    String firstTitle = shuffleChooser.getSelectedFile().getAbsolutePath();
    log.info(firstTitle);
    playShuffle(playlist, firstTitle);

  }



  void shutdownFTServer(FTServer server, JServerKonfigurierenFrame peerFrame) {
    if (server == null || peerFrame == null) return;
    log.info("server herunterfahren, nur den Pool und alle Clients abhängen");

    server.shutdown();

    Runnable task1 = () -> {
      peerFrame.startStop(false);
      // abschalten transferpane
      // ftService.ftsIsDown(); muss gepr�ft werden ftService ist ein Frame
    };
    EventQueue.invokeLater(task1);
    // DELETEUPLOADFILES senden
    // und abschalten transferpane

    DELETEUPLOADFILES deletefiles = new DELETEUPLOADFILES();
    deletefiles.setCommand(Command.DELETEUPLOADFILES);
    deletefiles.setHeader(REQUEST);
    deletefiles.setDataset(Protocol.DATASET);
    deletefiles.setIp(myIP);
    deletefiles.setPseudoid(userdata.getPseudoid());
    executorChat.execute(() -> wsClient.sendMessage(deletefiles));

  }



  /**
   * Ein Anwender möchte sich einschreiben.
   *
   *
   * @param domain
   *               an diese Domäne wird gesendet
   * @param email
   *               EMail-Adresse vom Benutzer
   * @param lang
   *               die Sprache kommt aus JLogon/JRegistry
   * @return SIGNIN-Protocol-String
   */
  public SIGNIN signin(String domain, String email, String lang) {

    SIGNIN signin = new SIGNIN();
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      client.register(MessageBodySIGNIN.class);
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
          .append("/javacommserver/signin");
      WebTarget target = client.target(url.toString());
      Form form = new Form();
      form.param("email", email);
      form.param("lang", lang);
      signin = target.request(MediaType.APPLICATION_JSON)
          .post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED), SIGNIN.class);
    }
    catch (Exception e) {
      log.error(e.getMessage());
    }
    return signin;

  }



  /**
   * Alle Lautsprecherleitungen werden freigegeben.
   */
  private void speakerTerminate() {
    // Kopie von new ArrayList, weil ArrayList nicht threadsafe ist
    // eventuell später CopyOnWriteArrayList einbauen
    new ArrayList<>(conferenceSpeakerlist).forEach(ConferenceSpeaker::release);
    conferenceSpeakerlist.clear();
  }



  /**
   * Das Oberflächenfarbschema wechselt.
   *
   *
   * @param sorte
   *              das neue Farbschema
   */
  void switchColorTheme(Sorte sorte) {
    switch(sorte) {
      case MOKKA:
        itemMokka.setSelected(true);
        itemMokka.doClick();
        break;
      case VANILLE:
        itemVanille.setSelected(true);
        itemVanille.doClick();
        break;
      case JOGHURT:
        itemJoghurt.setSelected(true);
        itemJoghurt.doClick();
        break;
      case BLAUBEERE:
        itemBlaubeere.setSelected(true);
        itemBlaubeere.doClick();
        break;
      case ERDBEERE:
        itemErdbeere.setSelected(true);
        itemErdbeere.doClick();
        break;
      case ZITRONE:
        itemZitrone.setSelected(true);
        itemZitrone.doClick();
        break;
      default:
        itemVanille.setSelected(true);
        itemVanille.doClick();
        break;
    }

  }



  /**
   * Die Oberflächensprache wird aktiviert.
   *
   */
  void switchLanguage(ISO639 language) {
    babelfish.forEach(babel -> {
      babel.setLanguage(language);
    });
  }



  /**
   * Schalte alle Menüpunkte ein, wenn der Anwender online ist.
   * 
   * @param online
   *               {@code true}, der Anwender ist online
   */
  void switchOnMenu(boolean online) {
    itemBearbeiten.setEnabled(online);
    itemAnmelden.setEnabled(!online);
    itemRegistrieren.setEnabled(!online);
    itemNeuesPasswortAnfordern.setEnabled(!online);
    itemLaunch.setEnabled(online);
    itemChatanfragenAblehnen.setEnabled(online);
    itemPrivateTalk.setEnabled(online);
    itemBesprechungsraum.setEnabled(online);
    itemGruppenraum.setEnabled(online);
    itemHochladenRunterladen.setEnabled(online);
    itemServerKonfigurieren.setEnabled(online);
    itemPausenraum.setEnabled(online);
    itemTelefonEinrichten.setEnabled(online);
    telefonateAblehnen.setEnabled(online);
    itemAnrufen.setEnabled(online);
    itemSchwarzeListe.setEnabled(online);
    itemBildschirmEmpfangen.setEnabled(online);
    itemTeilenframe.setEnabled(online);
    itemTelkoErstellen.setEnabled(online);
    itemTelkoOrganisieren.setEnabled(online);
    itemTelkoTeilnehmen.setEnabled(online);
    itemVerwalten.setEnabled(online);

  }



  /**
   * 
   * Dem Server wird mitgeteilt, dass der Projektor eingeschaltet wurde.
   * 
   * @param userid
   *               dieser Anwender
   */
  public void switchProjektor(String pseudoid) {
    switchProjektor(pseudoid, true);
  }



  /**
   * Dem Server wird mitgeteilt, ob der Projektor eingeschaltet oder ausgeschaltet
   * oder ausgeschaltet wurde.
   * 
   * @param userid
   *               dieser Anwender
   * @param on
   *               {@code true}, einschalten
   */
  public void switchProjektor(String pseudoid, boolean on) {
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    config.property(ClientProperties.READ_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      WebTarget target = client
          .target(Constants.PROTOCOL + Constants.DOMAIN + "/javacommserver/user/write/onprojektor");
      TransferUser transferUser = new TransferUser();
      transferUser.setOnprojektor(on);
      Form form = new Form();
      form.param("pseudoid", pseudoid);
      form.param("projektor", String.valueOf(on));
      target.request(MediaType.TEXT_PLAIN).post(Entity.form(form), Boolean.class);
    }
    catch (Exception e) {
      log.warn(e.getMessage());
    }
  }



  /**
   *
   *
   * @param pseudoid
   *                 ein Administrator
   * @param password
   *                 Passwort für den Mailserver
   * @param domain
   *                 an diese Domäne wird gesendet
   * @param config
   *                 die Konfigurationseinstellungen
   *
   * @return {@code true}, die Mail konnte gesendet werden
   */
  public void testmail(String pseudoid, CBlock cblock, String domain, String smtpHost, String smtpUser,
      Integer smtpPort, String smtpPassword, String adminPostfach) {
    CompletableFuture.runAsync(() -> {

      try(Client client = ClientBuilder.newClient()) {
        Form form = new Form();
        form.param("pseudoid", pseudoid).param("cblock", cblock.toString()).param("host", smtpHost)
            .param("user", smtpUser).param("port", smtpPort.toString()).param("smptPassword", smtpPassword)
            .param("adminPostfach", adminPostfach);

        StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
            .append("/javacommserver/administrator/testmail");

        WebTarget target = client.target(url.toString());
        try(Response response = target.request(MediaType.TEXT_PLAIN).post(Entity.form(form));) {
          log.info("gesendet {},{},{},{}, {}", smtpHost, smtpUser, smtpPort, smtpPassword, adminPostfach);

          switch(response.getStatus()) {
            case 200:
              String help = response.readEntity(String.class);
              String neu = testmailWurdeGesendet.toString().replace("XXX", help)
                  .replace("YYY", checkadminMail.toString());
              EventQueue.invokeLater(
                  () -> JOptionPane.showMessageDialog(
                      JChat.this, neu, menuAdministrator.getText(), JOptionPane.INFORMATION_MESSAGE
                  )
              );

              break;
            case 403:
              EventQueue.invokeLater(
                  () -> JOptionPane.showMessageDialog(
                      JChat.this,
                      "<html>" + dieTestmailKonnte.toString()
                          + "<br><br>"
                          + Status.FORBIDDEN.getReasonPhrase()
                          + " - "
                          + userdata.getNickname()
                          + hatKeineBerechtigung.toString()
                          + "</html>",
                      menuAdministrator.getText() + testmailNichtGesendet.toString(),
                      JOptionPane.ERROR_MESSAGE
                  )
              );

              break;
            case 500:
              String error = response.readEntity(String.class);
              EventQueue.invokeLater(
                  () -> JOptionPane.showMessageDialog(
                      JChat.this,
                      "<html>" + dieTestmailKonnte.toString()
                          + "<br><br>"
                          + Status.INTERNAL_SERVER_ERROR.getReasonPhrase()
                          + " - "
                          + error
                          + "</html>",
                      menuAdministrator.getText() + testmailNichtGesendet.toString(),
                      JOptionPane.ERROR_MESSAGE
                  )
              );

              break;
            case 400:
              String fehlerhafterParameter = response.readEntity(String.class);
              EventQueue.invokeLater(
                  () -> JOptionPane.showMessageDialog(
                      JChat.this,
                      "<html>" + "HTTP 400 - ein fehlerhafter Parameter verursachte: "
                          + fehlerhafterParameter
                          + "</html>",
                      menuAdministrator.getText() + testmailNichtGesendet.toString(),
                      JOptionPane.ERROR_MESSAGE
                  )
              );

              break;
            case 422:
              String fehlerhafterPort = response.readEntity(String.class);
              EventQueue.invokeLater(
                  () -> JOptionPane.showMessageDialog(
                      JChat.this, "<html>" + fehlerhafterPort + "</html>",
                      menuAdministrator.getText() + testmailNichtGesendet.toString(),
                      JOptionPane.ERROR_MESSAGE
                  )
              );
              break;
            default:
              throw new UnsupportedOperationException(response.getStatusInfo().toEnum().toString());

          }

        }
      }

    }, executorJersey);

  }



  /**
   * Übertrage alle Dateieinträge aus dem Upload-Verzeichnis auf den Server.
   *
   */
  synchronized void transmit(String[] pathtofile) {
    // userdata kann bei der Anmeldung null sein, weil der Thread sofort nach
    // Programmstart läuft
    if (userdata == null) return;

    // log.info("TRANSFER, diese Dateien werden als Update in die Datenbank
    // eingetragen.");
    // Maximal 64 in einem Chunk übertragen

    textbuffer = new TextBuffer(pathtofile, 64);
    List<String> partial = textbuffer.readPartial();

    /* int value = 0; for (int index=0; index<partial.size(); index++) {
     * //log.info(partial.get(index)); value += partial.get(index).length(); } */

    // log.info("Anzahl=" + partial.size());
    // log.info("Bytes=" + value);

    Runnable runnable = () -> {
      UPLOADFILES uploadfiles = new UPLOADFILES();
      uploadfiles.setCommand(Command.UPLOADFILES);
      uploadfiles.setHeader(REQUEST);
      uploadfiles.setDataset(Protocol.DATASET);
      uploadfiles.setPseudoid(userdata.getPseudoid());
      uploadfiles.setIp(myIP);
      Uploadfile[] uploadfile = new Uploadfile[partial.size()];
      for (int index = 0; index < uploadfile.length; index++) {
        uploadfile[index] = new Uploadfile();
        String filename = partial.get(index);
        uploadfile[index].setFilename(filename);
        Path path = Paths.get(filename);
        try {
          uploadfile[index].setFilesize(Files.size(path));
        }
        catch (IOException e) {
          log.warn(e.getMessage(), e.getCause());
        }
      }
      uploadfiles.setUploadfile(uploadfile);
      uploadfiles.setStarted(true);
      uploadfiles.setPort(transferConfig.getPort());
      wsClient.sendMessage(uploadfiles);
    };
    executorChat.execute(runnable);
  }



  /**
   * Die Mitgliederliste eines Chatraumes wird in aktualisierter Form an den
   * Server übergeben.
   *
   * @param room
   *                 Chatraum
   * @param roomtype
   *                 BESPRECHUNGSRAUM, GRUPPENRAUM, PAUSENRAUN, FORUM
   * @param chatuser
   *                 eine Liste aller Mitglieder und ihrer Attribute
   */
  void updateChatuser(String room, Roomtype roomtype, ChatUser[] chatuser) {
    Runnable task = () -> {
      CHANGETOPICMEMBER request = new CHANGETOPICMEMBER();
      request.setCommand(Command.CHANGETOPICMEMBER);
      request.setHeader(REQUEST);
      request.setDataset(Protocol.DATASET);
      request.setRoom(room); // für welchen Raum
      request.setRoomtype(roomtype);
      request.setPseudoid(userdata.getPseudoid()); // Absender
      request.setChatUser(chatuser);
      wsClient.sendMessage(request);
    };
    executorChat.execute(task);
  }



  /**
   * Der Anwender erhält die Meldung, dass eine Aktualisierung eingeleitet wird.
   *
   */
  private void updateMessage() {
    outdated = readOutdatedVersions(jLogon.getUrl());

    EventQueue.invokeLater(() -> {

      if (outdated) {
        String neuerText = labelAktualisieren.getText().replace("XXX", String.valueOf(Constants.VERSION));
        labelAktualisieren.setText(neuerText);
        JOptionPane.showMessageDialog(
            JChat.this, labelAktualisieren, versioncheck.toString(), JOptionPane.WARNING_MESSAGE
        );
        macheKeinUpdate = false;

        String url;
        if (Environment.getInstance().isWindows()) {
          url = Constants.DOWNLOAD_WINPORTABLE;
        }
        else if (Environment.getInstance().isLinux()) {
          url = Constants.DOWNLOAD_LINPORTABLE;
        }
        else {
          log.warn("OS not supported");
          macheKeinUpdate = true;
          return;
        }

        autoupdater = new AutoupdateWorker(url, zipPortable);
        autoupdater.addPropertyChangeListener(updateEvent -> {

          if (updateEvent.getPropertyName().equals("error")) {
            Throwable t = (Throwable) updateEvent.getNewValue();
            log.info(t.getMessage());
          }
          else if (updateEvent.getPropertyName().equals("progress")) {
            int value = (int) updateEvent.getNewValue();
            log.info("progress {}", value);
            if (value > 99) return;
            desktop.showBar((int) updateEvent.getNewValue());
            return;
          }
          else if (autoupdater.getState() == StateValue.DONE) {
            String destination = null;
            try {
              // bei DONE immer erfolgreich
              destination = autoupdater.get();
              desktop.showBar(100);
              // Properties mit userdaten
              Properties props = new Properties();
              props.put(JChat.USER, userdata == null ? JLogon.UID : userdata.getEmail());
              props.put(
                  JChat.PASSWORD,
                  userdata == null ? JLogon.PASSWORD : Crypto.decryptAES(userdata.getPassword(), secretAES)
              );
              props.put(JChat.DOMAIN, jLogon.getUrl());// auch bei disconnect, elapsed
              props.put(JChat.LANGUAGE, root.getLanguage().value());
              Path propertyfile = Paths.get(destination, GUI.userproperty);
              try(FileOutputStream outfile = new FileOutputStream(propertyfile.toString())) {
                props.storeToXML(outfile, null);
              }
              catch (IOException e) {
                log.error(e.getMessage(), e);
              }
              ImageIcon iconDownload = new ImageIcon(getClass().getResource(Resource.DOWNLOAD_46x48));
              String succes = dieVersionWurdeErfolgreich.toString()
                  .replace("XXX", String.valueOf(Constants.VERSION)).replace("domain", root.getDomain());
              JOptionPane.showInternalMessageDialog(
                  desktop, succes, programmaktualisierung.toString(), JOptionPane.INFORMATION_MESSAGE,
                  iconDownload
              );

            }
            catch (InterruptedException | ExecutionException e) {
              log.error("update ist schief gelaufen");
              log.info(e.getMessage());
            }

            desktop.hiddenProzent();
            unzipped = true;
          }
        });
        autoupdater.execute();

      }
      else {
        macheKeinUpdate = true;
      }

    });

  }



  /**
   * Der Dateianhang im Chat wird gespeichert.
   * 
   * @param domain
   *                         is sent to this domain
   * @param absoluteFilename
   *                         this file will be uploaded
   * @param identifier
   *                         identifier for the transferred file
   * @param progressbar
   *                         diese Bildschirmkomponente
   * 
   * @return die Anzahl der übertragenen Bytes, -1 wenn der Anwender den
   *         Abbruchknopf betätigte
   * 
   * @throws IOException
   *                     the transmission was aborted
   */
  private long uploadChatfile(String domain, String absoluteFilename, long identifier,
      JProgressBar progressbar) throws IOException {
    long count = 0;
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      Path file = Paths.get(absoluteFilename);
      String dateiname = file.getFileName().toString();
      long filesize = Files.size(file);
      StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
          .append("/javacommserver/chat/upload");
      byte[] chunk = new byte[UPLOAD_DOWNLAOD_BUFFERSIZE];
      try(BufferedInputStream infuffer = new BufferedInputStream(Files.newInputStream(file))) {
        int read;
        int consecutiveChunkNumber = 1;
        ByteArrayOutputStream sendBuffer = new ByteArrayOutputStream();
        while ((read = infuffer.read(chunk)) != -1) {
          if (cancelUpload.get().equals(dateiname)) {
            cancelUpload.set("");
            return -1;
          }
          sendBuffer.reset();
          sendBuffer.write(chunk, 0, read);
          try(FormDataMultiPart multipart = new FormDataMultiPart()) {
            multipart.field("anlagennummer", String.valueOf(identifier));
            multipart.field("chunknummer", String.valueOf(consecutiveChunkNumber));
            multipart.field("content", sendBuffer.toByteArray(), MediaType.APPLICATION_OCTET_STREAM_TYPE);
            Integer sendBytes = client.target(url.toString()).request(MediaType.TEXT_PLAIN)
                .post(Entity.entity(multipart, multipart.getMediaType()), Integer.class);

            count += sendBytes;
            final long help = count;
            EventQueue.invokeLater(() -> {
              int neu = (int) (help * 100l / filesize);
              progressbar.setValue(neu);
            });
          }
          consecutiveChunkNumber++;
        }
      }
    }
    return count;
  }



  /**
   * Übertrage eine angeheftete Datei an den Server.
   * 
   * @param botschaft
   *                  alle Dateiinformationen über den Anhang
   * @param chatframe
   *                  dieser Anzeigedialog
   * @param chatid
   *                  dieser Raumname
   */
  private void uploadChatfileAnlage(Botschaft botschaft, JChatFrame chatframe, String chatid) {
    // Die Nachricht wird mit einem Upload gesendet.
    CircularProgressBarUI circular = new CircularProgressBarUI();
    JProgressBar progressbar = new JProgressBar();
    progressbar.setUI(circular);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_STROKE_WIDTH, 5);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_ACCELERATE, false);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_PULSE_COMPLETION_ACTIVE, true);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_SPARK_ACTIVE, true);
    progressbar.setIndeterminate(false);
    progressbar.setStringPainted(true);
    progressbar.setBackground(Resource.JQUERY_HOMEPAGE);
    progressbar.setForeground(Resource.JQUERY_ORANGE);
    progressbar
        .putClientProperty(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
    progressbar.setPreferredSize(new Dimension(50, 50));

    GridBagLayout gridbag = new GridBagLayout();
    GridBagConstraints con = new GridBagConstraints();
    JPanel uploadPanel = new JPanel(gridbag);
    JLabel uploadLocation = new JLabel(botschaft.getFilename());

    con.gridx = 0;
    con.gridy = 0;
    con.anchor = GridBagConstraints.CENTER;
    con.weightx = 1.0;
    con.insets = new Insets(0, 0, 0, 0);
    gridbag.setConstraints(uploadLocation, con);
    uploadPanel.add(uploadLocation);

    con.gridx = 0;
    con.gridy = 1;
    con.anchor = GridBagConstraints.CENTER;
    con.weightx = 1.0;
    con.insets = new Insets(12, 0, 0, 0);
    gridbag.setConstraints(progressbar, con);
    uploadPanel.add(progressbar);

    MultilingualButton uploadAbbrechen = new MultilingualButton(KEY.BUTTON_ABBRECHEN);
    uploadAbbrechen.setMnemonic(-1);
    uploadAbbrechen.addActionListener(abbrechenEvent -> {
      cancelUpload.set(botschaft.getFilename());
    });
    babelfish.add(uploadAbbrechen);

    JOptionPane pane = new JOptionPane(uploadPanel, JOptionPane.PLAIN_MESSAGE);
    pane.setOptionType(JOptionPane.DEFAULT_OPTION);
    pane.setOptions(new MultilingualButton[] {uploadAbbrechen});

    JInternalFrame uploadFrame = pane.createInternalFrame(chatframe, uploadAttachment.toString());
    MultilingualInternalFrame multi = new MultilingualInternalFrame(KEY.STRING_UPLOAD, uploadFrame);
    babelfish.add(multi);

    multi.setMaximizable(false);
    multi.setResizable(false);
    multi.setIconifiable(false);
    multi.setClosable(false);
    multi.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    multi.setFrameIcon(new ImageIcon(resource.getResource(Resource.UPLOAD_CLOUD_32x32)));
    multi.show();

    CompletableFuture.runAsync(() -> {
      try {
        // Generates a short (64 bit) Universal Unique Identifier
        // Asynchron aufrufen
        Long id = uploadChatfileAttribute(
            root.getDomain(), botschaft.getFilesize(), chatid, botschaft.getAbsoluteFilename(),
            botschaft.getFilename()
        );
        long anzahlBytes = uploadChatfile(root.getDomain(), botschaft.getAbsoluteFilename(), id, progressbar); // synchron
        if (anzahlBytes != -1) {
          Thread.sleep(Duration.ofSeconds(1));
          CHATMESSAGE chatMessage = new CHATMESSAGE();
          chatMessage.setCommand(Command.CHATMESSAGE);
          chatMessage.setHeader(REQUEST);
          chatMessage.setDataset(Protocol.DATASET);
          ChatUser chatuser = new ChatUser();
          chatuser.setBackgroundColor(userdata.getBackgroundColor());
          chatuser.setForegroundColor(userdata.getForegroundColor());
          chatuser.setNickname(userdata.getNickname());
          chatuser.setPseudoid(userdata.getPseudoid());
          chatMessage.setChatUser(chatuser);
          chatMessage.setRoom(chatid);
          chatMessage.setMessage(botschaft.getBotschaft());
          chatMessage.setAttachment(id);
          chatMessage.setFilename(botschaft.getFilename());
          chatMessage.setFilesize(botschaft.getFilesize());
          executorChat.execute(() -> {
            wsClient.sendMessage(chatMessage);
          });
        }
      }
      catch (IOException | InterruptedException e) {
        log.info(e);
        throw new CompletionException(e);
      }
      finally {
        EventQueue.invokeLater(() -> {
          babelfish.remove(uploadAbbrechen);
          babelfish.remove(multi);
          multi.doDefaultCloseAction();
        });

      }
    }, executorJersey);
  }



  /**
   * 
   * @param domain
   *                         an diese Domäne wird gesendet
   * @param filesize
   *                         die Dateigröße der angegängten Datei
   * @param chatid
   *                         dieser Chatraum
   * @param absoluteFilename
   *                         Dateiname mit Pfadangabe
   * @param filename
   *                         Dateiname ohne Pfadangabe
   * @return Identifier für die übertragene Datei
   * 
   * @throws IOException
   *                     generischer Fehler wie falsch formatierte Parameter
   */
  public Long uploadChatfileAttribute(String domain, Long filesize, String chatid, String absoluteFilename,
      String filename) throws IOException {
    if (filesize == null) throw new IllegalArgumentException("filesize is null");
    if (absoluteFilename == null) throw new IllegalArgumentException("absoluteFilename is null");
    Long id;
    ClientConfig config = new ClientConfig();
    config.property(ClientProperties.CONNECT_TIMEOUT, 2000);
    try(Client client = ClientBuilder.newClient(config)) {
      client.register(MultiPartFeature.class);

      try(FormDataMultiPart formMultipart = new FormDataMultiPart()) {

        formMultipart.field("FILESIZE", String.valueOf(filesize)); // 4GB
        formMultipart.field("CHATID", "chatraum");
        formMultipart.field("ABSOLUTE", absoluteFilename);
        formMultipart.field("FILENAME", filename);

        StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
            .append("/javacommserver/chat/save/chatfile/attribute");

        id = client.target(url.toString()).request()
            .post(Entity.entity(formMultipart, MediaType.MULTIPART_FORM_DATA), Long.class);

        formMultipart.cleanup();
      }
    }
    return id;
  }



  /**
   * Diese Methode befindet sich wieder im AWT. Ein FileUpload wird eingeleitet
   * und mit einer ProgressBar angezeigt.
   * 
   * @param chatfile
   *                 diese Nachricht wurde empfangen
   */
  private void uploadPrivateChatfile(PRIVATECHATFILE chatfile) {
    // Führe den edt aus

    final String filename = chatfile.getFilename();
    JChatFrame found = null;
    for (JChatFrame chatframe : chatVector) {
      if (!chatframe.getFrameTitleId().equals(chatfile.getRemoteNickname())) continue;
      found = chatframe;
      found.removeAnlage();
    }
    CircularProgressBarUI circular = new CircularProgressBarUI();
    JProgressBar progressbar = new JProgressBar();
    progressbar.setUI(circular);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_STROKE_WIDTH, 5);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_ACCELERATE, false);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_PULSE_COMPLETION_ACTIVE, true);
    progressbar.putClientProperty(CircularProgressBarUI.PROPERTY_SPARK_ACTIVE, true);
    progressbar.setIndeterminate(false);
    progressbar.setStringPainted(true);
    progressbar.setBackground(Resource.JQUERY_HOMEPAGE);
    progressbar.setForeground(Resource.JQUERY_ORANGE);
    progressbar
        .putClientProperty(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
    progressbar.setPreferredSize(new Dimension(50, 50));

    GridBagLayout gridbag = new GridBagLayout();
    GridBagConstraints con = new GridBagConstraints();
    JPanel uploadPanel = new JPanel(gridbag);
    JLabel uploadLocation = new JLabel(filename);

    con.gridx = 0;
    con.gridy = 0;
    con.anchor = GridBagConstraints.CENTER;
    con.weightx = 1.0;
    con.insets = new Insets(0, 0, 0, 0);
    gridbag.setConstraints(uploadLocation, con);
    uploadPanel.add(uploadLocation);

    con.gridx = 0;
    con.gridy = 1;
    con.anchor = GridBagConstraints.CENTER;
    con.weightx = 1.0;
    con.insets = new Insets(12, 0, 0, 0);
    gridbag.setConstraints(progressbar, con);
    uploadPanel.add(progressbar);

    MultilingualButton uploadAbbrechen = new MultilingualButton(KEY.BUTTON_ABBRECHEN);
    uploadAbbrechen.setMnemonic(-1);
    uploadAbbrechen.addActionListener(abbrechenEvent -> {
      cancelUpload.set(filename);
    });
    babelfish.add(uploadAbbrechen);

    JOptionPane pane = new JOptionPane(uploadPanel, JOptionPane.PLAIN_MESSAGE);
    pane.setOptionType(JOptionPane.DEFAULT_OPTION);
    pane.setOptions(new MultilingualButton[] {uploadAbbrechen});

    JInternalFrame uploadFrame = pane.createInternalFrame(found, uploadAttachment.toString());
    MultilingualInternalFrame multi = new MultilingualInternalFrame(KEY.STRING_UPLOAD, uploadFrame);
    babelfish.add(multi);

    multi.setMaximizable(false);
    multi.setResizable(false);
    multi.setIconifiable(false);
    multi.setClosable(false);
    multi.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    multi.setFrameIcon(new ImageIcon(resource.getResource(Resource.UPLOAD_CLOUD_32x32)));
    multi.show();

    CompletableFuture.runAsync(() -> {
      try {
        long count = uploadPrivateChatfileChunks(
            root.getDomain(), chatfile.getNumber(), chatfile.getAbsoluteFilename(), progressbar
        );
        if (count != -1) {
          Thread.sleep(Duration.ofSeconds(1));
          PRIVATEMESSAGE privatemessage = new PRIVATEMESSAGE();
          privatemessage.setCommand(Command.PRIVATEMESSAGE);
          privatemessage.setHeader(REQUEST);
          privatemessage.setDataset(Protocol.DATASET);
          privatemessage.setPseudoid(userdata.getPseudoid());
          privatemessage.setRemotePseudoid(chatfile.getRemotePseudoid());
          privatemessage.setMessage(chatfile.getMessage());
          privatemessage.setAttachment(chatfile.getNumber());
          privatemessage.setFilename(filename);
          privatemessage.setFilesize(chatfile.getFilesize());
          log.info("-----> {}", privatemessage.toString());
          executorChat.execute(() -> wsClient.sendMessage(privatemessage));
        }
      }
      catch (IOException | InterruptedException e) {
        log.error(e.getMessage());
      }
      finally {
        EventQueue.invokeLater(() -> {
          babelfish.remove(uploadAbbrechen);
          babelfish.remove(multi);
          multi.doDefaultCloseAction();
        });

      }
    }, executorJersey);

  }



  /**
   * 
   * @param domain
   *                         an diese Domäne wird gesendet
   * @param identifier
   *                         der Datenbankschlüssel auf die übertragene Datei
   * @param absoluteFilename
   *                         vollständiger Pfadname und Datei
   * @param filesize
   *                         diese Dateigröße in Bytes
   * 
   * @return die Anzahl übertrangender Bytes, -1 bei Abbruch
   * 
   * @throws IOException
   *                     die Datenblöcke konnten nicht übertragen werden
   */
  long uploadPrivateChatfileChunks(String domain, Long identifier, String absoluteFilename,
      JProgressBar progressbar) throws IOException {

    long count = 0;
    StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
        .append("/javacommserver/chat/upload/private");
    byte[] chunk = new byte[UPLOAD_DOWNLAOD_BUFFERSIZE];
    Path file = Paths.get(absoluteFilename);
    long filesize = Files.size(file);
    String dateiname = file.getFileName().toString();
    try(
      Client client = ClientBuilder.newClient();
      BufferedInputStream infuffer = new BufferedInputStream(Files.newInputStream(file))
    ) {
      int read;
      int consecutiveChunkNumber = 1;
      ByteArrayOutputStream sendBuffer = new ByteArrayOutputStream();
      while ((read = infuffer.read(chunk)) != -1) {
        if (cancelUpload.get().equals(dateiname)) {
          cancelUpload.set("");
          return -1;
        }
        sendBuffer.reset();
        sendBuffer.write(chunk, 0, read);
        try(FormDataMultiPart multipart = new FormDataMultiPart()) {
          multipart.field("anlagennummer", String.valueOf(identifier));
          multipart.field("chunknummer", String.valueOf(consecutiveChunkNumber));
          multipart.field("content", sendBuffer.toByteArray(), MediaType.APPLICATION_OCTET_STREAM_TYPE);
          Integer sendBytes = client.target(url.toString()).request(MediaType.TEXT_PLAIN)
              .post(Entity.entity(multipart, multipart.getMediaType()), Integer.class);

          count += sendBytes;
          final long help = count;
          EventQueue.invokeLater(() -> {
            int neu = (int) (help * 100l / filesize);
            progressbar.setValue(neu);
          });
        }
        consecutiveChunkNumber++;
      }

    }
    return count;
  }



  private void wallpaper() throws SAXException, FileNotFoundException, ValidationException {
    net.javacomm.client.config.schema.Wallpaper wallpaper = root.getWallpaper();
    String str;
    if (wallpaper == null) {
      wallpaper = new net.javacomm.client.config.schema.Wallpaper();
      root.setWallpaper(wallpaper);
      str = Paths.get("", "wallpaper").toAbsolutePath().toString();
      wallpaper.setPathToFile(str);
    }
    else {
      str = wallpaper.getPathToFile();
    }

    FileNameExtensionFilter alle = new FileNameExtensionFilter(alleFormate.toString(), "jpg", "png");
    wallpaperChooser = new MediaFileChooser(str);
    wallpaperChooser.setAcceptAllFileFilterUsed(false);
    wallpaperChooser.setPreferredSize(GUI.FILE_CHOOSER_SIZE);

    FileNameExtensionFilter jpeg = new FileNameExtensionFilter(
        "Joint Photographic (*.jpg), (*.jpeg)", "jpg", "jpeg", "JPG", "JPEG"
    );
    FileNameExtensionFilter png = new FileNameExtensionFilter("Portable Network Graphics (*.png)", "png");

    wallpaperChooser.addChoosableFileFilter(jpeg);
    wallpaperChooser.addChoosableFileFilter(png);
    wallpaperChooser.addChoosableFileFilter(alle);

    wallpaperChooser.setFileFilter(alle);
    wallpaperChooser.viewTypeDetails();
    wallpaperChooser.addPropertyChangeListener(propertyEvent -> {
      if ("fileFilterChanged".equals(propertyEvent.getPropertyName())) {
        wallpaperChooser.sort();
      }
    });
    wallpaperChooser.sort();
    int i = wallpaperChooser.showOpenDialog(this);
    if (i == JFileChooser.APPROVE_OPTION) {
      File wallpaperFile = wallpaperChooser.getSelectedFile();
      wallpaper.setPathToFile(wallpaperFile.getAbsolutePath());
      try {
        desktop.setPicture(wallpaperFile.toURI().toURL());
      }
      catch (IOException e) {
        log.info(e.getMessage(), e);
      }
    }
  }



  /**
   * Das ODX-Leseverzeichnis wird in der Datenbank gespeichert.
   *
   * @param domain
   *                  an diese Domäne wird gesendet
   * @param userid
   *                  der ODX-Anwender
   * @param directory
   *                  das ODX-Verzeichnis, aus dem das letzte PDX-Archiv oder die
   *                  letzte ODX-Datei gelesen wurde
   * @return HTTP-Response-Codes
   */
  public Future<Response> writeOdxDirectory(String domain, String userid, String directory) {
    Client client = ClientBuilder.newClient();
    StringBuilder url = new StringBuilder(Constants.PROTOCOL).append(domain)
        .append("/javacommserver/odx/write/load/directory/");
    WebTarget target = client.target(url.toString()).path(userid);
    return target.request().async().put(Entity.text(directory));
  }

}
