/**
 *  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 com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.MediaType;
import jakarta.xml.bind.JAXBException;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.Future;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.xml.sax.SAXException;
import net.javacomm.client.config.schema.Config;
import net.javacomm.client.config.schema.Dvd;
import net.javacomm.client.config.schema.FileTransferService;
import net.javacomm.client.config.schema.ISO6391;
import net.javacomm.client.config.schema.Iptv;
import net.javacomm.client.config.schema.Land;
import net.javacomm.client.config.schema.Login;
import net.javacomm.client.config.schema.Mediaplayer;
import net.javacomm.client.config.schema.Sorte;
import net.javacomm.client.config.schema.Sprache;
import net.javacomm.client.resource.Resource;
import net.javacomm.share.Constants;



public class Environment {
  /**
   * Der Webservice zur URL läuft im {@code restful} Projekt.
   */
  public final static String URL = Constants.PROTOCOL + Constants.DOWNLOAD_DOMAIN + "/restful/jaxrs/";
  public final static String FTS_URL = URL + "remoteip";
  public final static String DOMAIN = URL + "domains";
  public final static String CONFIG_SCHEMA = "config.xsd";
  /**
   * {@code true}, Telefonnachrichten werden über die Internet-IP versendet, sonst
   * über den localhost
   */
  public final static boolean PRODUCTION_MODE = true;
  public final static String DEFAULT_WALLPAPER = "default.png";
  public final static String CONFIGFILE = "config.xml";
  private static Environment instance;
  static {
    instance = new Environment();
  }

  private GsonBuilder gsonbuilder;

  private final Logger log = LogManager.getLogger(Environment.class);

  private Path workdir;
  private Path etcdir;

  private Path snapshotdir;

  private Environment() {
    setWorkdir(Paths.get(System.getProperty("user.home")));
    snapshotdir = Paths.get(getWorkdir().toAbsolutePath().toString(), "snapshot");
    gsonbuilder = new GsonBuilder().disableHtmlEscaping();

  }



  /**
   * Alle Methoden in Environment werden über getInstance() aufgerufen.
   *
   * @return eine Instanz auf Environment.
   */
  public synchronized static Environment getInstance() {
    return instance;
  }



  /**
   * Für Webstarter muss ein Verzeichnis angelegt werden.
   *
   *
   * @return true, Verzeichnis wurde angelegt
   * @throws EnvironmentException
   * @throws JAXBException
   * @throws SAXException
   * @throws IOException
   */
  public boolean createConfigfile() throws EnvironmentException, SAXException, JAXBException, IOException {

    if (log.isDebugEnabled()) log.debug("Javacomm wurde nicht mit Webstarter gestartet");
    Path etc = Paths.get(getWorkdir().toAbsolutePath().toString(), "etc");
    try {
      Files.createDirectory(etc);
    }
    catch (FileAlreadyExistsException e) {
      // OK
    }
    catch (IOException e) {
      log.error("Das Verzeichnis [" + etc.toAbsolutePath().toString() + "] konnte nicht angelegt werden");
      throw new EnvironmentException(
          etc.toAbsolutePath().toString() + "/config.xml konnte nicht angelgt werden"
      );
    }
    createDefaultConfigfile();
    return true;
  }



  /**
   * Eine Standardkonfigurationsdatei wird erstellt, falls noch keine vorhanden
   * ist.
   *
   * @return true, wurde angelegt; false war schon vorhanden
   * @throws SAXException
   * @throws JAXBException
   * @throws IOException
   */
  public boolean createDefaultConfigfile() throws SAXException, JAXBException, IOException {
    if (existsConfigxml()) return false;
    Configfile configfile = null;
    Path dir = Paths.get(getEtcdir(), CONFIGFILE);
    configfile = new Configfile(dir);

    Config root = new Config();
    root.setVersion(Constants.VERSION);
    Login login = new Login();
    login.setEmail("");
    login.setPassword("");
    root.setLogin(login);

    FileTransferService fts = new FileTransferService();
    root.setFileTransferService(fts);

    Mediaplayer mediaplayer = new Mediaplayer();
    mediaplayer.setWindowToBackground(true);
    root.setMediaplayer(mediaplayer);

    Locale locale = Locale.getDefault();
    String iso639 = locale.getLanguage();
    ISO6391 language = ISO6391.EN;
    root.setEis(Sorte.MOKKA);
    try {
      language = ISO6391.fromValue(iso639);
    }
    catch (IllegalArgumentException e) {}
    root.setLanguage(language);

    Dvd dvd = new Dvd();
    Land land = new Land();
    land.setIsoCode6391(language);
    switch(language) {
      case DE:
        land.setValue(Sprache.GERMAN);
        root.setEis(Sorte.MOKKA);
        break;
      case EN:
        land.setValue(Sprache.ENGLISH);
        root.setEis(Sorte.MOKKA);
        break;
      case ES:
        land.setValue(Sprache.SPANISH);
        root.setEis(Sorte.MOKKA);
        break;
      case FR:
        land.setValue(Sprache.FRENCH);
        break;
      case IT:
        land.setValue(Sprache.ITALIAN);
        break;
      case PT:
        land.setValue(Sprache.PORTUGUESE);
        break;
      case RU:
        land.setValue(Sprache.RUSSIAN);
        break;
      case TR:
        land.setValue(Sprache.TURKEY);
        break;
    }

    dvd.setLand(land);
    root.setDvd(dvd);
    root.setIptv(createIptv());
    configfile.write(root);
    log.info(CONFIGFILE + " angelegt!");

    return true;
  }



  /**
   * Von Mutter wird die Domainliste angefordert.
   *
   *
   * @return eine Liste aller Domains, die verfügbar sind und beim Login angezeigt
   *         werden.
   * 
   * @param urlValue
   *                 Adresse auf den Dienst
   * 
   * @throws DomainlistException
   *
   * @deprecated asynchrone Übertragung {@link #createDomainlistAsync(String)} ist
   *             bei einer GUI zu bevorzugen
   */
  @Deprecated
  public List<String> createDomainlist(String urlValue) throws DomainlistException {
    log.info(urlValue);
    Client client = null;
    ArrayList<String> domains = new ArrayList<>();
    try {
      client = ClientBuilder.newClient();
      Gson gson = gsonbuilder.create();
      StringBuilder url = new StringBuilder();
      url.append(urlValue);
      WebTarget target = client.target(url.toString());
      String result = target.request(MediaType.APPLICATION_JSON_TYPE).get(String.class);
      Type typeOfT = new TypeToken<ArrayList<String>>() {}.getType();
      List<String> jsonList = gson.fromJson(result, typeOfT);
      client.close();
      for (String tmp : jsonList) {
        domains.add(tmp);
      }
    }
    catch (ProcessingException e) {
      throw new DomainlistException("Die Domainliste konnte nicht abgerufen werden.", e.getCause());
    }
    finally {
      if (client != null) client.close();
    }
    return domains;
  }



  /**
   * Von Mutter wird die Domainliste angefordert.
   *
   *
   * @param urlValue
   *                 Adresse auf den Dienst
   *
   * @return eine Liste aller Domains, die verfügbar sind und beim Login angezeigt
   *         werden.
   *
   *
   */
  public Future<String> createDomainlistAsync(String urlValue) {
    Client client = ClientBuilder.newClient();
    StringBuilder url = new StringBuilder();
    url.append(urlValue);
    WebTarget target = client.target(url.toString());
    Future<String> result = target.request(MediaType.APPLICATION_JSON_TYPE).async().get(String.class);
    return result;
  }



  public void createDownloadDir() {
    if (!Files.isDirectory(getDownloadDir())) {
      try {
        Files.createDirectory(getDownloadDir());
        log.info(getDownloadDir() + " ---> ok!");
      }
      catch (IOException e) {
        log.error(e.getMessage(), e.getCause());
      }
    }
  }



  public Iptv createIptv() {
    Iptv iptv = new Iptv();
    iptv.setIptvvolume(68);
    return iptv;
  }



  /**
   * Lies die Senderliste aus.
   *
   * @param urlValue
   *                 von dieser Domäne wird die Senderliste angefordert
   * @return die Senderliste
   */
  public List<String> createIptvlist(String urlValue) {
    List<String> sender = new ArrayList<>();
    Client client = null;
    try {
      client = ClientBuilder.newClient();
      WebTarget webtarget = client.target(urlValue);
      String result = webtarget.request(MediaType.APPLICATION_JSON).get(String.class);
      Type typeOfT = new TypeToken<ArrayList<String>>() {}.getType();
      Gson gson = gsonbuilder.create();
      sender = gson.fromJson(result, typeOfT);
    }
    catch (ProcessingException e) {
      log.warn(e.getMessage());
    }
    finally {
      if (client != null) client.close();
    }
    return sender;
  }



  public Future<String> createIptvlistAsync(String urlValue) {
    Client client = ClientBuilder.newClient();
    WebTarget webtarget = client.target(urlValue);
    Future<String> result = webtarget.request(MediaType.APPLICATION_JSON).async().get(String.class);
    return result;
  }



  /**
   * Das Schnappschussverzeichnis wird angelegt
   *
   * @throws EnvironmentException
   *
   */
  public void createSnapshotdir() throws EnvironmentException {
    createSnapshotdir(getSnapshotdir());
  }



  /**
   * Das Schnappschussverzeichnis wird angelegt
   *
   *
   * @param path
   *             ein absoluter Verzeichnispfad
   * @throws EnvironmentException
   */
  public void createSnapshotdir(Path path) throws EnvironmentException {
    setSnapshotdir(path);
    try {
      Files.createDirectories(getSnapshotdir());
      log.info(getSnapshotdir().toAbsolutePath().toString() + " ---> ok!");
    }
    catch (IOException e) {
      log.error(e.getMessage(), e.getCause());
      throw new EnvironmentException("snapshotdir konnte nicht angelgt werden.");
    }
  }



  public void createUploadDir() {
    if (!Files.isDirectory(getUploadDir())) {
      try {
        Files.createDirectory(getUploadDir());
      }
      catch (IOException e) {
        log.info(getUploadDir() + " ---> ok!");
        log.error(e.getMessage(), e.getCause());
      }
    }
  }



  public boolean existsConfigxml() {
    Path dir = Paths.get(getEtcdir(), CONFIGFILE);
    return Files.isRegularFile(dir);
  }



  public Path getConfigfile() {
    Path file = Paths.get(getEtcdir(), CONFIGFILE);
    return file;
  }



  public void getDomains() {
    Client client = null;
    try {
      Gson gson = gsonbuilder.create();
      StringBuilder url = new StringBuilder();
      url.append(Environment.DOMAIN);
      client = ClientBuilder.newClient();
      WebTarget target = client.target(url.toString());
      String result = target.request(MediaType.APPLICATION_JSON_TYPE).get(String.class);
      Type typeOfT = new TypeToken<ArrayList<String>>() {}.getType();
      ArrayList<String> list = gson.fromJson(result, typeOfT);
      for (String tmp : list) {
        log.info(tmp);
      }
    }
    finally {
      if (client != null) client.close();
    }
  }



  public Path getDownloadDir() {
    return Paths.get("download");
  }



  /**
   *
   * Wo liegt das Konfigurationsverzeichnis?
   *
   * @return ein absoluter Verzeichnispfad
   */
  public String getEtcdir() {
    return etcdir.toAbsolutePath().toString();
  }



  /**
   *
   * Wo liegt das Libverzeichnis?
   *
   * @return das Konfigurationsverzeichnis
   */
  public String getLibdir() {
    Path etc = Paths.get(getWorkdir().toAbsolutePath().toString(), "lib");
    return etc.toAbsolutePath().toString();
  }



  /**
   * Wo liegen die Schnappschüsse von Videodateien?
   *
   * @return ein absoluter Verzeichnispfad
   */
  public Path getSnapshotdir() {
    return snapshotdir;
  }



  public Path getUploadDir() {
    return Paths.get("upload");
  }



  public Path getWallpaperdir() {
    return Paths.get(getWorkdir().toAbsolutePath().toString(), "wallpaper");
  }



  /**
   * Die Verbindungs-URL wird erstellt. Die Verbindung findet über das wss://
   * Protokoll statt.
   * 
   * @param domain
   *               mit dieser Domäne möchte sich der Client verbinden
   * @return die Verbindungs-URL
   */
  public String getWebsocketUrl(String domain) {
    StringBuilder urlbuffer = new StringBuilder();
    urlbuffer.append(Constants.WEBSOCKET);
    urlbuffer.append(domain);
    urlbuffer.append("/javacommserver/portal");
    return urlbuffer.toString();
  }



  /**
   * Das Installationsverzeichnis von Javacomm.
   *
   *
   * @return in dieses Verzeichnis wurde Javacomm installiert
   */
  public Path getWorkdir() {
    return workdir.toAbsolutePath();
  }



  /**
   * Ist das Betriebssystem Linux?
   *
   * @return {@code true}, wenn das OS Linux ist
   */
  public boolean isLinux() {
    return System.getProperty("os.name").contains("Linux") || System.getProperty("os.name").contains("LINUX");
  }



  /**
   * Ist das Betriebssystem Mac?
   *
   * @return {@code true}, wenn das OS Mac ist
   */
  public boolean isMac() {
    return System.getProperty("os.name").contains("Mac");
  }



  /**
   * Ist das Betriebssystem Windows?
   *
   * @return {@code true}, wenn das OS Windows ist
   */
  public boolean isWindows() {
    return System.getProperty("os.name").contains("Windows");
  }



  /**
   * Setze das Konfigurationsverzeichnis.
   *
   * @param value
   *              das Konfigurationsverzeichnis
   */
  public void setEtcdir(Path value) {
    etcdir = value;
  }



  /**
   * In dem Verzeichnis liegen Schnappsch�sse von Videodateien.
   *
   * @param value
   *              ein absoluter Verzeichnispfad
   */
  public void setSnapshotdir(Path value) {
    snapshotdir = value;
  }



  public void setWorkdir(Path value) {
    workdir = value;
  }



  /**
   * Nach dem Update wird Javacomm neu gestartet.
   *
   *
   * @return die Batchdatei als absoluter Pfad
   * @throws IOException
   */
  public Path unpackAutostart() throws IOException {
    Path destination = Paths.get("");

    String AUTOSTART = null;
    if (isWindows()) {
      AUTOSTART = "autostart.cmd";
    }
    else if (isLinux()) {
      AUTOSTART = "autostart.sh";
    }
    destination = Paths.get(AUTOSTART); // entpacke in das Installationsverzeichnis
    log.info(destination.toAbsolutePath().toString());
    try(
      InputStream instream = getClass().getResourceAsStream("/net/javacomm/client/resource/" + AUTOSTART);
      FileOutputStream out = new FileOutputStream(destination.toFile());
    ) {

      byte[] all = instream.readAllBytes();
      out.write(all);
    }
    if (isLinux()) {
      Set<PosixFilePermission> filePermission = PosixFilePermissions.fromString("rwxr-xr-x");
      Files.setPosixFilePermissions(destination, filePermission);
    }
    return destination.toAbsolutePath();
  }



  /**
   * Autostartme leitet das Updateende ein und verweist auf windows2.
   *
   *
   * @return die Batchdatei als absoluter Pfad
   * @throws IOException
   */
  public Path unpackAutostartme() throws IOException {
    Path destination = Paths.get("");

    String AUTOSTART = null;
    if (isWindows()) {
      AUTOSTART = "autostartme.cmd";
    }
    else if (isLinux()) {
      AUTOSTART = "autostartme.sh";
    }
    destination = Paths.get(AUTOSTART); // entpacke in das Installationsverzeichnis
    log.info(destination.toAbsolutePath().toString());
    try(
      InputStream instream = getClass().getResourceAsStream("/net/javacomm/client/resource/" + AUTOSTART);
      FileOutputStream out = new FileOutputStream(destination.toFile());
    ) {

      byte[] all = instream.readAllBytes();
      out.write(all);
    }
    if (isLinux()) {
      Set<PosixFilePermission> filePermission = PosixFilePermissions.fromString("rwxr-xr-x");
      Files.setPosixFilePermissions(destination, filePermission);
    }
    return destination.toAbsolutePath();

  }



  public boolean unpackConfigschema() {
    boolean done = false;
    byte[] buffer = new byte[4096];
    BufferedInputStream inBuffer = null;
    BufferedOutputStream outbuffer = null;
    try {
      File file = new File(getEtcdir(), CONFIG_SCHEMA);
      FileOutputStream outfile = new FileOutputStream(file);
      outbuffer = new BufferedOutputStream(outfile, 65535);
      Class<? extends Environment> resource = getClass();
      inBuffer = new BufferedInputStream(resource.getResourceAsStream(Resource.CONFIG_SCHEMA), 65535);
      int count;
      while ((count = inBuffer.read(buffer)) != -1) {
        outbuffer.write(buffer, 0, count);
      }
      done = true;
    }
    catch (IOException e) {}
    finally {
      try {
        if (inBuffer != null) inBuffer.close();
      }
      catch (IOException e) {}
      try {
        if (outbuffer != null) outbuffer.flush();
      }
      catch (IOException e) {}
      try {
        if (outbuffer != null) outbuffer.close();
      }
      catch (IOException e) {}
    }
    return done;
  }



  /**
   * Der Mauscursor, der in der Bilschirmübertragung zu sehen ist, wird entpackt.
   * Lege den Mauscursor im {@code /etc} Verzeichnis ab.
   *
   * @return ein absoluter Dateipfad auf die entpackte Datei
   *
   */
  public Path unpackMousecursor() {

    Path output = Paths.get(getEtcdir(), Paths.get(Resource.DUKEMAUS).getFileName().toString());
    try(
      InputStream resourceAsStream = getClass().getResourceAsStream(Resource.DUKEMAUS);
      OutputStream outputStream = Files.newOutputStream(output)
    ) {

      byte[] read = resourceAsStream.readAllBytes();
      outputStream.write(read);
    }
    catch (IOException e) {
      log.error(e.getMessage(), e);
    }
    return output.toAbsolutePath();
  }



  /**
   * Die Batchdatei für ein Update wird entpackt.
   *
   *
   * @return die Batchdatei als absoluter Dateipfad
   *
   * @throws IOException
   */
  public Path unpackUpdate() throws IOException {
    Path destination = Paths.get("");
    if (isWindows()) {
      destination = Paths.get("update.cmd"); // entpacke in das Installationsverzeichnis
      log.info(destination.toAbsolutePath().toString());
      try(
        InputStream instream = getClass().getResourceAsStream("/net/javacomm/client/resource/update.cmd");
        FileOutputStream out = new FileOutputStream(destination.toFile());
      ) {

        byte[] all = instream.readAllBytes();
        out.write(all);
      }
    }
    else if (isLinux()) {
      destination = Paths.get("update.sh"); // entpacke in das Installationsverzeichnis
      log.info(destination.toAbsolutePath().toString());
      try(
        InputStream instream = getClass().getResourceAsStream("/net/javacomm/client/resource/update.sh");
        FileOutputStream out = new FileOutputStream(destination.toFile());
      ) {
        byte[] all = instream.readAllBytes();
        out.write(all);
      }
    }
    return destination.toAbsolutePath();
  }



  /**
   * Entpacke alle Wallpapers. Die Methode hat keinen Rückgabewert, weil
   * Wallpapers nicht notwendig sind. ein bestehendes etc Verzeichnis.
   *
   */
  public void unpackWallpaper() {
    Path wallpaperDir = getWallpaperdir();
    if (!Files.isDirectory(wallpaperDir)) {
      try {
        Files.createDirectories(wallpaperDir);
      }
      catch (IOException e) {
        log.error("Das Verzeichnis <" + e.getMessage() + "> konnte nicht erstellt werden.");
        return;
      }
    }
    byte[] buffer = new byte[4096];
    BufferedInputStream inBuffer = null;
    BufferedOutputStream outbuffer = null;
    try {
      Path file = Paths.get(wallpaperDir.toAbsolutePath().toString(), DEFAULT_WALLPAPER);
      outbuffer = new BufferedOutputStream(Files.newOutputStream(file), 65535);
      Class<? extends Environment> resource = getClass();
      inBuffer = new BufferedInputStream(resource.getResourceAsStream(Resource.DEFAULT_WALLPAPER), 65535);
      int count;
      while ((count = inBuffer.read(buffer)) != -1) {
        outbuffer.write(buffer, 0, count);
      }
    }
    catch (IOException e) {
      log.error(e.getMessage(), e);
    }
    finally {
      try {
        if (inBuffer != null) inBuffer.close();
      }
      catch (IOException e) {}
      try {
        if (outbuffer != null) outbuffer.flush();
      }
      catch (IOException e) {}
      try {
        if (outbuffer != null) outbuffer.close();
      }
      catch (IOException e) {}
    }
  }

}
