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

import java.util.ArrayList;
import java.util.List;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.Mixer.Info;
import javax.sound.sampled.Port;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;
import net.javacomm.transfer.Sprachguete;



public final class TelefonUtil {

  private TelefonUtil() {}



  /**
   * Ist ein Mikrofon vorhanden?
   * 
   * @return {@code true}, Mikrofon ist vorhanden
   */
  public static boolean isMicro() {
    return AudioSystem.isLineSupported(Port.Info.MICROPHONE);
  }



  /**
   * Wandle den übergebenen Klartext in ein Telefonformat um.
   * 
   * @param value
   *              ein Telefonformat als Klartext
   * @return ein Telefonformat
   * 
   * @throws TelefonformatException
   *                                unbekanntes Telefonformat
   */
  public static Telefonformat toTelefonformat(String value) throws TelefonformatException {
    for (Telefonformat telefonformat : Telefonformat.values()) {
      if (telefonformat.toString().equals(value)) return telefonformat;
    }
    throw new TelefonformatException("'" + value + "' ist kein gültiges Telefonformat");
  }



  public static Telefonformat toTelefonformat(Sprachguete value) throws TelefonformatException {
    return toTelefonformat(value.toString());
  }



  /**
   * Ein Mikrofonname wird als Klartext übergeben und als Mixer zurückgegeben.
   * 
   * @param value
   *              ein Gerätename für das Mikrofon
   * @return ein Mikrofonmixer oder {@code null}, falls keiner gefunden wurde
   * @throws MixerNotFoundException
   *                                Mixer nicht gefunden
   */
  public static Mixer.Info toMicrofonmixer(String value) throws MixerNotFoundException {

    Info[] allMixer = AudioSystem.getMixerInfo();
    for (Info tmp : allMixer) {
      if (tmp.getName().equals(value)) return tmp;
    }
    throw new MixerNotFoundException(value + ", no corresponding mixer was found");
  }



  /**
   * Ein Lautsprechername wird als Klartext übergeben und als Mixer zurückgegeben.
   * 
   * 
   * @param value
   *              ein Lautsprechername
   * @return ein Speakermixer oder {@code null}, falls keiner gefunden wurde
   * 
   * @throws MixerNotFoundException
   *                                Mixer nicht gefunden
   */
  public static Mixer.Info toSpeakermixer(String value) throws MixerNotFoundException {
    return toMicrofonmixer(value);
  }



  /**
   * 
   * Ist ein Kopfhörer vorhanden?
   * 
   * @return {@code true}, Kopfhörer ist vorhanden
   */
  public static boolean isHeadphone() {
    return AudioSystem.isLineSupported(Port.Info.HEADPHONE);
  }



  public static boolean isLineout() {
    return AudioSystem.isLineSupported(Port.Info.LINE_OUT);
  }



  public static boolean isLinein() {
    return AudioSystem.isLineSupported(Port.Info.LINE_IN);
  }



  public static boolean isSpeaker() {
    return AudioSystem.isLineSupported(Port.Info.SPEAKER);
  }



  /**
   * Das AudioFormat wird aus dem Telefonformat ermittelt.
   * 
   * @param format
   *               dieses Telefonformat
   * @return das ermittelte AudioFormat
   */
  public static AudioFormat getAudioFormat(Telefonformat format) {
    return new AudioFormat(
        format.getFrequenz(), format.getSampleSizeInBits().getSampleSizeInBits(),
        format.channels().getChannels(), format.encoding().getEncoding(), format.endian().getEndian()
    );
  }



  /**
   * Welche Ausgabegeräte gibt es für Kopfhörer oder Lautprecher?
   * 
   * @return alle verfügbaren Mixer, die mit einem Abspielgerät gekoppelt sind
   * 
   * @throws IllegalArgumentException
   *                                  {@code Telefonformat.EMPTY}
   */
  public static List<Info> speakerMixer(Telefonformat format) {
    ArrayList<Info> result = new ArrayList<>();
    DataLine.Info dataLine = new DataLine.Info(SourceDataLine.class, getAudioFormat(format));
    Info[] mixerinfo = AudioSystem.getMixerInfo();
    for (Info element : mixerinfo) {
      Mixer mixer = AudioSystem.getMixer(element);
      try {
        mixer.getLine(dataLine);
        result.add(element);
      }
      catch (IllegalArgumentException | LineUnavailableException e) {}
    }
    return result;
  }



  /**
   * Gibt es ein Mikrofon für das übergebene Telefonformat?
   * 
   * @param format
   *               ein Audioformat
   * @return alle verfügbaren Mixer, die mit einem Mikrofon gekoppelt sind
   * 
   * 
   * @throws IllegalArgumentException
   *                                  {@code Telefonformat.EMPTY}
   * 
   */
  public static List<Info> microMixer(Telefonformat format) {
    ArrayList<Info> result = new ArrayList<>();
    DataLine.Info dataLine = new DataLine.Info(
        TargetDataLine.class, getAudioFormat(format), format.getBufferSize()
    );
    Info[] mixerinfo = AudioSystem.getMixerInfo();
    for (Info element : mixerinfo) {
      Mixer mixer = AudioSystem.getMixer(element);
      try {
        mixer.getLine(dataLine);
        result.add(element);
//        if (log.isDebugEnabled()) log.debug(mixerinfo[index].getName());
      }
      catch (IllegalArgumentException | LineUnavailableException e) {}
    }
    return result;
  }



  /**
   * Eine Mikrofonleitung wird ausgewählt, um sie anschließend zu öffnen.
   * 
   * 
   * @param mixerInfo
   *                  ein Mikrofonmixer
   * @param format
   *                  ein Telefonformat
   * 
   * @return eine Mikrofonausgabeleitung aus dem Mixer
   * 
   * @throws LineUnavailableException
   *                                  die Leitung steht nicht zur Verfügung, weil
   *                                  sie nicht vorhanden ist oder eine andere
   *                                  Anwendung sie benutzt
   */
  public static TargetDataLine getTargetDataLine(Mixer.Info mixerInfo, Telefonformat format)
      throws LineUnavailableException {
    Mixer mixer = AudioSystem.getMixer(mixerInfo);
    DataLine.Info dataLine = new DataLine.Info(TargetDataLine.class, getAudioFormat(format));
    TargetDataLine line = (TargetDataLine) mixer.getLine(dataLine);
    return line;
  }



  public static SourceDataLine getSourceDataLine(Mixer.Info mixerInfo, Telefonformat format)
      throws LineUnavailableException {
    Mixer mixer = AudioSystem.getMixer(mixerInfo);
    DataLine.Info dataLine = new DataLine.Info(SourceDataLine.class, getAudioFormat(format));
    SourceDataLine line = (SourceDataLine) mixer.getLine(dataLine);
    return line;
  }

}
