/**
 *  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.environment;

import jakarta.xml.bind.JAXBException;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.GraphicsEnvironment;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.nexuswob.util.NoDirectoryException;
import org.nexuswob.util.Util;
import org.xml.sax.SAXException;
import net.javacomm.client.base.JChat;
import net.javacomm.client.resource.Blaubeere;
import net.javacomm.client.resource.Erdbeere;
import net.javacomm.client.resource.Joghurt;
import net.javacomm.client.resource.Mokka;
import net.javacomm.client.resource.Resource;
import net.javacomm.client.resource.Vanille;
import net.javacomm.client.resource.Zitrone;
import net.javacomm.multilingual.MultilingualException;



/**
 * GUI startet den Konverter als Swing-Applikation.
 *
 * @author llange
 *
 */
public final class GUI {

  static {
    // Hier übergeben wir Log4j2 direkt die Instanz unserer Factory.
    // Das überschreibt alle internen Suchmechanismen von Log4j2 sofort.
    ConfigurationFactory.setConfigurationFactory(new ImageLoggingFactory());
  }

//  public final static int SPLITWIDTH = 231;
  public final static Dimension FILE_CHOOSER_SIZE = new Dimension(1100, 600);

  private final static Logger log = LogManager.getLogger(GUI.class);
  private static GUI gui;
  public static String updateDir = "windows";
  public static String userproperty = "user.properties";
  public static Properties properties;
  static Process process;

  static {
    gui = new GUI();
  }

  public static Font prozentFont16;

  public static Font regularFont13;
  public static Font thinFont13;
  public static Font semiFont15;
  public static Font regularBold;
  public static Font regularFrametitle;
  public static Mokka mokka = new Mokka();
  public static Joghurt joghurt = new Joghurt();
  public static Vanille vanille = new Vanille();
  public static Blaubeere blaubeere = new Blaubeere();
  public static Erdbeere erdbeere = new Erdbeere();
  public static Zitrone zitrone = new Zitrone();

  /**
   * Die veraltete Programmversion wird entfernt. Zwischen Windows und Linux muss
   * unterschieden werden.
   *
   * @return {@code true}, die veraltete Version wurde entfernt
   */
  public static boolean deletePortable() {
    Path update = Paths.get("", "update");
    boolean done = Files.exists(update.toAbsolutePath());
    if (done) {
      // Lösche winportable-64
      try {
        Files.deleteIfExists(update);
      }
      catch (IOException e1) {
        log.error(e1.getMessage(), e1);
      }

      Path portable = null;
      if (Environment.getInstance().isLinux()) {
        portable = Paths.get("", "..", "portable-linux").toAbsolutePath().normalize();
      }
      else if (Environment.getInstance().isWindows()) {
        portable = Paths.get("", "..", "portable-win64").toAbsolutePath().normalize();
      }

      // Lösche portable
      if (!Files.exists(portable)) return done;

      try {
        Util.deleteTree(portable);
      }
      catch (NoDirectoryException | IOException e) {
        log.warn(e.getMessage(), e);
      }
    }
    return done;
  }



  /**
   * Ein Singeleton verhindert, dass unnötige Instanzen der Klasse erzeugt werden.
   *
   * @return
   */
  public static synchronized GUI getInstance() {
    return gui;
  }



  /**
   *
   * Startet den Swing-Client und richtet Nimbus als Look&Feel ein. Falls Nimbus
   * nicht zur Verfügung steht, wird das JAVA Look&Feel verwendet. Im Anschluss
   * wird {@link org.nexuswob.gui.swing.JApplication} aufgerufen.
   *
   * @param args
   *             die Kommandozeilenparameter werden nicht ausgewertet.
   */
  public static void main(String args[]) {
    try {

      Runtime.getRuntime().addShutdownHook(new Thread(() -> {
        log.info("Fahre JerseyClientFactory herunter...");
        JerseyClientFactory.shutdown();
      }));

      if (deletePortable()) {
        getInstance().createPortable();
        Environment.getInstance().unpackAutostartme();
        Path autostart = Environment.getInstance().unpackAutostart();
        if (Environment.getInstance().isWindows()) {
          Runtime.getRuntime().exec(new String[] {"cmd", "/c", "start", autostart.toString()});
        }
        else if (Environment.getInstance().isLinux()) {
          Runtime.getRuntime().exec(new String[] {"xterm", "-e", autostart.toString()});
        }
        return;
      }
      else {
        Path updateDir = Paths.get("", "..", GUI.updateDir).toAbsolutePath().normalize();
        if (Files.isDirectory(updateDir)) {
          if (Environment.getInstance().isWindows()) {
            process = Runtime.getRuntime()
                .exec(new String[] {"taskkill", "/fi", "\"windowtitle", "eq", "windows1\""});

          }
          else if (Environment.getInstance().isLinux()) {
            process = Runtime.getRuntime().exec(new String[] {"pkill", "windows1"});
          }
          try {
            process.waitFor();
          }
          catch (InterruptedException e) {
            log.info(e.getMessage(), e);
          }
          getInstance().cleanDownload(updateDir);
          // TODO Daumen hoch für eine erfolgreiche Installation

          Path propertyfile = Paths.get(GUI.userproperty).toAbsolutePath();
          try(FileInputStream instream = new FileInputStream(propertyfile.toString())) {
            properties = new Properties();
            properties.loadFromXML(instream);
            Files.deleteIfExists(propertyfile);
          }
          catch (FileNotFoundException e) {
            log.warn(e.getMessage(), e);
          }
          catch (IOException e) {
            log.error(e.getMessage(), e);
          }
        }
        else {
          // es gibt kein Update
        }
      }
    }
    catch (IOException e) {
      log.error(e.getMessage(), e);
    }

    try {
      Environment env = Environment.getInstance();
      env.setWorkdir(Paths.get(".").normalize());
      env.setEtcdir(Paths.get(env.getWorkdir().toAbsolutePath().toString(), "etc"));
      env.setSnapshotdir(Paths.get(env.getWorkdir().toAbsolutePath().toString(), "snapshot"));

      // config.xml schaffen

      log.info("etcdir=" + env.getEtcdir());
      if (env.existsConfigxml()) {
        log.info("config.xml                -----> ok!");
      }
      else {
        log.info("config.xml                -----> nok");
        env.createConfigfile();
      }

      // Domainliste aktualisieren?

      // Verezeichnis für Videoschnappschüsse erstellen
      env.createSnapshotdir();

      // Wallpaperverezeichnis schaffen, ist aber nicht notwendig
      env.unpackWallpaper();

      // Donwload und Upload Verzeichnis erstellen, ist aber nicht notwendig
      env.createDownloadDir();
      env.createUploadDir();

      if (env.unpackConfigschema()) {
        log.info(Environment.CONFIG_SCHEMA + "                -----> ok!");
      }
      else {
        log.warn(Environment.CONFIG_SCHEMA + "                -----> nok");
      }
      try {
        UIManager.setLookAndFeel(mokka.getLookAndFeel());
      }
      catch (UnsupportedLookAndFeelException e1) {
        log.error(e1.getMessage(), e1.getCause());
      }
      JChat chat = new JChat();
      SwingUtilities.invokeLater(chat);
    }
    catch (NullPointerException e) {
      log.error(e.fillInStackTrace());
      log.error(e.getMessage(), e.getCause());
      System.exit(-1);
    }
    catch (SAXException e) {
      log.fatal("Schemadatei ist in javacomm.jar nicht vorhanden oder fehlerhaft.");
      log.fatal(e.getMessage(), e.getCause());
    }
    catch (JAXBException e) {
      // Schreiben die Konfigurationsdatei hat einen Schemafehler
      log.warn(e.getMessage(), e.getCause());
    }
    catch (EnvironmentException | IOException | MultilingualException e) {
      log.fatal(e.getMessage(), e.getCause());
    }
  }

  private Font regular;

  private Font thin;

  private Font semi;

  private GUI() {
    // load fontss
    regular = registerFonts(Resource.TRUETYPE_REGULAR);
    regularFont13 = regular.deriveFont(Font.PLAIN, 13);
    regularBold = regular.deriveFont(Font.BOLD, 13);
    regularFrametitle = regular.deriveFont(Font.BOLD, 14);
    prozentFont16 = regular.deriveFont(Font.BOLD, 16);

    thin = registerFonts(Resource.TRUETYPE_THIN);
    thinFont13 = thin.deriveFont(Font.PLAIN, 13);

    semi = registerFonts(Resource.TRUETYPE_BOLD);
    semiFont15 = semi.deriveFont(Font.PLAIN, 15);

  }



  /**
   * Das Update wurde entpackt und umkopiert. Jetzt wird es aus dem
   * Downloadverzeichnis gelöscht.
   *
   * @param root
   *             das Update wurde in dieses Verzeichnis entpackt
   */
  public void cleanDownload(Path root) {
    try {
      Util.deleteTree(root);
    }
    catch (NoDirectoryException | IOException e) {
      log.info(e.getMessage(), e);
    }
  }



  /**
   * Kopiere die neue Version an den Ort der alten Version. Zwischen Windows und
   * Linux muss unterschieden werden.
   *
   */
  public void createPortable() {
    Path destination = null;
    if (Environment.getInstance().isLinux()) {
      destination = Paths.get("", "..", "portable-linux").toAbsolutePath().normalize();
    }
    else if (Environment.getInstance().isWindows()) {
      destination = Paths.get("", "..", "portable-win64").toAbsolutePath().normalize();
    }

    try {
      // Auotostart soll nach portable kopiert werden, damit ein Update gekennzeichnet
      // wird
      Path source = Paths.get("", "..", GUI.updateDir).toAbsolutePath().normalize();
      Path target = destination;
      log.info("Installationsvorgang gestartet");
      Util.copyTree(source, target);
      log.info("Installationsvorgang ---> ok!");
    }
    catch (Exception e) {
      log.error(e.getMessage(), e);
    }
  }



  /**
   * Die VideoLAN DLLs für Windows werden geladen.
   *
   *
   * @param plugins
   *                in diesem Pfad liegen alle Plugin-DLLs für Windows
   */
  public void loadDll(Path plugins) {
    try(DirectoryStream<Path> iterate = Files.newDirectoryStream(plugins, "*.dll")) {
      for (Path nextPath : iterate) {
        System.load(
            Paths.get(plugins.toAbsolutePath().toString(), nextPath.getFileName().toString()).toAbsolutePath()
                .toString()
        );
      }
    }
    catch (IOException e) {
      log.error(e.getMessage(), e);
    }

  }



  /**
   * Registriere einen TrueTypeFont.
   *
   * @param ttf
   *            der TrueTypeFont
   */
  public Font registerFonts(String ttf) {
    Font font = null;
    try(InputStream input = getClass().getResourceAsStream(ttf)) {
      font = Font.createFont(Font.TRUETYPE_FONT, input);
      GraphicsEnvironment graphicEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
      // Exception werfen, wenn der Font nicht erstellt werden konnte
      boolean done = graphicEnvironment.registerFont(font);
    }
    catch (IOException | FontFormatException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return font;
  }

}
