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

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.nexuswob.util.Util;



/*****************************************************************************/
/*                                                                           */
/*                              Class PlayerFilter                           */
/*                                                                           */
/*****************************************************************************/

public final class PlayerFilter {

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

  public final static String QUICKTIME[] = new String[] {"mov", "mp4", "3gp", "3g2", "mj2"};
  public final static String MP3[] = new String[] {"mp3"};
  public final static String AVI[] = new String[] {"avi"};
  public final static String ADTS[] = new String[] {"aac"};
  public final static String WEBM[] = new String[] {"webm"};
  public final static String MPEG[] = new String[] {"mpg", "mpeg"};
  public final static String MATROSKA[] = new String[] {"mkv"};
  public final static String M4A[] = new String[] {"m4a"};
  public final static String SONG[] = new String[] {"aac", "m4a", "mp3", "mp4"};
  public final static String MP4[] = new String[] {"mp4"};
  public final static String OGG[] = new String[] {"ogg"};
  public final static String OGV[] = new String[] {"ogv"};
  public final static String WMV[] = new String[] {"wmv"};
  public final static String WAVE[] = new String[] {"wav"};
  public final static String FLAC[] = new String[] {"flac"};
  public final static String DAV[] = new String[] {"dav"};

  public final static String ALLE[] = {"mov", "mp4", "3gp", "3g2", "mj2", "mp3", "avi", "aac", "webm", "mpg",
      "mpeg", "mkv", "m4a", "aac", "m4a", "mp3", "mp4", "ogg", "wav", "wmv", "flac", "dav"};

  private PlayerFilter() {}



  /**
   * Eine Liste von Audiodateien wird zurückgegeben.
   *
   * @param path
   *             ein absoluter Dateipfad
   * @return alle verfügbaren Dateien
   *
   * @throws FilterException
   *                              im Dateipfad kann nicht gefiltert werden
   * @throws NoAudiofileException
   *                              das Verzeichnis enthält keine Audiodateien
   * @throws NoDirectoryException
   *                              der Ablageort ist kein Verzeichnis
   *
   */
  public static List<Path> audiofiles(Path path)
      throws FilterException, NoAudiofileException, NoDirectoryException {
    if (path == null) throw new NoDirectoryException("There is no existing directory at path 'null'.");
    if (
      !Files.isDirectory(path)
    ) throw new NoDirectoryException(
        "There is no existing directory at path '" + path.toAbsolutePath().toString() + "'."
    );
    List<Path> filelist = new ArrayList<>();
    try(Stream<Path> contentdir = Files.list(path)) {
      filelist = contentdir.filter((arg1) -> {
        if (!Files.isRegularFile(arg1)) return false;
        if (PlayerFilter.isSupportedAudioformat(arg1)) return true;
        return false;
      }).collect(Collectors.toList());
    }
    catch (IOException e) {
      log.error(e.getMessage(), e);
      throw new FilterException("Im Verzeichnis kann nicht gefiltert werden");
    }
    if (filelist.size() == 0) throw new NoAudiofileException("No files found to play.");
    return filelist;
  }



  /**
   * Eine Liste gefilterter Audiodateien wird zurückgegeben.
   *
   *
   * @param path
   *                   ein absoluter Dateipfad
   * @param fileFilter
   *                   filefilter aac, m4a, mp3, mp4 ...
   *
   * @return alle verfügbaren Dateien, die {@code filefilter} entsprechen
   *
   * @throws NoAudiofileException
   *                              das Verzeichnis enthält keine Audiodateien
   * @throws FilterException
   *                              im Dateipfad kann nicht gefiltert werden
   * @throws NoDirectoryException
   *                              der Ablageort ist kein Verzeichnis
   */
  public static List<Path> audiofiles(Path path, FileNameExtensionFilter fileFilter)
      throws NoAudiofileException, FilterException, NoDirectoryException {
    if (path == null) throw new NoDirectoryException("There is no existing directory at path 'null'.");
    if (fileFilter == null)
      throw new FilterException("Im Verzeichnis kann nicht gefiltert werden, weil der Filter 'null' ist");
    if (
      !Files.isDirectory(path)
    ) throw new NoDirectoryException(
        "There is no existing directory at path '" + path.toAbsolutePath().toString() + "'."
    );
    String[] extensions = fileFilter.getExtensions();
    Filter<Path> filter = (file) -> {
      for (String extension : extensions) {
        if (file.toAbsolutePath().toString().endsWith(extension)) return true;
      }
      return false;
    };
    ArrayList<Path> filelist = new ArrayList<>();
    try(DirectoryStream<Path> streampath = Files.newDirectoryStream(path, filter)) {
      streampath.forEach((action) -> {
        filelist.add(action);
      });
    }
    catch (IOException e) {
      log.error(e.getMessage(), e.getCause());
      throw new FilterException("Im Verzeichnis kann nicht gefiltert werden");
    }
    if (filelist.size() == 0) throw new NoAudiofileException("No files found to play.");
    return filelist;
  }



  /**
   * Eine Liste von Audiodateien wird zurückgegeben.
   *
   * @param value
   *              ein absoluter Dateipfad
   * @return alle verfügbaren Dateien
   *
   * @throws IOException
   *                              im Dateipfad kann nicht gefiltert werden
   * @throws NoAudiofileException
   *                              das Verzeichnis enthält keine Audiodateien
   * @throws NoDirectoryException
   *                              der Ablageort ist kein Verzeichnis
   */
  public static List<Path> audiofiles(String value)
      throws IOException, NoAudiofileException, NoDirectoryException {
    Path path = null;
    try {
      path = PlayerFilter.toPath(value);
    }
    catch (NullPathException e) {}
    return audiofiles(path);
  }



  /**
   * Gib den DVD-Namen zurück.
   *
   * @param path
   *             ein Dateipfad
   * @return {@code null}, der Dateipfad befindet sich nicht auf einem
   *         DVD-Laufwerk
   */
  public static String getDvdName(Path path) {
    if (!isSupportedDvd(path)) return null;
    FileStore volumename = null;
    try {
      volumename = Files.getFileStore(path);
    }
    catch (IOException e) {
      log.error(e.getMessage(), e.getCause());
    }
    return volumename.name();
  }



  /**
   * Verweist das Dateinamenende auf das ADTS-Format "aac"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist ADTS
   */
  public static boolean isADTS(Path path) {
    return isHit(path, PlayerFilter.ADTS);
  }



  /**
   * Verweist das Dateinamenende auf das AVI-format "avi"?
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist AVI
   */
  public static boolean isAVI(Path path) {
    return isHit(path, PlayerFilter.AVI);
  }



  private static boolean isHit(Path path, String[] values) {
    boolean supported = false;
    for (String tmp : values) {
      supported = supported || path.toString().toLowerCase().endsWith(tmp);
    }
    return supported;
  }



  /**
   * Verweist das Dateinamenende auf das MPEG4-Audioformat "m4a"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist M4A
   */
  public static boolean isM4A(Path path) {
    return isHit(path, PlayerFilter.M4A);
  }



  /**
   * Verweist das Dateinamenende auf das VORBIS-Audioformat "ogg"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist OGG
   */
  public static boolean isOGG(Path path) {
    return isHit(path, PlayerFilter.OGG);
  }



  /**
   * Verweist das Dateinamenende auf das VORBIS-Videoformat "ogv"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist OGG
   */
  public static boolean isOGV(Path path) {
    return isHit(path, PlayerFilter.OGV);
  }



  /**
   * Verweist das Dateiende auf das Matroska-Format "mkv"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist MATROSKA
   */
  public static boolean isMATROSKA(Path path) {
    return isHit(path, PlayerFilter.MATROSKA);
  }



  /**
   * Verweist das Dateinamenende auf das MP3-Format "mp3"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist MP3
   */
  public static boolean isMP3(Path path) {
    return isHit(path, PlayerFilter.MP3);
  }



  /**
   * Verweist das Dateiende auf das MPEG-1/2-Format "mpeg"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist MPEG-1/2
   */
  public static boolean isMPEG(Path path) {
    return isHit(path, PlayerFilter.MPEG);
  }



  /**
   * Verweist das Dateiende auf das WAVE-Format "wav"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist WAVE
   */
  public static boolean isWAVE(Path path) {
    return isHit(path, PlayerFilter.WAVE);
  }



  /**
   * Verweist das Dateiende auf das FLAC-Format "flac"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist FLAC
   */
  public static boolean isFLAC(Path path) {
    return isHit(path, PlayerFilter.FLAC);
  }



  /**
   * Verweist das Dateiende auf das DAV-Format "dav"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist DAV
   */
  public static boolean isDAV(Path path) {
    return isHit(path, PlayerFilter.DAV);
  }



  /**
   * Verweist das Dateinamenende auf ein Quicktimeformat "mov", "mp4", "m4a",
   * "3gp", "3g2", "mj2"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist Quicktime
   */
  public static boolean isQuicktime(Path path) {
    return isHit(path, PlayerFilter.QUICKTIME);
  }



  /**
   *
   * Wird das Audio- oder Videoformat uneterstützt?
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} wird unterstützt
   */
  public static boolean isSupported(Path path) {
    return isSupportedAudioformat(path) || isSupportedVideoformat(path);
  }



  /**
   * Wird das Audioformat untserstützt?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} wird unterstützt, AAC, FLAC, M4A, MP3, OGG, WAV
   */
  public static boolean isSupportedAudioformat(Path path) {
    return isADTS(path) || isM4A(path) || isMP3(path) || isOGG(path) || isWAVE(path) || isFLAC(path);
  }



  /**
   * Gehört das Verzeichnis zu einem DVD-Laufwerk?
   *
   *
   * @param dir
   *            ein Verzeichnis
   * @return {@code true}, es ist ein Laufwerk, welches UDF oder CDFS unterstützt
   *
   * @throws DvdException
   *                      das Gerät ist nicht bereit
   */
  public static boolean isSupportedDvd(Path dir) {
    FileStore store = null;
    try {
      store = Files.getFileStore(dir);
    }
    catch (IOException e) {
      log.warn(e.getMessage(), e.getCause());
      throw new DvdException(e.getMessage());

    }
    String type = store.type();
    return type.equalsIgnoreCase("UDF") || type.equalsIgnoreCase("CDFS");
  }



  /**
   * Wird das Videoformat untserstützt?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} wird unterstützt
   */
  public static boolean isSupportedVideoformat(Path path) {
    return isQuicktime(path)
        || isAVI(path)
        || isWEBM(path)
        || isMPEG(path)
        || isMATROSKA(path)
        || isOGV(path)
        || isWMV(path)
        || isDAV(path)
        || isSupportedDvd(path);
  }



  /**
   * Verweist das Dauteiende auf das WEBM-Format "webm"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist ADTS
   */
  public static boolean isWEBM(Path path) {
    return isHit(path, PlayerFilter.WEBM);
  }



  /**
   * Verweist das Dateiende auf das Windows Media Video - Format "wmv"?
   *
   *
   * @param path
   *             ein vollständiger Dateiname oder eine URL
   * @return {@code true} ist MATROSKA
   */
  public static boolean isWMV(Path path) {
    return isHit(path, PlayerFilter.WMV);
  }



  /**
   * Eine Liste von Musikdateien wird übergeben. Aus dieser Liste wird ein
   * Musikstück zufällig ausgwählt und zurückgegeben. Das ausgwählte Musikstück
   * wird aus der übergebenen Liste gestrichen. Vor dem Aufruf der Methode hat
   * {@code files} n-Elemente. Nach dem Aufruf hat {@code files} (n-1)-Elemente.
   *
   * @param files
   *              eine Liste mit Musikstücken
   * @return ein Musikstück aus der übergebenen Liste
   *
   * @throws ShuffleException
   *                          die Musikliste ist leer
   */
  public static Path shuffle(List<Path> files) throws ShuffleException {
    if (files == null) throw new IllegalArgumentException("files is null");
    int count = files.size();
    if (count == 0) throw new ShuffleException("die Musikliste ist leer");
    int random = Util.RNUMBER.nextInt(count);
    return files.remove(random);
  }



  /**
   * Ein vollständiger Dateipfad mit oder ohne Dateiname wird nach {@code Path}
   * gewandelt.
   *
   *
   * @param path
   *             ein Dateipfad
   *
   * @return der übergebene Dateipfad als {@code Path}
   * @throws NullPathException
   *                           der übergebene Dateipfad ist {@code null}
   */
  public static Path toPath(String path) throws NullPathException {
    if (path == null) throw new NullPathException();
    return Paths.get(path);
  }

}
