/**
 *  Copyright © 2022-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.chat;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.util.Hashtable;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultCellEditor;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToolTip;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.nexuswob.gui.ArrowTooltip;
import org.nexuswob.gui.JToolTipBorder;
import org.nexuswob.gui.MediaFileChooser;
import org.nexuswob.gui.TableColumnAdapter;
import org.nexuswob.gui.TableModel3;
import org.nexuswob.util.Cell;
import org.nexuswob.util.Util.Record;
import net.javacomm.client.config.schema.Sorte;
import net.javacomm.client.environment.GUI;
import net.javacomm.client.resource.Resource;
import net.javacomm.multilingual.Babelfish;
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.Attachment;
import net.javacomm.protocol.ChatUser;
import net.javacomm.share.Constants;
import net.javacomm.window.manager.Control;
import net.javacomm.window.manager.Frames;



@SuppressWarnings("serial")
public class JTicker extends JScrollPane implements Babelfish {

  private Sorte tickerBackground;
  private JTable table = new JTable();
  private TableColumnModel columnmodel;
  private DefaultTableCellRenderer headerRenderer;
  private TableColumn columnSpitzname;
  private TableColumn columnZeit;
  private TableColumn columnNachricht;
  private TableColumn columnAnlage;
  private TableColumn columnMB;
  private TableColumn columnNumber;
  private MultilingualString speichern = new MultilingualString(KEY.BUTTON_SPEICHERN);
  private MultilingualString cancel = new MultilingualString(KEY.BUTTON_ABBRECHEN);
  private JTableFileeditor fileEditor = new JTableFileeditor();
  private int widthLabel = 319;
  private NachrichtenRenderer nachrichtenRenderer = new NachrichtenRenderer();
  private JLabel corner;
  private Hashtable<Integer, ChatUser> hashuser = new Hashtable<>();
  private JTextField textfieldNachricht = new JTextField();
  private DefaultCellEditor editor = new DefaultCellEditor(textfieldNachricht);
  private JScrollBar vertical;
  private boolean unten = true;
  private MultilingualString spitzname = new MultilingualString(KEY.LABEL_SPITZNAME);
  private MultilingualString zeit = new MultilingualString(KEY.STRING_ZEIT);
  private MultilingualString nachricht = new MultilingualString(KEY.STRING_NACHRICHT);
  private MultilingualString anlage = new MultilingualString(KEY.LABEL_DATEI);
  private PropertyChangeSupport changes = new PropertyChangeSupport(this);
  private JPopupMenu popup = new JPopupMenu();
  private MultilingualMenuItem itemCopy = new MultilingualMenuItem(KEY.STRING_ZWISCHENABLAGE);
  private MultilingualString tooltip = new MultilingualString(KEY.TOOLTIP_TICKER);
  private MultilingualString tooltipAnlage = new MultilingualString(KEY.TOOLTIP_ANLAGE);
  private Long filenumber;
  private String downloadDir;

  private class JTableFileeditor extends AbstractCellEditor implements TableCellEditor {

    private static final long serialVersionUID = 342738769519125412L;
    protected MediaFileChooser anlageChooser;
    protected String filename = "";
    protected JLabel label = new JLabel();

    /**
     * Wenn der Einstieg kein Verzeichnis ist, dann wird das
     * Satndardbenutzerverzeichnis gesetzt
     * 
     * @param currentDirectoryPath
     *                             ein Verzeichnis wird übergeben und keine Datei
     */

    public JTableFileeditor() {
      label.setOpaque(true);
    }



    @Override
    public Object getCellEditorValue() {
      return filename;
    }



    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row,
        int column) {

      try {
        filename = value.toString();
        label.setText(filename);
        label.setForeground(new Color(hashuser.get(row).getForegroundColor()));
        label.setBackground(new Color(hashuser.get(row).getBackgroundColor()));
        anlageChooser = new MediaFileChooser();
        anlageChooser.setDialogType(JFileChooser.SAVE_DIALOG);
        anlageChooser.setPreferredSize(GUI.FILE_CHOOSER_SIZE);

        anlageChooser.setDialogTitle(speichern.toString()); // ok
        anlageChooser.viewTypeDetails();

        Path directory;
        if (Files.isDirectory(Paths.get(downloadDir))) {
          directory = Paths.get(downloadDir);
        }
        else {
          directory = Paths.get(downloadDir).getParent();
        }
        anlageChooser.setCurrentDirectory(directory.toFile());
        anlageChooser.setSelectedFile(new File(filename));

        anlageChooser.sort();
        int result = anlageChooser.showDialog(JTicker.this, speichern.toString());
        if (result == JFileChooser.APPROVE_OPTION) {
          filenumber = (Long) model3.getValueAt(row, SPALTE_NUMBER);
          Speicherort speicherort = new Speicherort();
          speicherort.setLocation(anlageChooser.getSelectedFile().getAbsolutePath());
          speicherort.setDateiname(filename);
          speicherort.setNumber(filenumber);
          changes.firePropertyChange(String.valueOf(filenumber), speicherort, Control.DOWNLOAD);
        }
      }
      catch (NullPointerException e) {}
      return label;
    }
  }

  /**
   * Die SPALTE MB wird formatiert ausgegeben.
   * 
   * @author llange
   *
   */
  private class MBRenderer extends DefaultTableCellRenderer {
    private final DecimalFormat formatter = new DecimalFormat("####0.0");

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
        boolean hasFocus, int row, int column) {

      value = (Double) value == 0 ? "" : formatter.format(value);
      setHorizontalAlignment(SwingConstants.CENTER);
      setText(value.toString());
      try {
        super.setForeground(new Color(hashuser.get(row).getForegroundColor()));
        super.setBackground(new Color(hashuser.get(row).getBackgroundColor()));
      }
      catch (NullPointerException e) {}
      return this;
    }
  }

  /**
   * Eine Nachricht wird für eine Tabellenzelle formatiert.
   * 
   * 
   * @author llange
   *
   */
  class NachrichtenRenderer extends DefaultTableCellRenderer {

    private int width;

    /**
     * Der Renderer ist für die Spalte Nachrichten zuständig.
     * 
     * 
     */
    public NachrichtenRenderer() {
      this(255);
    }



    /**
     * Die Spaltenbreite muss im Bereich 100<=pixel<=999 liegen.
     * 
     * 
     * @param x
     *          width in pixel
     */
    public NachrichtenRenderer(int x) {
      if (x < 100) throw new IllegalArgumentException(String.valueOf(x) + " - pixel muss >=100 sein");
      if (x >= 1000) throw new IllegalArgumentException(String.valueOf(x) + " - pixel muss <= 999 sein");
      setOpaque(true);
      width = x;
    }



    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
        boolean hasFocus, int row, int column) {

      String neu = value.toString().replaceFirst("width:\\d{3}", "width:" + String.valueOf(width));
      setText(neu.toString());
      try {
        super.setForeground(new Color(hashuser.get(row).getForegroundColor()));
        super.setBackground(new Color(hashuser.get(row).getBackgroundColor()));
      }
      catch (NullPointerException e) {}
      return this;
    }



    /**
     * Die neue Spaltenbreite.
     * 
     * @param x
     *          Breite in Pixel
     */
    void setWidth(int x) {
      width = x;
    }

  }

  public static class Speicherort {

    private String location;
    private Long number;
    private String dateiname;

    Speicherort() {}



    /**
     * Gib den Dateinamen ohne Pfadangabe zurück.
     * 
     * @return dieser Dateiname
     */
    public String getDateiname() {
      return dateiname;
    }



    /**
     * Gib den Ablageort zurück.
     * 
     * @return vollständiger Pfadname mit Dateiname
     */
    public String getLocation() {
      return location;
    }



    /**
     * Gib den Datenbankschlüssel für die Anlage zurück.
     * 
     * @return Anlage/Index auf eine Datei
     */
    public Long getNumber() {
      return number;
    }



    /**
     * Setze den Dateinamen ohne Pfadangabe
     * 
     * @param dateiname
     *                  dieser Dateiname
     */
    void setDateiname(String dateiname) {
      this.dateiname = dateiname;
    }



    /**
     * An diesem Ablageort liegt die Datei.
     * 
     * @param location
     *                 vollständiger Pfadname mit Dateiname
     */
    void setLocation(String location) {
      this.location = location;
    }



    /**
     * Die Dateikennung in der Datenbank.
     * 
     * @param filenumber
     *                   Anlage/Index auf eine Datei
     */
    void setNumber(Long filenumber) {
      number = filenumber;
    }

  }

  final static Logger log = LogManager.getLogger(JTicker.class);
  private final static int SPALTE_SPITZNAME = 0;
  private final static int SPALTE_ZEIT = 1;
  private final static int SPALTE_NACHRICHT = 2;
  private final static int SPALTE_ANLAGE = 3;
  private final static int SPALTE_MB = 4;
  private final static int SPALTE_NUMBER = 5;
  private final static double MEGABYTE = 1024 * 1024;
  private TableModel3 model3 = new TableModel3() {

    @Override
    public boolean isCellEditable(int row, int column) {
      if (column == SPALTE_NACHRICHT) return true;
      if (column == SPALTE_ANLAGE && getValueAt(row, column).toString().length() > 0) return true;
      return false;
    }



    @Override
    public void setValueAt(Object value, int row, int column) {

    }

  };

  private JTableHeader header = new JTableHeader() {

    @Override
    public JToolTip createToolTip() {
      ToolTipManager.sharedInstance().setInitialDelay(200);
      ToolTipManager.sharedInstance().setDismissDelay(7000);
      ArrowTooltip arrow = new ArrowTooltip(Resource.JQUERY_BLAU);
      arrow.setComponent(header);
      arrow.setTextAttributes(GUI.regularFont13, Resource.JQUERY_HELLBLAU, Resource.JQUERY_BLAU);
      arrow.setBorder(new JToolTipBorder(7, Resource.JQUERY_YELLOW, Resource.JQUERY_BLAU));
      return arrow;
    }



    @Override
    public String getToolTipText(MouseEvent event) {
      int column = columnAtPoint(event.getPoint());
      if (column == SPALTE_NACHRICHT) {
        header.setToolTipText("<html>" + tooltip.toString() + "</html>");
        return super.getToolTipText();
      }
      else if (column == SPALTE_ANLAGE) {
        header.setToolTipText("<html>" + tooltipAnlage.toString() + "</html>");
        return super.getToolTipText();
      }
      return null;
    }

  };

  public JTicker() {
    table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    model3.setHeader(
        spitzname.toString(), zeit.toString(), nachricht.toString(), anlage.toString(), "MB", "Number"
    );
    table.setModel(model3);
    table.setCellSelectionEnabled(true);
    table.setDefaultRenderer(String.class, nachrichtenRenderer);
    table.setTableHeader(header);
    header.setReorderingAllowed(false);
    header.setForeground(Color.BLACK); //
    header.setPreferredSize(new Dimension(getPreferredSize().width, 32));
    header.setOpaque(true);
    headerRenderer = (DefaultTableCellRenderer) header.getDefaultRenderer();
    centerHeader();
    columnmodel = table.getColumnModel();
    header.setColumnModel(columnmodel);

    columnSpitzname = columnmodel.getColumn(SPALTE_SPITZNAME);
    columnSpitzname.setPreferredWidth(120);
    columnSpitzname.setMinWidth(0);

    columnZeit = columnmodel.getColumn(SPALTE_ZEIT);
    columnZeit.setPreferredWidth(160);
    columnZeit.setMinWidth(0);

    columnNachricht = columnmodel.getColumn(SPALTE_NACHRICHT);
    columnNachricht.setPreferredWidth(widthLabel);
    columnNachricht.setMinWidth(128);
    columnmodel.addColumnModelListener(new TableColumnAdapter() {

      @Override
      public void columnMarginChanged(ChangeEvent event) {
        widthLabel = columnNachricht.getPreferredWidth();
        nachrichtenRenderer.setWidth(widthLabel);
        updateRowHeights();
      }

    });
    columnNachricht.setCellEditor(editor);
    textfieldNachricht.addFocusListener(new FocusListener() {

      @Override
      public void focusGained(FocusEvent e) {
        String print = textfieldNachricht.getText().replaceFirst("<html><p style='width:\\d{1,3}'>", "")
            .replaceAll("<br>", "").replaceFirst("</p></html>", "");
        textfieldNachricht.setText(print);
        textfieldNachricht.selectAll();
        switch(tickerBackground) {
          case BLAUBEERE:
            textfieldNachricht.setBackground(Resource.JQUERY_HELLBLAU);
            break;
          case ERDBEERE:
            textfieldNachricht.setBackground(Resource.JQUERY_ERDBEERE);
            break;
          case JOGHURT:
            textfieldNachricht.setBackground(Color.WHITE);
            break;
          case MOKKA:
            textfieldNachricht.setBackground(Resource.JQUERY_MOKKA);
            break;
          case VANILLE:
            textfieldNachricht.setBackground(Resource.JQUERY_VANILLE);
            break;
          default:
            break;

        }
      }



      @Override
      public void focusLost(FocusEvent e) {}
    });
    textfieldNachricht.add(popup);
    popup.add(itemCopy);

    itemCopy.addActionListener(event -> {
      cancelCellEditing();
    });

    textfieldNachricht.addMouseListener(new MouseAdapter() {
      @Override
      public void mousePressed(MouseEvent event) {
        if (event.isPopupTrigger()) {
          copyToClipboard(event);
        }
      }



      @Override
      public void mouseReleased(MouseEvent event) {
        if (event.isPopupTrigger()) {
          copyToClipboard(event);
        }
      }

    });

    columnAnlage = columnmodel.getColumn(SPALTE_ANLAGE);
    columnAnlage.setPreferredWidth(190);
    columnAnlage.setMinWidth(0);
    columnAnlage.setCellEditor(fileEditor);

    columnMB = columnmodel.getColumn(SPALTE_MB);
    columnMB.setMinWidth(0);
    columnMB.setCellRenderer(new MBRenderer());

    columnNumber = columnmodel.getColumn(SPALTE_NUMBER);
    columnmodel.removeColumn(columnNumber);

    corner = new JLabel();
    corner.setBackground(UIManager.getColor("nimbusSelectionBackground"));
    corner.setOpaque(true);
    setCorner(ScrollPaneConstants.LOWER_RIGHT_CORNER, corner); // ok!

    setViewportView(table);

    vertical = getVerticalScrollBar();
    vertical.setUnitIncrement(13);
    vertical.addAdjustmentListener(event -> {
      int max = event.getAdjustable().getMaximum();
      int value = event.getAdjustable().getValue();
      int visible = event.getAdjustable().getVisibleAmount();
      unten = visible + value >= max;
      if (unten) {
        validate();
        vertical.setValue(max);
        return;
      }
    });

    table.getDefaultRenderer(Double.class);

  }



  public void addTicketListener(PropertyChangeListener l) {
    changes.addPropertyChangeListener(l);
  }



  /**
   * Die Nachrichtenmarkierung wird aufgehoben.
   */
  void cancelCellEditing() {
    editor.cancelCellEditing();
  }



  /**
   * Wenn das Farbschemma geändert wird, muss diese Methode aufgerufen werden,
   * damit die Spaltenüberschriften wieder zentriert sind.
   * 
   */
  void centerHeader() {
    headerRenderer = (DefaultTableCellRenderer) header.getDefaultRenderer();
    headerRenderer.setHorizontalAlignment(SwingConstants.CENTER);
  }



  /**
   * Speicher freigeben und alle Tickerattribute einer Nachricht löschen.
   * 
   */
  public void clear() {
    model3.removeAll();
    hashuser.clear();
  }



  /**
   * Eine Nachricht in die Zwischenablage kopieren.
   * 
   * @param event
   */
  private void copyToClipboard(MouseEvent event) {
    popup.show(textfieldNachricht, event.getX(), event.getY());
    Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    StringSelection stringSelection = new StringSelection(textfieldNachricht.getText());
    clipboard.setContents(stringSelection, null);
  }



  void down() {
    validate();
    revalidate();
    vertical.setValue(vertical.getMaximum());
  }



  /**
   * Eine neue Nachricht ist eingetroffen.
   * 
   * @param chatmessage
   *                    eine Nachricht
   * @throws MessageException
   */
  public void dropMessage(net.javacomm.protocol.RecordInterface chatmessage) throws MessageException {
    if (model3.getRowCount() >= Constants.HISTORY_MESSAGES * 2) {
      // Wenn zu viele Messages im Chat sind, dann wird eine HISTORY neu geladen
      changes.firePropertyChange(JTicker.class.getName(), Control.NULL, Control.HISTORYMESSAGE);
      return;
    }

    Attachment attachment = null;
    Message msg;
    if (chatmessage instanceof Attachment) {
      attachment = (Attachment) chatmessage;
      msg = new MessageImpl(attachment);
    }
    else {
      msg = new MessageImpl(chatmessage);
    }

    hashuser.put(model3.getRowCount(), chatmessage.getChatUser());

    Record record = new Record();

    Cell<String> nickname = new Cell<>();
    nickname.setValue(msg.getNickname());
    record.add(nickname);

    Cell<String> zeit = new Cell<>();
    zeit.setValue(msg.getMddsDateTime(chatmessage.getDatetime()));
    record.add(zeit);

    Cell<String> nachricht = new Cell<>();
    nachricht.setValue(
        "<html><p style='width:" + String.valueOf(widthLabel)
            + "'>"
            + msg.getMessage().replaceAll(Message.EOT, "<br>")
            + "</p></html>"
    );
    record.add(nachricht);

    Cell<String> anlage = new Cell<>();
    anlage.setValue(msg.getFilename() == null ? "" : msg.getFilename());
    record.add(anlage);

    Cell<Double> cellmb = new Cell<>();
    cellmb.setValue(msg.getFilesize() == null ? 0 : msg.getFilesize() / MEGABYTE);
    record.add(cellmb);

    Cell<Long> number = new Cell<>();
    number.setValue(msg.getFilenumber() == null ? -1 : msg.getFilenumber());
    record.add(number);

    nachrichtenRenderer.setBackground(msg.getBackgroundColor());
    nachrichtenRenderer.setForeground(msg.getForegroundColor());

    // die Farbe vom USer

    model3.addRow(record);
    updateRowHeights();
    if (unten) {
      down();
    }
  }



  /**
   * Für jeden Raumtyp werden die passenden Spalten angezeigt.
   * 
   * @param type
   */
  void fitColumns(Frames type) {
    switch(type) {
      case BESPRECHNUNGSRAUM:
        columnmodel.removeColumn(columnAnlage);
        columnmodel.removeColumn(columnMB);
        columnmodel.addColumn(columnAnlage);
        columnmodel.addColumn(columnMB);
        break;
      case FORUM:
        columnmodel.removeColumn(columnAnlage);
        columnmodel.removeColumn(columnMB);
        break;
      case GRUPPENRAUM:
        columnmodel.removeColumn(columnAnlage);
        columnmodel.removeColumn(columnMB);
        break;
      case PAUSENRAUM:
        columnmodel.removeColumn(columnAnlage);
        columnmodel.removeColumn(columnMB);
        break;
      case PRIVATE_CHAT:
        columnmodel.removeColumn(columnAnlage);
        columnmodel.removeColumn(columnMB);
        columnmodel.addColumn(columnAnlage);
        columnmodel.addColumn(columnMB);
        break;
      default:
        throw new RuntimeException("Der Raumtyp ist unbekannt. - " + type.toString());
    }
  }



  /**
   * Diese Datei soll heruntergeladen werden.
   * 
   * @return ein Index auf eine Datei
   */
  Long getFilenumber() {
    return filenumber;
  }



  public void removeAllListener() {
    for (PropertyChangeListener l : changes.getPropertyChangeListeners()) {
      removeTicketListener(l);
    }
    for (AdjustmentListener l : vertical.getAdjustmentListeners()) {
      vertical.removeAdjustmentListener(l);
    }

    for (ActionListener al : itemCopy.getActionListeners()) {
      itemCopy.removeActionListener(al);
    }

  }



  public void removeTicketListener(PropertyChangeListener l) {
    changes.removePropertyChangeListener(l);
  }



  public void setCorner(Color color) {
    corner.setBackground(color);
    header.setBackground(color); //
    getViewport().setBackground(color);
  }



  public void setDownloadDir(String dir) {
    downloadDir = dir;
  }



  @Override
  public void setLanguage(ISO639 code) {
    itemCopy.setLanguage(code);

    spitzname.setLanguage(code);
    zeit.setLanguage(code);
    nachricht.setLanguage(code);
    anlage.setLanguage(code);

    columnSpitzname.setHeaderValue(spitzname.toString());
    columnZeit.setHeaderValue(zeit.toString());
    columnNachricht.setHeaderValue(nachricht.toString());
    columnAnlage.setHeaderValue(anlage.toString());

    tooltip.setLanguage(code);
    header.setToolTipText("<html>" + tooltip.toString() + "</html>");
    speichern.setLanguage(code);
    cancel.setLanguage(code);

    tooltipAnlage.setLanguage(code);
  }



  void setTickerBackground(Sorte eis) {
    tickerBackground = eis;
  }



  /**
   * Die Zeilenhöhe für alle Reihen wird berechnet. Die Standardhöhe ist 26.
   * 
   */
  private void updateRowHeights() {
    for (int row = 0; row < table.getRowCount(); row++) {
//      int rowHeight = table.getRowHeight(row);
      int rowHeight = 26;
      for (int column = 0; column < table.getColumnCount(); column++) {
        Component comp = table.prepareRenderer(table.getCellRenderer(row, column), row, column);
        rowHeight = Math.max(rowHeight, comp.getPreferredSize().height);
      }

      table.setRowHeight(row, rowHeight);
    }
  }

}
