/**
 *  Copyright © 2020-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.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JToolTip;
import javax.swing.ToolTipManager;
import javax.swing.border.EmptyBorder;
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.util.Util;
import net.javacomm.client.config.schema.Sorte;
import net.javacomm.client.environment.GUI;
import net.javacomm.client.gui.MultilingualServiceButton;
import net.javacomm.client.resource.Resource;
import net.javacomm.multilingual.Babelfish;
import net.javacomm.multilingual.MultilingualButton;
import net.javacomm.multilingual.MultilingualString;
import net.javacomm.multilingual.schema.ISO639;
import net.javacomm.multilingual.schema.KEY;
import net.javacomm.protocol.RecordInterface;
import net.javacomm.window.manager.Control;
import net.javacomm.window.manager.Frames;



/**
 * Das Panel enthält den Ticker, den Eingabebereich und die Knopfleiste für das
 * Versenden von Nachrichten.
 *
 *
 * @author llange
 *
 */
public class JChatPane extends JPanel implements Babelfish {

  /**
   * Eine Botschaft hat eine Nachricht und kann eine Anlage enthalten
   *
   *
   * @author llange
   *
   */
  public static class Botschaft {

    private String botschaft = "";
    private String path;
    private String filename;
    private boolean attachment = false;
    private long filesize;
    private String absoluteFilename;

    Botschaft() {}



    /**
     * Der Dateiname mit Pfad.
     *
     * @return Dateiname mit Pfad
     */
    public String getAbsoluteFilename() {
      return absoluteFilename;
    }



    /**
     * Was der Anwender eingegeben hat.
     *
     * @return ein Text
     */
    public String getBotschaft() {
      return botschaft;
    }



    /**
     * Ein Dateiname ohne Pfad.
     *
     * @return der Dateiname
     */
    public String getFilename() {
      return filename;
    }



    /**
     * Die Dateigröße.
     *
     * @return 0<=n-Bytes
     */
    public long getFilesize() {
      return filesize;
    }



    /**
     * Der vollständige Dateipfad.
     *
     * @return ein Pfad mit Dateiname
     */
    String getPath() {
      return path;
    }



    /**
     * Gibt es eine Anlage?
     *
     * @return {@code true}, eine Anlage ist vorhanden
     */
    public boolean isAttachment() {
      return attachment;
    }



    /**
     * Ein absoluter Dateiname.
     *
     * @param absoluteFilename
     *                         Dateiname mit Pfad
     */
    void setAbsoluteFilename(String absoluteFilename) {
      this.absoluteFilename = absoluteFilename;
    }



    /**
     * Eine Anlage mitteilen.
     *
     * @param attachment
     *                   {@code true}, eine Anlage wird mitgeschickt
     */
    private void setAttachment(boolean attachment) {
      this.attachment = attachment;
    }



    /**
     * Was der Anwender eingegeben hat.
     *
     * @param botschaft
     *                  ein Text
     */
    void setBotschaft(String botschaft) {
      this.botschaft = botschaft;
    }



    /**
     * Der Dateiname ohne Pfad.
     *
     * @param filename
     *                 der Dateiname
     */
    void setFilename(String filename) {
      this.filename = filename;
    }



    /**
     * Die Dateigröße.
     *
     * @param filesize
     *                 0<=n-Bytes
     */
    void setFilesize(long filesize) {
      this.filesize = filesize;
    }



    /**
     * Der Pfad auf eine Datei.
     *
     * @param path
     *             der Pfadname ist vollständig
     */
    void setPath(String path) {
      this.path = path;
    }

  }

  final static Logger log = LogManager.getLogger(JChatPane.class);
  private static final long serialVersionUID = -7456234940529148762L;
  final static int thickness = 3;
  private JInput input = new JInput();
  private JTicker ticker = new JTicker();
  private MultilingualServiceButton knopfleiste = new MultilingualServiceButton(
      KEY.BUTTON_ABSCHICKEN, KEY.STRING_ANHANG
  );
  private MultilingualButton abschicken = new MultilingualButton(KEY.BUTTON_ABSCHICKEN);
  private PropertyChangeSupport changes = new PropertyChangeSupport(this);
  private JPanel mainPanel = new JPanel(new BorderLayout());
  private EmptyBorder emptyborder = new EmptyBorder(0, thickness, 0, thickness);
  private JPanel center = new JPanel();
  private JPanel panSouth = new JPanel();
  private PropertyChangeListener buttonlistener;
  private GridBagLayout gridbag = new GridBagLayout();
  private GridBagConstraints con = new GridBagConstraints();
  private MultilingualString entferneAnlage = new MultilingualString(KEY.STRING_ENTFERNE_ANHANG);
  private JButton buttonAnlage = new JButton() {

    @Override
    public JToolTip createToolTip() {
      ToolTipManager.sharedInstance().setInitialDelay(200);
      ToolTipManager.sharedInstance().setDismissDelay(5000);
      ArrowTooltip arrow = new ArrowTooltip(Resource.JQUERY_TEXTBRAUN);
      arrow.setComponent(buttonAnlage);
      arrow.setTextAttributes(GUI.regularFont13, Color.WHITE, Resource.JQUERY_TEXTBRAUN);
      arrow.setBorder(new JToolTipBorder(7, Resource.JQUERY_ORANGE, Resource.JQUERY_TEXTBRAUN));
      return arrow;
    }
  };
  private ComponentAdapter componentAdapter;
  private Botschaft botschaft;
  private Path absoluterDateiname;

  private String raumname;

  /**
   * Gehört der zentrale Anzeigebereich zu einem Forum, Besprechungsraum,
   * Gruppenraum oder Privatraum. Der Raumtyp bestimmt die Spaltenanzeige.
   *
   *
   * @param type
   *             der Raumtyp
   */
  public JChatPane(Frames type) {
    setLayout(new BorderLayout());
    center.setLayout(new BorderLayout());
    center.setBorder(emptyborder);
    center.add(BorderLayout.CENTER, ticker);
    ticker.getHorizontalScrollBar().setUnitIncrement(13);
    ticker.setBorder(null);
    ticker.addTicketListener(event -> {

      // TODO
      // odlValue = Spiecherort, DOWNLOAD oder HositoryMESSAGE
      changes.firePropertyChange(raumname, event.getOldValue(), event.getNewValue());

//      changes.firePropertyChange(event);
    });

    mainPanel.add(BorderLayout.CENTER, center);
    mainPanel.add(BorderLayout.SOUTH, input);
    mainPanel.setBorder(null);

    input.addInputListener(event -> {
      switch((Control) event.getNewValue()) {
        case MESSAGE:
          // gesendet über die Tastatur
          sendMessage();
          break;
        case INPUT_FOCUS:
          ticker.cancelCellEditing();
          break;
        case CHATTOOLTIP:
          changes.firePropertyChange(event);
          break;
        default:
          break;
      }

    });

    add(BorderLayout.CENTER, mainPanel);
    panSouth.setLayout(gridbag);
    knopfleiste.setHorizontalGap(24);
    knopfleiste.setEnabledButton1();
    buttonlistener = new PropertyChangeListener() {
      @Override
      public void propertyChange(PropertyChangeEvent event) {
        Control rc = (Control) event.getNewValue();
        switch(rc) {
          case BUTTON_1:
            // gesendet über den Knopfdruck
            sendMessage();
            break;
          case BUTTON_2:
            if (buttonAnlage != null) {
              removeAnlage();
            }
            changes.firePropertyChange(raumname, Control.NULL, Control.ANLAGE);
            break;
          default:
            break;
        }
      }
    };
    knopfleiste.addButtonListener(buttonlistener);

    con.gridx = 0;
    con.gridy = 0;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.insets = new Insets(12, 0, 12, 0);
    con.anchor = GridBagConstraints.CENTER;
    gridbag.setConstraints(knopfleiste, con);

    abschicken.addActionListener(event -> {
      sendMessage();
    });
    gridbag.setConstraints(abschicken, con);
    setRoomtype(type);
    buttonAnlage.setIcon(new ImageIcon(getClass().getResource(Resource.PAPERCLIP_16x22)));
    buttonAnlage.setToolTipText(entferneAnlage.toString());

  }



  /**
   * Der Standardraumtyp ist Forum. Der Raumtyp bestimmt die Spaltenanzeige.
   * 
   * @param record
   *               der Raumanme
   */
  public JChatPane(String record) {
    this(Frames.FORUM);
    raumname = record;
  }



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



  void centerHeader() {
    ticker.centerHeader();
  }



  void chatTooltip(boolean chattooltip) {
    input.chatTooltip(chattooltip);
  }



  public void clearMessages() {
    ticker.clear();
  }



  /**
   * Hat die Nachricht eine Anlage?
   *
   * @return {@code true}, eine Anlage ist vorhanden
   */
  public boolean hasAttachment() {
    List<Component> components = Util.getAllComponents(panSouth);
    for (Component component : components) {
      if (component.equals(buttonAnlage)) return true;
    }
    return false;
  }



  /**
   * Alle Eingabefelder werden gesperrt. Die Hintergrundfarbe aller Komponenten
   * werden rot eingefärbt.
   * 
   * @deprecated das Fenster wird geschlossen
   */
  @Deprecated
  public void offline() {
    input.disableInput();
    panSouth.setBackground(Resource.JQUERY_RED);
    knopfleiste.setBackground(Resource.JQUERY_RED);
    knopfleiste.removeButtonListener(buttonlistener);
  }



  public void removeAllListener() {
    buttonAnlage.removeComponentListener(componentAdapter);
    for (ActionListener al : buttonAnlage.getActionListeners()) {
      buttonAnlage.removeActionListener(al);
    }
    ticker.clear();
    ticker.removeAllListener();
    for (PropertyChangeListener listener : changes.getPropertyChangeListeners()) {
      removeCenterListener(listener);
    }
    input.removeAllListener();
    knopfleiste.removeAllListener();

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

  }



  /**
   * Die Anlage wird entfernt.
   */
  void removeAnlage() {
    panSouth.remove(buttonAnlage);
    panSouth.revalidate();
  }



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



  private void sendMessage() {
    botschaft = new Botschaft();
    botschaft.setBotschaft(input.getMessage());
    botschaft.setAttachment(hasAttachment());
    botschaft.setFilename(botschaft.isAttachment() ? buttonAnlage.getText() : null);
    botschaft.setPath(botschaft.isAttachment() ? absoluterDateiname.toString() : null);
    botschaft.setAbsoluteFilename(botschaft.isAttachment() ? absoluterDateiname.toString() : null);
    long nBytes;
    try {
      nBytes = botschaft.isAttachment() ? Files.size(absoluterDateiname) : 0;
    }
    catch (IOException e) {
      nBytes = 0;
    }
    botschaft.setFilesize(nBytes);
    changes.firePropertyChange(botschaft.getBotschaft(), botschaft, Control.MESSAGE);
    input.clear();
  }



  /**
   * Der Anwender hat eine Datei als Anlage ausgewählt.
   *
   * @param absoluteFilename
   *                         ein absoluter Dateiname
   */
  void setAnlage(String absoluteFilename) {
    if (buttonAnlage != null) {
      buttonAnlage.removeComponentListener(componentAdapter);
      for (ActionListener al : buttonAnlage.getActionListeners()) {
        buttonAnlage.removeActionListener(al);
      }
    }
    // Anlage auf Anlage

    absoluterDateiname = Paths.get(absoluteFilename);
    buttonAnlage.setText(absoluterDateiname.getFileName().toString());

    componentAdapter = new ComponentAdapter() {
      @Override
      public void componentResized(ComponentEvent event) {
        // Der Ticker wird nur nach unten geschoben, wenn sich die Schriftlänge auf dem
        // Knopf ändert
        ticker.down();
      }
    };
    buttonAnlage.addComponentListener(componentAdapter);
    buttonAnlage.addActionListener(event -> {
      ticker.down();
      panSouth.remove(buttonAnlage);
      panSouth.revalidate();
    });
    con.gridx = 0;
    con.gridy = 1;
    con.gridwidth = 1;
    con.gridheight = 1;
    con.weightx = 0.0;
    con.weighty = 0.0;
    con.insets = new Insets(0, 0, 12, 0);
    con.anchor = GridBagConstraints.CENTER;
    gridbag.setConstraints(buttonAnlage, con);
    panSouth.add(buttonAnlage);
    revalidate();
  }



  public void setDownloadDir(String dir) {
    ticker.setDownloadDir(dir);
  }



  public void setFocus() {
    input.setFocus();
  }



  public void setInputBackground(int bg) {
    input.setBg(new Color(bg));
    ticker.setCorner(new Color(bg));
  }



  public void setInputForeground(int fg) {
    input.setFg(new Color(fg));
    center.setBackground(new Color(fg));
  }



  @Override
  public void setLanguage(ISO639 code) {
    ticker.setLanguage(code);
    input.setLanguage(code);
    knopfleiste.setLanguage(code);
    abschicken.setLanguage(code);
    entferneAnlage.setLanguage(code);
    buttonAnlage.setToolTipText(entferneAnlage.toString());
  }



  public void setMessage(RecordInterface data) throws MessageException {
    /* Es kommt eine Nachricht herein */
    /* Message speichern */
    ticker.dropMessage(data);
  }



  /**
   * Der Raumtyp bestimmt die Spaltenanzeige.
   *
   * @param type
   *             Forum, Besprechungsraum, Gruppenraum oder Privatraum
   */
  void setRoomtype(Frames type) {
    panSouth.removeAll();
    panSouth.validate();
    switch(type) {
      case BESPRECHNUNGSRAUM:
        panSouth.add(knopfleiste);
        break;
      case FORUM:
        gridbag.setConstraints(abschicken, con);
        panSouth.add(abschicken);
        break;
      case GRUPPENRAUM:
        gridbag.setConstraints(abschicken, con);
        panSouth.add(abschicken);
        break;
      case PAUSENRAUM:
        gridbag.setConstraints(abschicken, con);
        panSouth.add(abschicken);
        break;
      case PRIVATE_CHAT:
        panSouth.add(knopfleiste);
        break;
      default:
        break;
    }
    add(BorderLayout.SOUTH, panSouth);
    ticker.fitColumns(type);
  }



  void setTickerBackground(Sorte eis) {
    ticker.setTickerBackground(eis);
  }



  public void updateBackground(String nickname, int bg) {
    ticker.updateBackgound(nickname, bg);
  }



  public void updateForeground(String nickname, int fg) {
    ticker.updateForegound(nickname, fg);
  }

}
