/**
 *  Copyright © 2021-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.streams;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.pump.swing.JSwitchButton;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyVetoException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.javacomm.client.config.schema.Livestream;
import net.javacomm.client.config.schema.Livestreams;
import net.javacomm.client.gui.JSliderVolumePanel;
import net.javacomm.client.mediaplayer.VLCMediaPlayerAdapter;
import net.javacomm.client.resource.Resource;
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.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.WindowAnrufenEvent;
import net.javacomm.window.manager.WindowAnrufenListener;
import net.javacomm.window.manager.WindowManagerInternalFrame;
import uk.co.caprica.vlcj.media.Media;
import uk.co.caprica.vlcj.media.MediaEventAdapter;
import uk.co.caprica.vlcj.media.MediaParsedStatus;
import uk.co.caprica.vlcj.player.base.AudioApi;
import uk.co.caprica.vlcj.player.base.EventApi;
import uk.co.caprica.vlcj.player.base.MediaPlayer;
import uk.co.caprica.vlcj.player.base.VideoApi;
import uk.co.caprica.vlcj.player.component.CallbackMediaPlayerComponent;
import uk.co.caprica.vlcj.player.component.callback.FilledCallbackImagePainter;
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer;



public class StreamFrame extends WindowManagerInternalFrame implements WindowAnrufenListener {

  /**
   *
   * @author llange
   *
   */
  private class DurationAdapter extends MediaEventAdapter {
    private MediaParsedStatus status;

    MediaParsedStatus getStatus() {
      return status;
    }



    @Override
    public void mediaParsedChanged(Media media, MediaParsedStatus newStatus) {
      status = newStatus;
      countdown.countDown();
    }

  }

  /**
   * Gson setzt die Variablen über Reflection!
   *
   * @author llange
   *
   */
  private static class Sender {

    private int code;
    private String programmname;
    private String url;

    @SuppressWarnings("unused")
    int getCode() {
      return code;
    }



    String getProgrammname() {
      return programmname;
    }



    String getUrl() {
      return url;
    }

  }

  private final static Logger log = LogManager.getLogger(StreamFrame.class);
  public final static int TOP_BAR = 220;
  private static final long serialVersionUID = -4639298125644834299L;
  public final static int SCREEN_WIDTH = 900;
  public final static int SCREEN_HEIGHT = 656;
  public final static int SLOTS = 11;
  private BorderLayout borderlayout = new BorderLayout();
  private CallbackMediaPlayerComponent mediaplayerComponent;
  private JComponent videosurface;
  private MultilingualLabel corner;
  private MultilingualString streamBelegen = new MultilingualString(KEY.STRING_STREAM_BELEGEN);
  private MultilingualString nichtBelegt = new MultilingualString(KEY.STRING_NICHT_BELEGT);
  // korrespondiert mit den Livestreams aus der config.xml
  private List<Livestream> listOfLivestreams;
  private boolean guard = false;

  private MultilingualString textfieldaName = new MultilingualString(KEY.STRING_NAME);
  private ButtonGroup programgroup = new ButtonGroup();
  private GsonBuilder gsonbuilder;
  // private ScaledCallbackImagePainter imagePainter;
  // private FixedCallbackImagePainter imagePainter;
  private FilledCallbackImagePainter imagePainter;
  private JPopupMenu popup = new JPopupMenu();

  private MouseAdapter videosurfaceListener;
  private MouseAdapter robotListener;
  private int firsttime = 0;
  private JPanel eispanel = new JPanel();
  private JButton buttonVollbild;

  private ActionListener fullscreenListener;
  private int iptvVolume;
  private AudioApi audioapi;
  private EmbeddedMediaPlayer embedded;

  private IptvSwingWorker iptvSwingWorker;
  private PropertyChangeSupport changes = new PropertyChangeSupport(this);
  private InternalFrameAdapter internalFrameAdapter = new InternalFrameAdapter() {

    @Override
    public void internalFrameActivated(InternalFrameEvent event) {
      changes.firePropertyChange(StreamFrame.class.getName(), Control.NULL, Control.IPTV_FRAME_ACTIVATED);
    }



    @Override
    public void internalFrameClosing(InternalFrameEvent e) {
      shutdown();
      changes.firePropertyChange(StreamFrame.class.getName(), Control.NULL, Control.CLOSE);
    }

  };
  private int bottom = 6;
  private EventApi eventsAPI;
  private VideoApi videoApi;
  private ImageIcon image;
  private ImageIcon fullscreen;
  private URL url;
  private CountDownLatch countdown;
  private DurationAdapter mediaEventAdapter = new DurationAdapter();
  private String record = "";
  private JSplitPane splitpaneHorizontal = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
  private JScrollPane scrollvideopane = new JScrollPane();
  private GridBagLayout gridbag = new GridBagLayout();
  private GridBagConstraints con = new GridBagConstraints();

  private JPanel buttonpanel = new JPanel(gridbag);
  private JScrollPane topPanel = new JScrollPane();
  private JSplitPane splitpaneVertical = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
  private MultilingualLabel imageRobot = new MultilingualLabel();
  private Class<? extends StreamFrame> resource;
  private JSwitchButton switchButton = new JSwitchButton("Stream belegen", false);
  private StreamButton buttonArteDeutsch = new StreamButton("ARTE Deutsch");
  private StreamButton buttonArteFrance = new StreamButton("ARTE Français");
  private StreamButton buttonBBC = new StreamButton("BBC");
  private StreamButton buttonBBCOne = new StreamButton("BBC One");
  private StreamButton buttonBloomberg = new StreamButton("Bloomberg");
  private StreamButton buttonBlueTV = new StreamButton("Blue TV (Brasil)");
  private StreamButton buttonRakutenTV = new StreamButton("Rakuten TV");
  private StreamButton buttonKissTV = new StreamButton("Kiss TV (Music)");
  private StreamButton buttonCanal24 = new StreamButton("Canal 24");
  private StreamButton buttonCNN = new StreamButton("CNN");
  private StreamButton buttonRTDE = new StreamButton("RT DE");
  private StreamButton buttonIndiaNews = new StreamButton("India News");
  private StreamButton buttonMir24 = new StreamButton("МИР 24");
  private StreamButton buttonNTW = new StreamButton("НТВ");
  private StreamButton buttonCGTN_EN = new StreamButton("CGTN English");
  private StreamButton buttonCGTN_FR = new StreamButton("CGTN Français");
  private StreamButton buttonCGTN_ES = new StreamButton("CGTN Español");
  private StreamButton buttonTeleSUR_ES = new StreamButton("teleSUR Español");
  private StreamButton buttonRedeNGT = new StreamButton("Rede NGT");
  private StreamButton buttonNTW_SERIAL = new StreamButton("НТВ Сериал");
  private StreamButton buttonAXN = new StreamButton("AXN Portugal");

  private StreamButton buttonZDFinfo = new StreamButton("ZDFinfo");

  private UserDefinedButton[] userDefinedButtons = new UserDefinedButton[SLOTS];
  private InternalAssignFrame assignframe;

  private MultilingualMenuItem itemVollbild = new MultilingualMenuItem(KEY.VOLLBILDMODUS);
  private MultilingualMenuItem itemLauter = new MultilingualMenuItem(KEY.STRING_LAUTER);
  private MultilingualMenuItem itemLeiser = new MultilingualMenuItem(KEY.STRING_LEISER);
  private MultilingualMenu menuProgramme = new MultilingualMenu(KEY.TV);
  private JCheckBoxMenuItem itemArteDeutsch = new JCheckBoxMenuItem(buttonArteDeutsch.getText());
  private JCheckBoxMenuItem itemArteFrance = new JCheckBoxMenuItem(buttonArteFrance.getText());
  private JCheckBoxMenuItem itemAXN = new JCheckBoxMenuItem(buttonAXN.getText());
  private JCheckBoxMenuItem itemBBC = new JCheckBoxMenuItem(buttonBBC.getText());
  private JCheckBoxMenuItem itemBBCOne = new JCheckBoxMenuItem(buttonBBCOne.getText());
  private JCheckBoxMenuItem itemBloomberg = new JCheckBoxMenuItem(buttonBloomberg.getText());
  private JCheckBoxMenuItem itemBlueTV = new JCheckBoxMenuItem(buttonBlueTV.getText());
  private JCheckBoxMenuItem itemCGTN_EN = new JCheckBoxMenuItem(buttonCGTN_EN.getText());
  private JCheckBoxMenuItem itemCGTN_ES = new JCheckBoxMenuItem(buttonCGTN_ES.getText());
  private JCheckBoxMenuItem itemCGTN_FR = new JCheckBoxMenuItem(buttonCGTN_FR.getText());
  private JCheckBoxMenuItem itemCNN = new JCheckBoxMenuItem(buttonCNN.getText());
  private JCheckBoxMenuItem itemRakutenTV = new JCheckBoxMenuItem(buttonRakutenTV.getText());
  private JCheckBoxMenuItem itemKissTV = new JCheckBoxMenuItem(buttonKissTV.getText());
  private JCheckBoxMenuItem itemRedeNGT = new JCheckBoxMenuItem(buttonRedeNGT.getText());
  private JCheckBoxMenuItem itemCanal24 = new JCheckBoxMenuItem(buttonCanal24.getText());
  private JCheckBoxMenuItem itemRTDE = new JCheckBoxMenuItem(buttonRTDE.getText());
  private JCheckBoxMenuItem itemIndiaNews = new JCheckBoxMenuItem(buttonIndiaNews.getText());
  private JCheckBoxMenuItem itemMir24 = new JCheckBoxMenuItem(buttonMir24.getText());
  private JCheckBoxMenuItem itemNTW = new JCheckBoxMenuItem(buttonNTW.getText());
  private JCheckBoxMenuItem itemTeleSUR_ES = new JCheckBoxMenuItem(buttonTeleSUR_ES.getText());
  private JCheckBoxMenuItem itemNTW_SERIAL = new JCheckBoxMenuItem(buttonNTW_SERIAL.getText());
  private JCheckBoxMenuItem itemZdfinfo = new JCheckBoxMenuItem(buttonZDFinfo.getText());
  private JSliderVolumePanel volumePanel = new JSliderVolumePanel();
  private PropertyChangeListener volumelistener = new PropertyChangeListener() {

    @Override
    public void propertyChange(PropertyChangeEvent event) {
      Integer value = (Integer) event.getNewValue();
      iptvVolume = value;
      synchronized (this) {
        if (audioapi != null) {
          audioapi.setVolume(iptvVolume);
        }
      }
      changes.firePropertyChange(StreamFrame.class.getName(), Control.NULL, Control.IPTV_VOLUME);
    }

  };

  private ActionListener lauterListener = new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent event) {
      // Lautstärke erhöhen um 15 Punkte
      iptvVolume = volumePanel.getLoudness() + 15;
      volumePanel.setLoudness(iptvVolume);
      synchronized (this) {
        try {
          if (audioapi != null) audioapi.setVolume(iptvVolume);
        }
        catch (Error error) {
          log.error(error.getMessage());
        }
      }

    }

  };
  private ActionListener leiserListener = new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent e) {
      // Lautstärke runter um 15 Punkte
      iptvVolume = volumePanel.getLoudness() - 15;
      volumePanel.setLoudness(iptvVolume);
      synchronized (this) {
        try {
          if (audioapi != null) audioapi.setVolume(iptvVolume);
        }
        catch (Error error) {
          log.error(error.getMessage());
        }
      }

    }

  };
  private JButton buttonPower = new JButton();
  private TVSender alterSender;
  private ComponentAdapter componentAdapter = new ComponentAdapter() {

    @Override
    public void componentResized(ComponentEvent event) {
      if (StreamFrame.this.isMaximum()) {
        setHorizontalDivider(1.0);
        setVerticalDivider(firsttime >= 2 ? 0 : TOP_BAR);
        firsttime++;
        // setVerticalDivider(0.0);
      }
      else {
        setHorizontalDivider(SCREEN_WIDTH - WindowManagerInternalFrame.SPLITWIDTH);
        setVerticalDivider(TOP_BAR);
        Rectangle rectangle = StreamFrame.this.getParent().getBounds();
        setLocation(
            (rectangle.width - StreamFrame.SCREEN_WIDTH) / 2,
            (rectangle.height - StreamFrame.SCREEN_HEIGHT) / 2
        );

      }
    }

  };
  private VLCMediaPlayerAdapter playerAdapter = new VLCMediaPlayerAdapter() {

    @Override
    public void stopped(MediaPlayer mediaPlayer) {

    }



    @Override
    public void videoOutput(MediaPlayer mediaPlayer, int newCount) {

      EventQueue.invokeLater(() -> {
        videoApi = mediaPlayer.video();
        // videoApi könnte null sein
        videosurface.requestFocusInWindow();
        try {
          videosurface.setSize(videoApi.videoDimension());
        }
        catch (Exception e) {}
        catch (Error error) {
          log.info(error.getMessage());
        }
        audioapi = mediaPlayer.audio();
        iptvVolume = volumePanel.getLoudness();
        audioapi.setVolume(iptvVolume);
      });

    }
  };
  private Cursor dukeCursor;
  private ImageIcon remoteImage;

  private String oldname;
  private UserDefinedButton alterChannel;

  private String oldnameTelefonEinrichten;
  private ActionListener userDefinedButtonListener = new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent event) {
      UserDefinedButton userDefinedButton = (UserDefinedButton) event.getSource();
      // TODO
      // Ist der UserDefinedButton zugewiesen und gedrückt?
      if (!userDefinedButton.isAssigned()) return;
//      log.info(
//          "slot {}, assigned {}, name {}, url {} ... starten", userDefinedButton.getCode(),
//          userDefinedButton.isAssigned(), userDefinedButton.getText(), userDefinedButton.getStreamURL()
//      );
      zapping(TVSender.Livestream, userDefinedButton, true);
    }

  };

  public StreamFrame() {
    gsonbuilder = new GsonBuilder().disableHtmlEscaping();
    resource = getClass();
    setMaximizable(true);
    setResizable(true);
    setIconifiable(true);
    setClosable(true);
    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    setFrameTitleId(Frames.LIVESTREAMS.toString());
    setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
    url = getClass().getResource(Resource.LIVESTREAM_25x25);
    image = new ImageIcon(url);
    setFrameIcon(image);
    setLayout(borderlayout);
    addInternalFrameListener(internalFrameAdapter);
    init();
  }



  @Override
  public void activated() {
    try {
      setSelected(true);
      windowManager.setActivNode(record);
    }
    catch (PropertyVetoException e) {}
  }



  public void addIptvListener(PropertyChangeListener listener) {
    changes.addPropertyChangeListener(listener);
  }



  /**
   * Baue eine Verbindung zum Server auf.
   *
   * @param adresse
   *                die Serveradresse
   */
  private void connect(String adresse) {
    // der alte muss beendet sein

    embedded = createEmbeddedPlayer();
    countdown = new CountDownLatch(1);
    embedded.media().prepare(adresse);
    eventsAPI = embedded.events();
    eventsAPI.addMediaEventListener(mediaEventAdapter);
    eventsAPI.addMediaPlayerEventListener(playerAdapter);
    embedded.media().parsing().parse(); // asynchron
    try {
      // Warte, bis er mit dem Parsen fertig ist
      countdown.await(7, TimeUnit.SECONDS);
      MediaParsedStatus status = mediaEventAdapter.getStatus();
      if (status == MediaParsedStatus.FAILED) {
        log.warn("Die Adresse ist ungültig -> " + adresse);
        return;
      }
      embedded.media().play(adresse, "network-caching=8960"); // löst Status done aus
    }
    catch (InterruptedException e) {
      log.error(e.getMessage(), e.getCause());
      return;
    }
  }



  /**
   * Eine Hilfsmehode, welche die rechte untere Ecke in der Videoansicht einfärbt.
   * Nach einem L&F-Wechsel wurde die Farbe nicht geändert.
   *
   * @param color
   *              eine Farbe
   */
  public void corner(Color color) {
    corner.setBackground(color);
    eispanel.setBackground(color);

  }



  private EmbeddedMediaPlayer createEmbeddedPlayer() {
    mediaplayerComponent = new CallbackMediaPlayerComponent(null, null, null, true, null, null, null, null);

    if (videosurface != null) {
      // MouseAdapter entfernen
      videosurface.removeMouseListener(videosurfaceListener);
    }

    videosurface = mediaplayerComponent.videoSurfaceComponent();
    videosurfaceListener = new MouseAdapter() {

      @Override
      public void mousePressed(MouseEvent event) {
        if (event.isPopupTrigger()) {
          popup.show(videosurface, event.getX(), event.getY());
        }
      }



      @Override
      public void mouseReleased(MouseEvent event) {
        if (event.isPopupTrigger()) {
          popup.show(videosurface, event.getX(), event.getY());
        }
      }

    };
    videosurface.addMouseListener(videosurfaceListener);
    scrollvideopane.getViewport().add(videosurface);
    scrollvideopane.setCursor(dukeCursor);
    imagePainter = new FilledCallbackImagePainter();
    mediaplayerComponent.setImagePainter(imagePainter);
    EmbeddedMediaPlayer player = mediaplayerComponent.mediaPlayer();
    return player;
  }



  /**
   * Disables all livestream buttons.
   */
  void disableLivestreamButtons() {
    for (Component comp : buttonpanel.getComponents()) {
      if (comp instanceof JButton button) {
        button.setForeground(WMResource.ENABLED_BUTTON_FOREGROUND);
        button.setBackground(WMResource.ENABLED_BUTTON_BACKGROUND);
      }
    }
  }



  @Override
  public String getFrameTitleId() {
    return record;
  }



  @Override
  public Dimension getMinimumSize() {
    return new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT);
  }



  @Override
  public Frames getType() {
    return Frames.LIVESTREAMS;
  }

  // public void start() {
  // start("udp://localhost@:29478");
  // }



  /**
   * Die Lautsprecherstärke wird vom Slider abgelesen.
   *
   *
   * @return die Lautstärke
   */
  public int getVolume() {
    return volumePanel.getLoudness();
  }



  /**
   * Die Knöpfe werden ausgelegt.
   */
  private void init() {
    remoteImage = new ImageIcon(resource.getResource(Resource.REMOTECURSOR));
    Toolkit toolkit = Toolkit.getDefaultToolkit();
    dukeCursor = toolkit.createCustomCursor(remoteImage.getImage(), new Point(0, 0), "fernbedienung");

    popup.setCursor(dukeCursor);
    popup.add(menuProgramme);
    menuProgramme.setIcon(new ImageIcon(resource.getResource(Resource.ROBOT_TV_30x26)));

    menuProgramme.add(itemArteDeutsch);
    menuProgramme.add(itemArteFrance);
    menuProgramme.add(itemAXN);
    menuProgramme.add(itemBBC);
    menuProgramme.add(itemBBCOne);
    menuProgramme.add(itemBloomberg);
    menuProgramme.add(itemBlueTV);
    menuProgramme.add(itemCanal24);
    menuProgramme.add(itemCNN);
    menuProgramme.add(itemCGTN_EN);
    menuProgramme.add(itemCGTN_ES);
    menuProgramme.add(itemCGTN_FR);
    menuProgramme.add(itemIndiaNews);
    menuProgramme.add(itemKissTV);
    menuProgramme.add(itemMir24);
    menuProgramme.add(itemNTW);
    menuProgramme.add(itemNTW_SERIAL);
    menuProgramme.add(itemRakutenTV);
    menuProgramme.add(itemRedeNGT);
    menuProgramme.add(itemRTDE);
    menuProgramme.add(itemTeleSUR_ES);
    menuProgramme.add(itemZdfinfo);

    programgroup.add(itemArteDeutsch);
    programgroup.add(itemArteFrance);
    programgroup.add(itemBBC);
    programgroup.add(itemBBCOne);
    programgroup.add(itemBloomberg);
    programgroup.add(itemBlueTV);
    programgroup.add(itemCanal24);
    programgroup.add(itemCNN);
    programgroup.add(itemCGTN_EN);
    programgroup.add(itemCGTN_ES);
    programgroup.add(itemCGTN_FR);
    programgroup.add(itemIndiaNews);
    programgroup.add(itemKissTV);
    programgroup.add(itemMir24);
    programgroup.add(itemNTW);
    programgroup.add(itemNTW_SERIAL);
    programgroup.add(itemRakutenTV);
    programgroup.add(itemRTDE);
    programgroup.add(itemRedeNGT);
    programgroup.add(itemTeleSUR_ES);
    programgroup.add(itemAXN);
    programgroup.add(itemZdfinfo);

    itemArteDeutsch.addActionListener(event -> {
      buttonArteDeutsch.doClick();
    });
    itemArteFrance.addActionListener(event -> {
      buttonArteFrance.doClick();
    });
    itemBBC.addActionListener(event -> {
      buttonBBC.doClick();
    });
    itemBBCOne.addActionListener(event -> {
      buttonBBCOne.doClick();
    });
    itemBloomberg.addActionListener(event -> {
      buttonBloomberg.doClick();
    });
    itemBlueTV.addActionListener(event -> {
      buttonBlueTV.doClick();
    });
    itemCanal24.addActionListener(event -> {
      buttonCanal24.doClick();
    });
    itemCGTN_EN.addActionListener(event -> {
      buttonCGTN_EN.doClick();
    });
    itemCGTN_ES.addActionListener(event -> {
      buttonCGTN_ES.doClick();
    });
    itemCGTN_FR.addActionListener(event -> {
      buttonCGTN_FR.doClick();
    });
    itemCNN.addActionListener(event -> {
      buttonCNN.doClick();
    });
    itemKissTV.addActionListener(event -> {
      buttonKissTV.doClick();
    });
    itemIndiaNews.addActionListener(event -> {
      buttonIndiaNews.doClick();
    });
    itemMir24.addActionListener(event -> {
      buttonMir24.doClick();
    });
    itemNTW.addActionListener(event -> {
      buttonNTW.doClick();
    });
    itemNTW_SERIAL.addActionListener(event -> {
      buttonNTW_SERIAL.doClick();
    });
    itemTeleSUR_ES.addActionListener(event -> {
      buttonTeleSUR_ES.doClick();
    });
    itemRTDE.addActionListener(event -> {
      buttonRTDE.doClick();
    });
    itemRedeNGT.addActionListener(event -> {
      buttonRedeNGT.doClick();
    });
    itemAXN.addActionListener(event -> {
      buttonAXN.doClick();
    });
    itemRakutenTV.addActionListener(event -> {
      buttonRakutenTV.doClick();
    });
    itemZdfinfo.addActionListener(event -> {
      buttonZDFinfo.doClick();
    });

    popup.addSeparator();
    popup.add(itemVollbild);
    itemVollbild.setIcon(new ImageIcon(resource.getResource(Resource.FULLSCREEN_29x26)));
    itemVollbild.addActionListener(event -> {
      buttonVollbild.doClick();
    });
    popup.addSeparator();
    popup.add(itemLauter);
    itemLauter.setIcon(new ImageIcon(resource.getResource(Resource.LAUTER_32x26)));
    itemLauter.addActionListener(lauterListener);

    popup.add(itemLeiser);
    itemLeiser.setIcon(new ImageIcon(resource.getResource(Resource.LEISER_32x26)));
    itemLeiser.addActionListener(leiserListener);

    setContentPane(splitpaneHorizontal);
    URL url = resource.getResource(Resource.ROBOT_TV_299x264);

    scrollvideopane.setMinimumSize(new Dimension(0, 0));
    scrollvideopane.getVerticalScrollBar().setUnitIncrement(24);
    scrollvideopane.getHorizontalScrollBar().setUnitIncrement(24);

    imageRobot.setIcon(new ImageIcon(url));
    imageRobot.setOpaque(true);
    imageRobot.setHorizontalAlignment(SwingConstants.CENTER);

    // ROBOT Area
    robotListener = new MouseAdapter() {
      @Override
      public void mousePressed(MouseEvent event) {
        if (event.isPopupTrigger()) {
          popup.show(imageRobot, event.getX(), event.getY());
        }
      }



      @Override
      public void mouseReleased(MouseEvent event) {
        if (event.isPopupTrigger()) {
          popup.show(imageRobot, event.getX(), event.getY());
        }
      }

    };
    imageRobot.addMouseListener(robotListener);
    imageRobot.setCursor(dukeCursor);

    scrollvideopane.setViewportView(imageRobot);
    // scrollvideopane.setBorder(null);
    corner = new MultilingualLabel();
    // die richtige Frabe einblenden
    corner.setBackground(UIManager.getColor("nimbusSelectionBackground"));
    corner.setOpaque(true);
    eispanel.setBackground(UIManager.getColor("nimbusSelectionBackground"));
    scrollvideopane.setCorner(ScrollPaneConstants.LOWER_RIGHT_CORNER, eispanel); // ok!

    topPanel.setViewportView(buttonpanel);

    topPanel.setBorder(null);
    topPanel.getVerticalScrollBar().setUnitIncrement(12);
    topPanel.getHorizontalScrollBar().setUnitIncrement(12);
    topPanel.setMinimumSize(new Dimension(0, 0));
    topPanel.setCorner(ScrollPaneConstants.LOWER_RIGHT_CORNER, corner); // ok!
    this.setVerticalDivider(TOP_BAR);

    splitpaneVertical.setTopComponent(topPanel);
    splitpaneVertical.setBottomComponent(scrollvideopane);
    splitpaneVertical.setOneTouchExpandable(true);
    splitpaneVertical.setContinuousLayout(true);
    splitpaneVertical.setMinimumSize(new Dimension(0, 0));

    splitpaneHorizontal.setLeftComponent(splitpaneVertical);
    splitpaneHorizontal.setRightComponent(windowManager);
    splitpaneHorizontal.setDividerLocation(SCREEN_WIDTH - WindowManagerInternalFrame.SPLITWIDTH);
    splitpaneHorizontal.setOneTouchExpandable(true);
    splitpaneHorizontal.setContinuousLayout(true);

    addComponentListener(componentAdapter);

    windowManager.setMinimumSize(new Dimension(0, 0));
    windowManager.addWMListener(new PropertyChangeListener() {
      @Override
      public void propertyChange(PropertyChangeEvent event) {
        changes.firePropertyChange(event);
      }
    });

    // SwitchButton
    con.gridx = 0;
    con.gridy = 2;
    con.gridwidth = 2;
    con.gridheight = 1;
    con.fill = GridBagConstraints.NONE;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.CENTER;
    gridbag.setConstraints(switchButton, con);
    buttonpanel.add(switchButton);
    switchButton.setHorizontalTextPosition(SwingConstants.LEFT);

    switchButton.addChangeListener(event -> {

      // alle Slots deaktivieren

      if (switchButton.isSelected()) {
        // guard verhindert Mehrfachregistrierungen, die durch addChangelistener
        // ausgelöst werden
        if (guard) return;

        Arrays.stream(userDefinedButtons).forEach(button -> {
          for (ActionListener al : button.getActionListeners()) {
            button.removeActionListener(al);
          }
        });

        guard = true;
        // helper für gespeicherten Knopf
        for (UserDefinedButton slot : userDefinedButtons) {
          slot.setBackground(Resource.JQUERY_GREEN_YELLOW);
          slot.setForeground(Color.BLACK);
          slot.addActionListener(buttomStreamEvent -> {
            assignframe = new InternalAssignFrame();
            assignframe.setTitle(streamBelegen.toString());

            if (slot.isAssigned()) {
              assignframe.setStreamName(slot.getText());
              assignframe.setStreamUrl(slot.getStreamURL());
              assignframe.setUnassignedPossible();
            }

            assignframe.setMaximizable(false);
            assignframe.setResizable(true);
            assignframe.setIconifiable(false);
            assignframe.setClosable(true);
            assignframe.setFrameIcon(new ImageIcon(resource.getResource(Resource.LIVESTREAM_25x25)));

            // TODO Dimming
            // 1. Der Blocker mit überschriebener paintComponent für echtes Dimming
            JPanel desktopBlocker = new JPanel() {
              @Override
              protected void paintComponent(Graphics g) {
                // Zeichnet die halbtransparente Fläche manuell
                g.setColor(getBackground());
                g.fillRect(0, 0, getWidth(), getHeight());
                super.paintComponent(g);
              }
            };

            // WICHTIG: setOpaque(false) sorgt dafür, dass Swing die Schichten darunter
            // rendert
            desktopBlocker.setOpaque(false);
            // Hier stellen wir die Farbe und die Stärke des Dimmens ein (z.B. 60 für
            // mittleres Grau)
            desktopBlocker.setBackground(new Color(0, 0, 0, 60));
            desktopBlocker.setBounds(0, 0, getDesktopPane().getWidth(), getDesktopPane().getHeight());

            // Alle Maus-Events abfangen
            MouseAdapter blockerAdapter = new MouseAdapter() {
              @Override
              public void mouseClicked(MouseEvent mouseEvent) {
                mouseEvent.consume();
              }



              @Override
              public void mouseEntered(MouseEvent mouseEvent) {
                mouseEvent.consume();
              }



              @Override
              public void mouseExited(MouseEvent moueseEvent) {
                moueseEvent.consume();
              }



              @Override
              public void mousePressed(MouseEvent mouseEvent) {
                mouseEvent.consume();
              }



              @Override
              public void mouseReleased(MouseEvent mouseEvent) {
                mouseEvent.consume();
              }
            };
            desktopBlocker.addMouseListener(blockerAdapter);
            desktopBlocker.addMouseMotionListener(blockerAdapter);

            // 2. Resize-Listener: Damit die Abdunkelung mitzieht
            ComponentAdapter resizeListener = new ComponentAdapter() {
              @Override
              public void componentResized(ComponentEvent e) {
                desktopBlocker.setBounds(0, 0, getDesktopPane().getWidth(), getDesktopPane().getHeight());
              }
            };
            getDesktopPane().addComponentListener(resizeListener);

            // 3. Den Blocker in den MODAL_LAYER legen
            getDesktopPane().add(desktopBlocker, JLayeredPane.MODAL_LAYER);
            getDesktopPane().setLayer(desktopBlocker, JLayeredPane.MODAL_LAYER);

            // 4. Den assignframe in den DRAG_LAYER legen (damit er ÜBER der Abdunkelung
            // liegt)
            getDesktopPane().add(assignframe, JLayeredPane.DRAG_LAYER);
            getDesktopPane().setLayer(assignframe, JLayeredPane.DRAG_LAYER);
            getDesktopPane().setPosition(assignframe, 0);

            // Positionierung des Frames
            assignframe.pack();
            Rectangle localRectangle = getBounds();
            assignframe.setLocation(
                (localRectangle.width - InternalAssignFrame.StreamDialogWidth) / 2,
                (localRectangle.height - InternalAssignFrame.StreamDialogHeight) / 4
            );

            // Cleanup beim Schließen
            assignframe.addInternalFrameListener(new InternalFrameAdapter() {
              @Override
              public void internalFrameClosed(InternalFrameEvent e) {
                assignframe.removeAllListener();
                getDesktopPane().remove(desktopBlocker);
                getDesktopPane().removeComponentListener(resizeListener);
                getDesktopPane().repaint();
              }
            });

            assignframe.addInternalAssignFrameListener(assignEvent -> {

              Livestream livestream;
              Control value = (Control) assignEvent.getNewValue();
              switch(value) {
                case UNASSIGN_STREAM:
                  livestream = listOfLivestreams.get(slot.getCode());
                  livestream.setAssigned(false);
                  livestream.setStreamName(null);
                  livestream.setStreamURL(null);
                  slot.setStreamURL(null);
                  slot.setText(nichtBelegt.toString());
                  slot.setAssigned(false);

                  break;
                case SAVE:
                  livestream = listOfLivestreams.get(slot.getCode());
                  livestream.setAssigned(assignframe.getStreamName().length() > 0 ? true : false);
                  slot.setAssigned(assignframe.getStreamName().length() > 0 ? true : false);
                  livestream.setStreamName(
                      assignframe.getStreamName().length() > 0 ? assignframe.getStreamName() : null
                  );
                  slot.setText(
                      assignframe.getStreamName().length() > 0 ? assignframe.getStreamName()
                          : nichtBelegt.toString()
                  );
                  livestream.setStreamURL(
                      assignframe.getStreamName().length() > 0 ? assignframe.getStreamUrl() : null
                  );
                  slot.setStreamURL(
                      assignframe.getStreamName().length() > 0 ? assignframe.getStreamUrl() : null
                  );
                  break;
                default:
                  break;
              }

            });

            assignframe.setVisible(true);

          });
        }
      }
      else {
        // Buttons zurücksetzen
        Arrays.stream(userDefinedButtons).forEach(button -> {
          button.setBackground(WMResource.ENABLED_BUTTON_BACKGROUND);
          button.setForeground(WMResource.ENABLED_BUTTON_FOREGROUND);
          for (ActionListener al : button.getActionListeners()) {
            button.removeActionListener(al);
          }
        });
        guard = false;

        // alle Slots aktivieren mit Programmauswahl
        for (UserDefinedButton slot : userDefinedButtons) {
          slot.addActionListener(userDefinedButtonListener);
        }

      }
    });

    fullscreen = new ImageIcon(resource.getResource(Resource.FULLSCREEN_58x52));
    buttonVollbild = new JButton(fullscreen);
    con.insets = new Insets(12, 6, bottom, 0);
    con.gridx = 0;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 2;
    con.fill = GridBagConstraints.NONE;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonVollbild, con);
    buttonpanel.add(buttonVollbild);

    fullscreenListener = new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent e) {
        changes.firePropertyChange(StreamFrame.class.getName(), Control.NULL, Control.FULLSIZE);
      }

    };
    buttonVollbild.addActionListener(fullscreenListener);

    con.insets = new Insets(12, 6, bottom, 0);
    con.gridx++;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 2;
    con.fill = GridBagConstraints.NONE;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonPower, con);
    buttonpanel.add(buttonPower);
    buttonPower.setIcon(new ImageIcon(resource.getResource(Resource.POWERBUTTON_52x52)));
    buttonPower.addActionListener(event -> {
      try {
        setClosed(true);
      }
      catch (PropertyVetoException e) {
        log.error(e.getMessage(), e.getCause());
      }
    });

    con.insets = new Insets(12, 6, 0, 0);
    con.gridx++;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonArteDeutsch, con);
    buttonpanel.add(buttonArteDeutsch);

    con.insets = new Insets(12, 6, 0, 0);
    con.gridx++;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonArteFrance, con);
    buttonpanel.add(buttonArteFrance);

    con.insets = new Insets(12, 6, 0, 0);
    con.gridx++;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonAXN, con);
    buttonpanel.add(buttonAXN);

    con.insets = new Insets(12, 6, 0, 0);
    con.gridx++;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonBBC, con);
    buttonpanel.add(buttonBBC);

    con.insets = new Insets(12, 6, 0, 0);
    con.gridx++;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonBBCOne, con);
    buttonpanel.add(buttonBBCOne);

    con.insets = new Insets(12, 6, 0, 0);
    con.gridx++;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonBloomberg, con);
    buttonpanel.add(buttonBloomberg);

    con.insets = new Insets(12, 6, 0, 0);
    con.gridx++;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonBlueTV, con);
    buttonpanel.add(buttonBlueTV);

    con.insets = new Insets(12, 6, 0, 0);
    con.gridx++;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonCanal24, con);
    buttonpanel.add(buttonCanal24);

    con.insets = new Insets(12, 6, 0, 0);
    con.gridx++;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonCGTN_EN, con);
    buttonpanel.add(buttonCGTN_EN);

    con.insets = new Insets(12, 6, 0, 0);
    con.gridx++;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonCGTN_FR, con);
    buttonpanel.add(buttonCGTN_FR);

    con.insets = new Insets(12, 6, 0, 0);
    con.gridx++;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonCGTN_ES, con);
    buttonpanel.add(buttonCGTN_ES);

    con.insets = new Insets(6, 6, bottom, 0);
    con.gridx = 2;
    con.gridy = 1;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonCNN, con);
    buttonpanel.add(buttonCNN);

    con.insets = new Insets(6, 6, bottom, 0);
    con.gridx++;
    con.gridy = 1;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonKissTV, con);
    buttonpanel.add(buttonKissTV);

    con.insets = new Insets(6, 6, bottom, 0);
    con.gridx++;
    con.gridy = 1;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonIndiaNews, con);
    buttonpanel.add(buttonIndiaNews);

    con.insets = new Insets(6, 6, bottom, 0);
    con.gridx++;
    con.gridy = 1;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonMir24, con);
    buttonpanel.add(buttonMir24);

    con.insets = new Insets(6, 6, bottom, 0);
    con.gridx++;
    con.gridy = 1;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonNTW, con);
    buttonpanel.add(buttonNTW);

    con.insets = new Insets(6, 6, bottom, 0);
    con.gridx++;
    con.gridy = 1;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonNTW_SERIAL, con);
    buttonpanel.add(buttonNTW_SERIAL);

    con.insets = new Insets(6, 6, bottom, 0);
    con.gridx++;
    con.gridy = 1;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonRakutenTV, con);
    buttonpanel.add(buttonRakutenTV);

    con.insets = new Insets(6, 6, bottom, 0);
    con.gridx++;
    con.gridy = 1;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonRTDE, con);
    buttonpanel.add(buttonRTDE);

    con.insets = new Insets(6, 6, bottom, 0);
    con.gridx++;
    con.gridy = 1;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonRedeNGT, con);
    buttonpanel.add(buttonRedeNGT);

    con.insets = new Insets(6, 6, bottom, 0);
    con.gridx++;
    con.gridy = 1;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonTeleSUR_ES, con);
    buttonpanel.add(buttonTeleSUR_ES);

    con.insets = new Insets(6, 6, bottom, 0);
    con.gridx++;
    con.gridy = 1;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(buttonZDFinfo, con);
    buttonpanel.add(buttonZDFinfo);

    con.insets = new Insets(6, 6, bottom, 0);
    con.gridx = 1;
    con.gridy = 2;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;

    for (int index = 0; index < userDefinedButtons.length; index++) {
      userDefinedButtons[index] = new UserDefinedButton();
      con.gridx++;
      gridbag.setConstraints(userDefinedButtons[index], con);
      buttonpanel.add(userDefinedButtons[index]);
    }

    // alle Slots aktivieren mit Programmauswahl
    for (UserDefinedButton slot : userDefinedButtons) {
      slot.addActionListener(userDefinedButtonListener);
    }

    con.insets = new Insets(12, 6, bottom, 0);
    con.gridx = 4;
    con.gridy = 3;
    con.gridwidth = 6;
    con.gridheight = 1;
    con.fill = GridBagConstraints.HORIZONTAL;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.anchor = GridBagConstraints.WEST;
    gridbag.setConstraints(volumePanel, con);
    buttonpanel.add(volumePanel);
    volumePanel.addVolumeListener(volumelistener);

    buttonCNN.addActionListener(event -> {
      zapping(TVSender.CNN, buttonCNN, false);
    });

    buttonArteDeutsch.addActionListener(event -> {
      zapping(TVSender.ARTE_DEUTSCH, buttonArteDeutsch);
    });
    buttonArteFrance.addActionListener(event -> {
      zapping(TVSender.ARTE_FRANKREICH, buttonArteFrance);
    });
    buttonKissTV.addActionListener(event -> {
      zapping(TVSender.ARD_SPORTSCHAU_LIVE, buttonKissTV);
    });

    buttonBloomberg.addActionListener(event -> {
      zapping(TVSender.BLOOMBERG, buttonBloomberg);
    });

    buttonBlueTV.addActionListener(event -> {
      zapping(TVSender.BLUETV, buttonBlueTV);
    });

    buttonCanal24.addActionListener(event -> {
      zapping(TVSender.CANAL_24, buttonCanal24);
    });

    buttonAXN.addActionListener(event -> {
      zapping(TVSender.AXN, buttonAXN);
    });

    buttonRakutenTV.addActionListener(event -> {
      zapping(TVSender.RAKUTEN_TV, buttonRakutenTV);
    });

    buttonZDFinfo.addActionListener(event -> {
      zapping(TVSender.ZDFinfo, buttonZDFinfo);
    });

    buttonRTDE.addActionListener(event -> {
      zapping(TVSender.RTDE, buttonRTDE);
    });

    buttonIndiaNews.addActionListener(event -> {
      zapping(TVSender.INDIA_NEWS, buttonIndiaNews);
    });

    buttonMir24.addActionListener(event -> {
      zapping(TVSender.MIR24, buttonMir24);
    });

    buttonNTW.addActionListener(event -> {
      zapping(TVSender.NTW, buttonNTW);
    });

    buttonCGTN_EN.addActionListener(event -> {
      zapping(TVSender.CGTN_EN, buttonCGTN_EN);
    });

    buttonCGTN_ES.addActionListener(event -> {
      zapping(TVSender.CGTN_ES, buttonCGTN_ES);
    });

    buttonCGTN_FR.addActionListener(event -> {
      zapping(TVSender.CGTN_FR, buttonCGTN_FR);
    });

    buttonBBC.addActionListener(event -> {
      zapping(TVSender.BBC, buttonBBC);
    });

    buttonTeleSUR_ES.addActionListener(event -> {
      zapping(TVSender.teleSUR_ES, buttonTeleSUR_ES);
    });

    buttonBBCOne.addActionListener(event -> {
      zapping(TVSender.AGUASCALIENTES, buttonBBCOne);
    });

    buttonNTW_SERIAL.addActionListener(event -> {
      zapping(TVSender.NTW_SERIAL, buttonNTW_SERIAL);
    });

    buttonRedeNGT.addActionListener(event -> {
      zapping(TVSender.REDE_NGT, buttonRedeNGT);

    });

  }



  @Override
  public void insertWM(WM value) {
    windowManager.addWindow(value);
  }



  public void registerComponentAdapter() {
    addComponentListener(componentAdapter);
  }



  public void removeAllListener() {
    for (PropertyChangeListener tmp : changes.getPropertyChangeListeners()) {
      removeIptvListener(tmp);
    }
    windowManager.removeAllListener();
    for (Component comp : buttonpanel.getComponents()) {
      if (comp instanceof JButton button) {
        try {
          button.removeActionListener(button.getActionListeners()[0]);
        }
        catch (ArrayIndexOutOfBoundsException e) {}
      }
    }
    removeComponentListener(componentAdapter);
    buttonVollbild.removeActionListener(fullscreenListener);
    volumePanel.removePropertyChangeListener(volumelistener);
    if (videosurface != null) {
      // MouseAdapter entfernen
      videosurface.removeMouseListener(videosurfaceListener);
    }
    itemLauter.removeActionListener(lauterListener);
    itemLeiser.removeActionListener(leiserListener);
    imageRobot.removeMouseListener(robotListener);

  }



  public synchronized void removeIptvListener(PropertyChangeListener listener) {
    changes.removePropertyChangeListener(listener);
  }



  @Override
  public void removeWM(WM frame) {
    windowManager.removeWM(frame);
  }



  @Override
  public void ringRing(WindowAnrufenEvent event) {
    extText = event.getNickname();
    getMultiAbnehmen().setLanguage(KEY.FRAME_ABNEHMEN, event.getCode(), extText);
    getMultiAnrufen().setLanguage(KEY.TELEFON_ANRUFEN, event.getCode(), extText);
  }



  @Override
  public void setBackgroundGradient(Color top, Color bottom) {
    windowManager.setBackgroundGradient(top, bottom);
  }



  @Override
  public void setFrameTitleId(String id) {
    record = id;
    setTitle(record);
  }



  /**
   * Die Trennleiste wird in horizontaler Richtung positioniert. Die Angabe
   * erfolgt in Prozent. 0-Prozent ist ganz links und 100-Prozent ist ganz rechts.
   *
   * @param prozent
   *                liegt im Intervall [0..1]
   */
  public void setHorizontalDivider(double prozent) {
    if (0 > prozent || prozent > 1) {
      log.warn(prozent + " - die Prozentangabe liegt nicht im Intervall [0..1]");
    }
    splitpaneHorizontal.setDividerLocation(prozent);
  }



  /**
   * Die Trennleiste wird in horizontaler Richtung positioniert. Die Angabe
   * erfolgt in Pixel. 0 ist ganz links und {@code SCREEN_WIDTH} ist ganz rechts.
   *
   *
   * @param pixel
   *              die X-Position
   */
  public void setHorizontalDivider(int pixel) {
    splitpaneHorizontal.setDividerLocation(pixel);
  }



  public void setIptvvolume(int volume) {
    volumePanel.setLoudness(volume);
  }



  @Override
  public void setLanguage(ISO639 code) {
    volumePanel.setLanguage(code);
    itemLauter.setLanguage(code);
    itemLeiser.setLanguage(code);
    menuProgramme.setLanguage(code);
    itemVollbild.setLanguage(code);
    streamBelegen.setLanguage(code);
    switchButton.setText(streamBelegen.toString());
    nichtBelegt.setLanguage(code);
    for (UserDefinedButton slot : userDefinedButtons) {
      if (!slot.isAssigned()) slot.setText(nichtBelegt.toString());
    }
    textfieldaName.setLanguage(code);

    if (assignframe != null) assignframe.setLanguage(code);
    if (assignframe != null) assignframe.setTitle(streamBelegen.toString());

    treeView(code);
  }



  /**
   * Diese Livestreams stehen in der Konfigurationsdatei {@code config.xml}.
   * 
   * @param listOfLivestreams
   *                          diese Livestreams
   */
  public void setLivestreams(Livestreams value) {

    listOfLivestreams = value.getLivestream();

    for (int index = 0; index < SLOTS; index++) {
      if (index >= listOfLivestreams.size()) {
        Livestream live = new Livestream();
        live.setAssigned(false);
        listOfLivestreams.add(live);
      }
      if (listOfLivestreams.get(index).isAssigned()) {
        userDefinedButtons[index].setText(listOfLivestreams.get(index).getStreamName());
        userDefinedButtons[index].setStreamURL(listOfLivestreams.get(index).getStreamURL());
        userDefinedButtons[index].setAssigned(true);
        userDefinedButtons[index].setCode(index);
      }
      else {
        // vorhanden
        // der richtige Streamname für den Dialog?
        userDefinedButtons[index].setAssigned(false);
        userDefinedButtons[index].setText(nichtBelegt.toString());
        userDefinedButtons[index].setCode(index);
      }
    }
  }



  /**
   * Alle TV Sender werden übergeben. Die Sender liegen im JSON-Format vor. Das
   * Format ist {@code code|programmname|url} mit den Typen
   * {@code int:String:String}. Das Attribut {@code programmname} muss gleich dem
   * Knopfnamen sein.
   *
   *
   * @param senderliste
   *                    alle Sender
   */
  public void setSenderliste(List<String> senderliste) {

    // über eine HashMap
    HashMap<String, String> name_url = new HashMap<>(senderliste.size());
    name_url.put(buttonArteDeutsch.getText(), "");
    name_url.put(buttonArteFrance.getText(), "");
    name_url.put(buttonKissTV.getText(), "");
    name_url.put(buttonRakutenTV.getText(), "");
    name_url.put(buttonCanal24.getText(), "");
    name_url.put(buttonAXN.getText(), "");
    name_url.put(buttonCNN.getText(), "");
    name_url.put(buttonZDFinfo.getText(), "");
    name_url.put(buttonBlueTV.getText(), "");
    name_url.put(buttonBloomberg.getText(), "");
    name_url.put(buttonRedeNGT.getText(), "");
    name_url.put(buttonRTDE.getText(), "");
    name_url.put(buttonIndiaNews.getText(), "");
    name_url.put(buttonMir24.getText(), "");
    name_url.put(buttonNTW.getText(), "");
    name_url.put(buttonCGTN_EN.getText(), "");
    name_url.put(buttonCGTN_ES.getText(), "");
    name_url.put(buttonCGTN_FR.getText(), "");
    name_url.put(buttonBBC.getText(), "");
    name_url.put(buttonTeleSUR_ES.getText(), "");
    name_url.put(buttonBBCOne.getText(), "");
    name_url.put(buttonNTW_SERIAL.getText(), "");
    Gson gson = gsonbuilder.create();
    senderliste.forEach(item -> {
      Sender result = gson.fromJson(item, Sender.class); // Reflection
      name_url.put(result.getProgrammname(), result.getUrl());
    });

    buttonArteDeutsch.setStreamURL(name_url.get(buttonArteDeutsch.getText()));
    buttonArteFrance.setStreamURL(name_url.get(buttonArteFrance.getText()));
    buttonKissTV.setStreamURL(name_url.get(buttonKissTV.getText()));
    buttonRakutenTV.setStreamURL(name_url.get(buttonRakutenTV.getText()));
    buttonCanal24.setStreamURL(name_url.get(buttonCanal24.getText()));
    buttonAXN.setStreamURL(name_url.get(buttonAXN.getText()));
    buttonCNN.setStreamURL(name_url.get(buttonCNN.getText()));
    buttonZDFinfo.setStreamURL(name_url.get(buttonZDFinfo.getText()));
    buttonBlueTV.setStreamURL(name_url.get(buttonBlueTV.getText()));
    buttonBloomberg.setStreamURL(name_url.get(buttonBloomberg.getText()));
    buttonRedeNGT.setStreamURL(name_url.get(buttonRedeNGT.getText()));
    buttonRTDE.setStreamURL(name_url.get(buttonRTDE.getText()));
    buttonIndiaNews.setStreamURL(name_url.get(buttonIndiaNews.getText()));
    buttonMir24.setStreamURL(name_url.get(buttonMir24.getText()));
    buttonNTW.setStreamURL(name_url.get(buttonNTW.getText()));
    buttonCGTN_EN.setStreamURL(name_url.get(buttonCGTN_EN.getText()));
    buttonCGTN_ES.setStreamURL(name_url.get(buttonCGTN_ES.getText()));
    buttonCGTN_FR.setStreamURL(name_url.get(buttonCGTN_FR.getText()));
    buttonBBC.setStreamURL(name_url.get(buttonBBC.getText()));
    buttonTeleSUR_ES.setStreamURL(name_url.get(buttonTeleSUR_ES.getText()));
    buttonBBCOne.setStreamURL(name_url.get(buttonBBCOne.getText()));
    buttonNTW_SERIAL.setStreamURL(name_url.get(buttonNTW_SERIAL.getText()));

  }



  @Override
  public void setType(Frames value) {
    log.warn("Der Typ wurde mit Frames.TV statisch vorgegeben.");
  }



  /**
   * Die Trennleiste wird in vertikaler Richtung positioniert. Die Angabe erfolgt
   * in Prozent. 0-Prozent ist ganz oben und 100-Prozent ist ganz unten.
   *
   * @param prozent
   *                liegt im Intervall [0..1]
   */
  public void setVerticalDivider(double prozent) {
    if (0 > prozent || prozent > 1) {
      log.warn(prozent + " - die Prozentangabe liegt nicht im Intervall [0..1]");
    }
    splitpaneVertical.setDividerLocation(prozent);
  }



  /**
   * Die Trennleiste wird in vertikaler Richtung positioniert. Die Angabe erfolgt
   * in Pixel. 0 ist ganz oben.
   *
   * @param pixel
   */
  public void setVerticalDivider(int pixel) {
    splitpaneVertical.setDividerLocation(pixel);
  }



  /**
   * Alle Resourcen vom VLC Player werden freigegeben.
   *
   */
  public void shutdown() {
    if (eventsAPI != null) {
      eventsAPI.removeMediaEventListener(mediaEventAdapter);
      eventsAPI.removeMediaPlayerEventListener(playerAdapter);
    }
    try {
      if (embedded != null) embedded.release();
    }
    catch (Exception | Error error) {
      log.info("alles ok, " + error.getMessage());
    }
  }



  public void unregisterComponentAdapter() {
    removeComponentListener(componentAdapter);
  }



  /**
   * Performs channel zapping when the user presses a livestream button.
   *
   * @param channel
   *                      the selected livestream
   * @param buttonChannel
   *                      the button associated with the livestream
   */
  void zapping(TVSender channel, StreamButton buttonChannel) {
    zapping(channel, buttonChannel, false);
  }



  /**
   * Performs channel zapping when the user presses a livestream button.
   *
   * @param channel
   *                      the selected livestream
   * @param buttonChannel
   *                      the button associated with the livestream
   * @param isLivestream
   *                      {@code true} if a user-defined livestream button was
   *                      pressed
   */
  void zapping(TVSender channel, StreamButton buttonChannel, boolean isLivestream) {
    if (iptvSwingWorker != null) {
      if (!iptvSwingWorker.isDone()) return;
    }

    iptvSwingWorker = new IptvSwingWorker(embedded, mediaEventAdapter, playerAdapter);
    iptvSwingWorker.addPropertyChangeListener(event -> {
      if (event.getNewValue() != SwingWorker.StateValue.DONE) return;
      // alle Buttons ausschalten
      disableLivestreamButtons();

      // zweimal derselbe schaltet sich aus
      // bei den userdefined geht es nicht so einfach, weil sie immemer auf Livestream
      // stehen

      // alter Sender = neuer Sender
      if (alterSender == channel && !isLivestream) {
        alterSender = null;
        return;
      }
      else if (isLivestream) {
        UserDefinedButton neuerChannel = (UserDefinedButton) buttonChannel;
        if (neuerChannel.equals(alterChannel)) {
          alterChannel = null;
          return;
        }
        else {
          alterChannel = neuerChannel;
        }
      }
      alterSender = channel;

      buttonChannel.setBackground(Resource.JQUERY_GREEN);
      buttonChannel.setForeground(Color.WHITE);
      buttonChannel.setSelected(true);

      connect(buttonChannel.getStreamURL());
    });
    iptvSwingWorker.execute(); // terminate MediaStream

  }

}
