/**
 * The MIT License
 * Copyright © 2020 Luis Andrés Lange <http://javacomm.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.nexuswob.util;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
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.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;



public final class Util {

  /**
   * AES Schlüsselgröße ist 128, 192 oder 256.
   */
  public enum KeySize {
    size128(128),
    size192(192),
    size256(256);

    private int keysize;

    private KeySize(int keysize) {
      this.keysize = keysize;
    }



    public int intKeysize() {
      return keysize;
    }

  }

  public enum SortType {
    ALPHA_NUMERIC,
    NUMERIC,
    DATE,
    DECIMAL
  }

  public final static Pattern ROOMNAME_PATTERN = Pattern.compile(
      "[a-zA-Z0-9ÄäÖöÜüßÉéÍíÑñÓóÚúÇçÁáËëÀàÊêÌìØøÆæÅåÕõÏï_][a-zA-Z0-9ÄäÖöÜüßÉéÍíÑñÓóÚúÇçÁáËëÀàÊêÌìØøÆæÅåÕõÏï _.]+"
  );

//  public final static Pattern PATTERN_EMAIL = Pattern.compile("[\\w-&&[^.]][\\w.-]{0,62}@[\\w.-]{1,255}");

  public final static Pattern PATTERN_EMAIL = Pattern.compile("[\\w-&&[^.]]+([.]*[\\w-]+)*@[\\w.-]{1,255}");

  public final static Pattern PATTERN_EMAIL2 = Pattern
      .compile("[\\w-&&[^.]]+([.]*[\\w-]+){0,1}@[\\w.-]{1,255}");

//  public final static Pattern PATTERN_EMAIL = Pattern.compile("[\\w]+@[\\w.-]{1,255}");

  // public final static Pattern PATTERN_EMAIL =
  // Pattern.compile("[^.][\\w-]*@[\\w.-]+");
  public static Random RNUMBER = new Random(System.currentTimeMillis());

  private Util() {}



  /**
   * Steht die Datei auf der Verbotsliste? Die Verbotsliste enthält gesperrte
   * Dateiendungen.
   *
   *
   * @param filename
   *                  eine Dateiname
   * @param blacklist
   *                  die Schwarze Liste enthält Dateiendungen, die verboten sind
   *
   * @return {@code true}, die Datei steht auf der Sperrliste
   */
  public static boolean accessDenied(String filename, List<String> blacklist) {
//    final String[] blacklist = {"cer", "crt", "pfx", "p12", "pdf", "doc", "odt", "zip", "ods", "xls", "xlsx", "txt"};

    for (String tmp : blacklist) {
      if (filename.toLowerCase().endsWith(tmp)) return true;
    }

    return false;
  }



  /**
   * Generiere einen 8-stelligen String aus Zahlen und Kleinbuchstaben.
   *
   * @return Zufallsstring
   */
  public static String getRandomID() {
    char userID[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
        's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
    int value;
    StringBuffer buffer = new StringBuffer();

    /* 8-stellige UserID erzeugen */

    buffer.setLength(0);
    for (int x = 0; x < 8; x++) {
      value = RNUMBER.nextInt(36);
      buffer.append(userID[value]);
    }
    return buffer.toString();
  }



  /**
   *
   * Quicksort wird eine Tabelle übergeben. Jeder {@code Record} ist eine von
   * n-Zeilen. Die Spalten sind in {@code Record} definiert. Die erste Spalte hat
   * den {@code columnIndex 0}.
   *
   *
   *
   * @param adapter
   *                    eine Tabelle mit n-Zeilen und m-Spalten
   * @param type
   *                    ist die zu sortierende Spalte numerisch oder
   *                    alphanumerisch
   * @param columnIndex
   *                    über diese Spalte wird sortiert
   *
   * @throws ColumnException
   *                         die Tabelle kann nicht sortiert werden
   */
  public static void quicksort(Adapter adapter, SortType type, int columnIndex) {
    List<Record> table = adapter.getRecords();
    if (table == null) throw new IllegalArgumentException("table is null");
    if (columnIndex < 0) throw new IllegalArgumentException("columnIndex is " + String.valueOf(columnIndex));

    for (int index = 0; index < table.size(); index++) {
      if (table.get(index).countColumns() <= columnIndex) {
        throw new ColumnException(
            "Der Record <" + String.valueOf(index) + "> ist zu kurz - Zelleneinträge fehlen"
        );
      }
    }
    int count = table.size();
    int left = 0;
    int right = count - 1;
    if (left >= right) return;

    switch(type) {
      case NUMERIC:
        quicksortVectorNumeric(table, left, right, columnIndex);
        break;
      case ALPHA_NUMERIC:
        quicksortVectorAlpha(table, left, right, columnIndex);
        break;
      case DATE:
        quicksortVectorDate(table, left, right, columnIndex);
        break;
      case DECIMAL:
        quicksortVectorDecimal(table, left, right, columnIndex);
        break;
    }

  }



  private static void quicksortVectorDecimal(List<Record> list, int left, int right, int keyIndex) {
    int i, j;
    Record v, t;

    if (right <= left) return;
    v = list.get(right);
    i = left - 1;
    j = right;

    do {
      while (
        Double.parseDouble(list.get(++i).getCellAt(keyIndex).getValue().toString()) < Double
            .parseDouble(v.getCellAt(keyIndex).getValue().toString())
      ) {
      }
      try {
        while (
          Double.parseDouble(list.get(--j).getCellAt(keyIndex).getValue().toString()) > Double
              .parseDouble(v.getCellAt(keyIndex).getValue().toString())
        ) {
        }
      }
      catch (IndexOutOfBoundsException e) {
        j++;
      }

      t = list.get(i);
      list.set(i, list.get(j));
      list.set(j, t);

    }
    while (j > i);

    list.set(j, list.get(i));
    list.set(i, list.get(right));
    list.set(right, t);

    quicksortVectorDecimal(list, left, i - 1, keyIndex);
    quicksortVectorDecimal(list, i + 1, right, keyIndex);
  }



  private static void quicksortVectorDate(List<Record> list, int left, int right, int keyIndex) {
    int i, j;
    Record v, t;

    if (right <= left) return;
    v = list.get(right);
    i = left - 1;
    j = right;

    do {
      while (
        ((Date) list.get(++i).getCellAt(keyIndex).getValue()).before((Date) v.getCellAt(keyIndex).getValue())
      ) {
      }
      try {
        while (
          ((Date) list.get(--j).getCellAt(keyIndex).getValue()).after((Date) v.getCellAt(keyIndex).getValue())
        ) {
        }
      }
      catch (IndexOutOfBoundsException e) {
        j++;
      }

      t = list.get(i);
      list.set(i, list.get(j));
      list.set(j, t);

    }
    while (j > i);

    list.set(j, list.get(i));
    list.set(i, list.get(right));
    list.set(right, t);

    quicksortVectorDate(list, left, i - 1, keyIndex);
    quicksortVectorDate(list, i + 1, right, keyIndex);
  }



  private static void quicksortVectorNumeric(List<Record> list, int left, int right, int keyIndex) {
    int i, j;
    Record v, t;

    if (right <= left) return;
    v = list.get(right);
    i = left - 1;
    j = right;

    do {
      while (
        Long.parseLong(list.get(++i).getCellAt(keyIndex).getValue().toString()) < Long
            .parseLong(v.getCellAt(keyIndex).getValue().toString())
      ) {
      }
      try {
        while (
          Long.parseLong(list.get(--j).getCellAt(keyIndex).getValue().toString()) > Long
              .parseLong(v.getCellAt(keyIndex).getValue().toString())
        ) {
        }
      }
      catch (IndexOutOfBoundsException e) {
        j++;
      }

      t = list.get(i);
      list.set(i, list.get(j));
      list.set(j, t);

    }
    while (j > i);

    list.set(j, list.get(i));
    list.set(i, list.get(right));
    list.set(right, t);

    quicksortVectorNumeric(list, left, i - 1, keyIndex);
    quicksortVectorNumeric(list, i + 1, right, keyIndex);
  }



  private static void quicksortVectorAlpha(List<Record> list, int left, int right, int keyIndex) {

    int i, j; // Indices
    Record v, t; // Hilfsvariable

    if (right <= left) return;

    v = list.get(right);

    i = left - 1;
    j = right;

    do {
      while (
        list.get(++i).getCellAt(keyIndex).getValue().toString()
            .compareTo(v.getCellAt(keyIndex).getValue().toString()) < 0
      ) {
      }
      try {
        while (
          list.get(--j).getCellAt(keyIndex).getValue().toString()
              .compareTo(v.getCellAt(keyIndex).getValue().toString()) > 0
        ) {
        }
      }
      catch (IndexOutOfBoundsException e) {
        j++;
      }

      t = list.get(i);
      list.set(i, list.get(j));
      list.set(j, t);

    }
    while (j > i);

    list.set(j, list.get(i));
    list.set(i, list.get(right));
    list.set(right, t);

    quicksortVectorAlpha(list, left, i - 1, keyIndex);
    quicksortVectorAlpha(list, i + 1, right, keyIndex);

  }



  /**
   * Ein Array wird sortiert zurückgegeben.
   *
   * @param <T>
   *            String, Object, Boolean, Integer, Long, Short, Byte, Date, Double,
   *            Float
   * @param t
   *            ein Array beliebiger Länge
   */
  public static <T> void quicksort(T[] t) {
    if (t == null) throw new IllegalArgumentException("array is null");
    boolean ok_alpha = t.getClass().getSimpleName().equals("String[]")
        || t.getClass().getSimpleName().equals("Object[]")
        || t.getClass().getSimpleName().equals("Boolean[]");
    boolean ok_numeric = t.getClass().getSimpleName().equals("Integer[]")
        || t.getClass().getSimpleName().equals("Long[]")
        || t.getClass().getSimpleName().equals("Short[]")
        || t.getClass().getSimpleName().equals("Byte[]");
    boolean ok_date = t.getClass().getSimpleName().equals("Date[]");
    boolean ok_decimal = t.getClass().getSimpleName().equals("Double[]")
        || t.getClass().getSimpleName().equals("Float[]");

    if (!ok_alpha && !ok_numeric && !ok_date && !ok_decimal)
      throw new QuicksortException("Der Datentyp ist ungültig - " + t.getClass().getSimpleName());
    int count = t.length;
    int left = 0;
    int right = count - 1;
    if (left >= right) return;
    if (ok_alpha) {
      quicksortAlpha(left, right, t);
    }
    else if (ok_numeric) {
      quicksortNumeric(left, right, t);
    }
    else if (ok_date) {
      quicksortDate(left, right, (Date[]) t);
    }
    else if (ok_decimal) {
      quicksortDecimal(left, right, t);
    }
  }



  private static <T> void quicksortAlpha(int left, int right, T[] array) {

    int i, j; // Indices
    T v, t; // Hilfsvariable

    if (right <= left) return;

    v = array[right];
    i = left - 1;
    j = right;

    do {
      while (array[++i].toString().compareTo(v.toString()) < 0) {
      }
      try {
        while (array[--j].toString().compareTo(v.toString()) > 0) {
        }
      }
      catch (IndexOutOfBoundsException e) {
        j++;
      }

      t = array[i];
      array[i] = array[j];
      array[j] = t;

    }
    while (j > i);

    array[j] = array[i];
    array[i] = array[right];
    array[right] = t;

    quicksortAlpha(left, i - 1, array);
    quicksortAlpha(i + 1, right, array);

  }



  private static <T extends Date> void quicksortDate(int left, int right, T[] array) {
    int i, j; // Indices
    T v, t; // Hilfsvariable

    if (right <= left) return;

    v = array[right];
    i = left - 1;
    j = right;

    do {
      while (array[++i].before(v)) {
      }
      try {
        while (array[--j].after(v)) {
        }
      }
      catch (IndexOutOfBoundsException e) {
        j++;
      }

      t = array[i];
      array[i] = array[j];
      array[j] = t;

    }
    while (j > i);

    array[j] = array[i];
    array[i] = array[right];
    array[right] = t;

    quicksortDate(left, i - 1, array);
    quicksortDate(i + 1, right, array);

  }



  private static <T> void quicksortDecimal(int left, int right, T[] array) {

    int i, j; // Indices
    T v, t; // Hilfsvariable

    if (right <= left) return;

    v = array[right];
    i = left - 1;
    j = right;

    do {
      while (Double.parseDouble(array[++i].toString()) < Double.parseDouble(v.toString())) {
      }
      try {
        while (Double.parseDouble(array[--j].toString()) > Double.parseDouble(v.toString())) {
        }
      }
      catch (IndexOutOfBoundsException e) {
        j++;
      }

      t = array[i];
      array[i] = array[j];
      array[j] = t;

    }
    while (j > i);

    array[j] = array[i];
    array[i] = array[right];
    array[right] = t;

    quicksortDecimal(left, i - 1, array);
    quicksortDecimal(i + 1, right, array);

  }



  private static <T> void quicksortNumeric(int left, int right, T[] array) {

    int i, j; // Indices
    T v, t; // Hilfsvariable

    if (right <= left) return;

    v = array[right];
    i = left - 1;
    j = right;

    do {
      while (Long.parseLong(array[++i].toString()) < Long.parseLong(v.toString())) {
      }
      try {
        while (Long.parseLong(array[--j].toString()) > Long.parseLong(v.toString())) {
        }
      }
      catch (IndexOutOfBoundsException e) {
        j++;
      }

      t = array[i];
      array[i] = array[j];
      array[j] = t;

    }
    while (j > i);

    array[j] = array[i];
    array[i] = array[right];
    array[right] = t;

    quicksortNumeric(left, i - 1, array);
    quicksortNumeric(i + 1, right, array);

  }

  /**
   *
   * Record ist ein Datensatz mit n-Zellen. Jede Zelle ist eine Spalte.
   *
   * @author llange
   *
   */
  public static class Record {

    private List<Cell<?>> alleZellen; // in diesem Record

    public Record() {
      alleZellen = new ArrayList<>();
    }



    /**
     * Eine weitere Zelle wurde in den Record eingefügt. Die Spaltenanzahl ist um 1
     * gewachsen.
     *
     * @param value
     *              ein Zellenwert
     */
    public void add(Cell<?> value) {
      alleZellen.add(value);
    }



    /**
     * Lies den Wert aus der n-ten Spalte aus.
     *
     * @param column
     *               eine Spalte
     * @return ein Zellenwert
     */
    public Cell<?> getCellAt(int column) {
      return alleZellen.get(column);
    }



    /**
     * Wie viele Spalten hat der Record?
     *
     *
     * @return die Anzahl der Spalten
     */
    public int countColumns() {
      return alleZellen.size();
    }



    @Override
    public int hashCode() {
      return Objects.hash(alleZellen);
    }



    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (!(obj instanceof Record)) {
        return false;
      }
      Record other = (Record) obj;
      return Objects.equals(alleZellen, other.alleZellen);
    }

  }

  /**
   * Finde alle Componenten in einem Container. Eine Componente kann ein Container
   * sein.
   *
   * @param c
   *          ein Container
   * @return alle Componenten
   *
   * @author Richard Bair
   */
  public static List<Component> getAllComponents(final Container c) {
    Component[] comps = c.getComponents();
    List<Component> compList = new ArrayList<>();
    for (Component comp : comps) {
      compList.add(comp);
      if (comp instanceof Container) {
        compList.addAll(getAllComponents((Container) comp));
      }
    }
    return compList;
  }



  /**
   * Eine Zipdatei wird entpackt. Das Zielverzeichnis wird angelegt, falls es
   * nicht existiert.
   *
   * @param sourceZipfile
   *                       eine Zipdatei {@code (*.zip) }
   * @param destinationDir
   *                       in dieses Verzeichnis wird entpackt
   *
   * @throws UnzipException
   *                        die Datei kann nicht entpackt werden
   */
  public static void unzip(Path sourceZipfile, Path destinationDir) throws UnzipException {
    if (sourceZipfile == null) throw new IllegalArgumentException("sourceZipfile is null");
    if (destinationDir == null) throw new IllegalArgumentException("destinationDir is null");
    try(ZipFile zipfile = new ZipFile(sourceZipfile.toFile())) {
      Files.createDirectories(destinationDir);
      ArrayList<? extends ZipEntry> entries = Collections.list(zipfile.entries());
      final int len = 8192;
      int read;
      byte[] buffer = new byte[len];
      for (ZipEntry zipentry : entries) {
        if (zipentry.isDirectory()) {
          Path subdir = Paths.get(destinationDir.toAbsolutePath().toString(), zipentry.getName());
          Files.createDirectories(subdir);
        }
        else {
          // kein Verzeichnis
          Path file = Paths.get(destinationDir.toAbsolutePath().toString(), zipentry.getName());

          try(
            InputStream inputstream = zipfile.getInputStream(zipentry);
            BufferedInputStream inbuffer = new BufferedInputStream(inputstream, len);
            BufferedOutputStream outbuffer = new BufferedOutputStream(Files.newOutputStream(file), len);
          ) {

            while ((read = inbuffer.read(buffer)) > 0) {
              outbuffer.write(buffer, 0, read);
            }
          }

        }
      }
    }
    catch (IOException e) {
      throw new UnzipException(e);
    }
  }



  /**
   * Die Uhrzeit wird um 2 Stunden addiert und auf 30 Minuten nach oben
   * aufgerundet. Aufgerundet meint von 2:00 auf 4:00, von 2:01 auf 4:30, von 2:30
   * auf 4:30, von 2:31 auf 5:00. Am Ende stehen die Minuten immer auf hour:00
   * oder hour:30.
   *
   * @param uhrzeit
   *                die aktuelle Uhrzeit ohne Zeitzone
   * @return Uhrzeit + 2 Stunden oder Uhrzeit + 2,5 Stunden
   */
  public static LocalDateTime aQueHora(LocalDateTime uhrzeit) {

    if (uhrzeit.getMinute() == 0) {
      return uhrzeit.plusHours(2);
    }
    uhrzeit = uhrzeit.plusHours(3);
    if (uhrzeit.getMinute() <= 30) {
      return uhrzeit.truncatedTo(ChronoUnit.HOURS).minusMinutes(30);
    }
    return uhrzeit.truncatedTo(ChronoUnit.HOURS);
  }



  /**
   *
   * Die Zeit wird auf volle 30 Minuten abgerundet zurückgegeben. Das Format ist
   * xx:00 oder xx:30.
   *
   * @param value
   *              Datum und Uhrzeit ohne Zeitzone
   *
   * @return die Uhrzeit wurde auf 30 Minuten abgerundet
   */
  public static LocalDateTime ahora(LocalDateTime value) {
    return value.getMinute() <= 29 ? value.truncatedTo(ChronoUnit.HOURS)
        : value.truncatedTo(ChronoUnit.HOURS).plusMinutes(30);
  }



  /**
   * Schneide vom Ende eines Textes den letzten Teil ab.
   *
   * @param text
   *                 dieser text wird gekürzt
   * @param trailing
   *                 der zu entfernende Text am Wortende
   *
   * @return der gekürzte Text oder der Ursprungstext, wenn {@code trailing} nicht
   *         vorkommt
   */
  public static String stripTrailing(String text, String trailing) {

    Pattern pattern = Pattern.compile("(.*)" + trailing + "$");
    Matcher matcher = pattern.matcher(text);
    if (!matcher.matches()) return text;
    String result = matcher.group(1);

    return result;
  }



  /**
   * Lösche das Arbeitsverzeichnis und alle enthaltenen Dateien.
   * Unterverzeichnisse und deren Inhalte werden nicht gelöscht.
   *
   *
   * @param target
   *               das zu löschende Verzeichnis
   *
   * @return {@code true}, das Verzeichnis wurde gelöscht
   */
  public static boolean deleteWorkdir(Path target) {
    if (!deleteIndir(target)) return false;
    try {
      Files.delete(target);
    }
    catch (IOException e) {
      return false;
    }
    return true;
  }



  public static boolean deleteIndir(Path target) {
    boolean done = false;
    if (target == null) return done;
    if (!Files.exists(target)) return true;
    try(DirectoryStream<Path> files = Files.newDirectoryStream(target)) {
      for (Path file : files) {
        Files.delete(file);
      }
      done = true;
    }
    catch (IOException e) {}
    return done;
  }



  /**
   * Kopiert eine Datei von A nach B. Metadaten wie die Uhrzeit wird mitkopiert.
   *
   * Util.copy(new File(Quellverzeichnis, "Datei_A"), new File(Zielverzeichnis,
   * "Datei_A"))
   *
   *
   * @param src
   *            ist die Quelldatei.
   * @param dst
   *            ist die Zieldatei.
   * @return true, kopieren erfolgreich.
   */
  public static boolean copy(File src, File dst) {
    final int len_buffer = 65535;
    byte buffer[] = new byte[len_buffer];
    boolean done = false;

    try(
      BufferedInputStream inBuffer = new BufferedInputStream(new FileInputStream(src), len_buffer);
      BufferedOutputStream outBuffer = new BufferedOutputStream(new FileOutputStream(dst), len_buffer)
    ) {
      int read = 0;
      while ((read = inBuffer.read(buffer, 0, len_buffer)) > 0) {
        outBuffer.write(buffer, 0, read);
      }
      done = true;
    }
    catch (IOException e) {}
    dst.setLastModified(src.lastModified());
    return done;
  }



  /**
   * Kopiere viele Dateien von A nach B.
   *
   * @param source
   *               die Quelldateien
   * @param dest
   *               ein Verzeichnis
   * @return {@code true}, alle Dateien konnten kopiert werden
   */
  public static boolean copyToDir(File[] source, File dest) {
    if (!dest.isDirectory()) return false;
    for (File src : source) {
      File target = new File(dest.getAbsoluteFile(), src.getName());
      if (!Util.copy(src, target)) return false;
    }
    return true;
  }



  /**
   * Kopiere Datei A nach B. Die Uhrzeit von A wird mitkopiert.
   * 
   * @param src
   *            diese Datei mit vollständigem Dateipfad
   * @param dst
   *            diese Datei mit Dateipfad
   * @return
   */
  public static boolean copy(Path src, Path dst) {
    return copy(src.toFile(), dst.toFile());
  }



  /**
   * Ein Verzeichnis wird angelegt.
   *
   * @param name
   *             Das Verzeichnis, das angelegt werden soll.
   * @return true, das Verzeichnis war bereits vorhanden oder ist ab sofort
   *         vorhanden.
   */
  public static boolean createDir(String name) {
    if (name == null) throw new NullPointerException("Der Verzeichnisname ist nicht definiert, null.");
    return createDir(new File(name));
  }



  /**
   * Ein Verzeichnis wird angelegt.
   *
   * @param file
   *             Das Verzeichnis, das angelegt werden soll.
   * @return true, das Verzeichnis war bereits vorhanden oder ist ab sofort
   *         vorhanden.
   */
  public static boolean createDir(File file) {
    if (file == null) throw new NullPointerException("Der Verzeichnisname ist nicht definiert, null.");
    if (!file.exists()) return file.mkdir();
    if (file.isDirectory()) return true;
    return false;
  }



  /**
   *
   * Lösche die Datei {@code file}.
   *
   * @param file
   *             die zu löschende Datei.
   *
   * @return true, die Datei wurde gelöscht.
   *
   * @deprecated verwende die Methode {@link #deleteFile(Path)}
   */
  @Deprecated
  public static boolean deleteFile(File file) {
    if (file == null) return false;
    return file.delete();
  }



  /**
   * Lösche die Datei {@code file}. Falls die Datei ein Verzeichnis ist, dann darf
   * das Verzeichnis keine weiteren Dateien enthalten, um gelöscht werden zu
   * können.
   *
   * @param file
   *             die zu löschende Datei
   * @return {@code true}, die Datei konnte gelöscht werden
   *
   */
  public static boolean deleteFile(Path file) {
    boolean done = false;
    try {
      Files.delete(file);
      done = true;
    }
    catch (IOException e) {}
    return done;
  }



  /**
   * Lösche den Verzeichnisbaum. Das Verzeichnis selber und alle
   * Unterverzeichnisse werden gelöscht.
   *
   * @param dir
   *            ein Verzeichnis
   *
   * @throws NoDirectoryException
   *                              die übergebene Datei ist kein Verzeichnis
   * @throws IOException
   */
  public static void deleteTree(Path dir) throws NoDirectoryException, IOException {
    if (!Files.isDirectory(dir)) throw new NoDirectoryException(dir.toAbsolutePath().toString());
    try(DirectoryStream<Path> files = Files.newDirectoryStream(dir)) {
      for (Path file : files) {
        if (Files.isDirectory(file)) {
          deleteTree(file);
        }
        Files.deleteIfExists(file);
      }
    }
    Files.delete(dir);
  }



  /**
   * Lösche alle Unterverzeichnisse und deren Inhalte rekursiv in
   * <b>{@code dir}</b>, die dem regulären Ausruck <b>{@code regex}</b>
   * entsprechen. <b>{@code dir}</b> wird nicht mitgelöscht.
   * 
   * 
   * @param dir
   *              in diesem Verzeichnis wird nach allen Unterverzeichnissen
   *              gesucht, die regex entsprechen
   * @param regex
   *              ein regulärer Ausdruck
   * 
   * @throws NoDirectoryException
   *                              die übergebene Datei ist kein Verzeichnis
   * @throws IOException
   *                              generischer Fehler
   */
  public static void deleteTree(Path dir, String regex) throws NoDirectoryException, IOException {
    if (!Files.isDirectory(dir)) throw new NoDirectoryException(dir.toAbsolutePath().toString());

    Pattern pattern = Pattern.compile(regex);

    try(DirectoryStream<Path> files = Files.newDirectoryStream(dir)) {
      for (Path file : files) {
        if (Files.isDirectory(file)) {
          Matcher matcher = pattern.matcher(file.getFileName().toString());
          if (matcher.matches()) {
            deleteTree(file);
          }
        }
      }
    }
  }



  /**
   * Lösche alle Dateien, die nicht dem regulären Ausdruck {@code regex}
   * entsprechen. Unterverzeichnisse und {@code dir} werden nicht gelöscht.
   * 
   * 
   * @param dir
   *              in diesem Verzeichnis wird gelöscht
   * @param regex
   *              ein regulärer Ausdruck
   * 
   * @throws IOException
   *                     generischer Fehler
   */
  public static void deleteIfNotExists(Path dir, String regex) throws IOException {
    Pattern pattern = Pattern.compile(regex);
    String input;
    try(DirectoryStream<Path> files = Files.newDirectoryStream(dir)) {
      for (Path file : files) {
        if (Files.isRegularFile(file)) {
          input = file.getFileName().toString();
          Matcher matcher = pattern.matcher(input);
          if (matcher.matches()) continue;
          Util.deleteFile(file);
        }
      }
    }
  }



  /**
   * Kopiere ein Verzeichnis und alle seine Unterverzeichnisse von A nach B. Wenn
   * B nicht vorhanden ist, dann lege B als Verzeichnis an.
   * 
   * @param sourceTree
   *                   Verzeichnisbaum A
   * @param destTree
   *                   Verzeichnisbaum B
   * @throws NoDirectoryException
   *                              der Quellbaum ist kein Verzeichnis
   * @throws IOException
   *                              das Zielverzeichnis konnte nicht angelegt werden
   */
  public static void copyTree(Path sourceTree, Path destTree) throws NoDirectoryException, IOException {

    Path created = null;
    if (!Files.isDirectory(sourceTree))
      throw new NoDirectoryException(sourceTree.toAbsolutePath().toString());
    if (!Files.isDirectory(destTree)) {
      created = Files.createDirectory(destTree);
    }
    try(DirectoryStream<Path> files = Files.newDirectoryStream(sourceTree)) {
      for (Path file : files) {
        if (Files.isDirectory(file)) {
          copyTree(file, created.resolve(file.getFileName()));
        }
        Path destFile = created.resolve(file.getFileName());
        Util.copy(file, destFile);
      }
    }

  }



  /**
   * Das aktuelle Datum wird als Text zurückgegeben.
   *
   * @param pattern
   *                das Datumformat
   *
   * @return ein formatiertes Datum
   */
  public static String date(String pattern) {
    LocalDate date = LocalDate.now();
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
    return date.format(formatter);
  }



  /**
   * Datum und Zeit werden als XML-Element zurückgegeben. Die Zeitzone wird nicht
   * berücksichtigt.
   * 
   * @param localDateTime
   *                      Datum und Zeit ohne Zeitzone
   * 
   * @return Datum und Zeit werden XML-Kalenderelement zurückgegeben
   * 
   * @throws DatatypeConfigurationException
   *                                        Konfigurationsfehler
   */
  public static XMLGregorianCalendar toXMLGregorianCalendar(LocalDateTime localDateTime)
      throws DatatypeConfigurationException {
    DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
    XMLGregorianCalendar xmlCalendar = datatypeFactory.newXMLGregorianCalendar();
    xmlCalendar.setDay(localDateTime.getDayOfMonth());
    xmlCalendar.setMonth(localDateTime.getMonthValue());
    xmlCalendar.setYear(localDateTime.getYear());
    xmlCalendar.setHour(localDateTime.getHour());
    xmlCalendar.setMinute(localDateTime.getMinute());
    xmlCalendar.setSecond(localDateTime.getSecond());
    return xmlCalendar;
  }



  /**
   * Ändere die Vordergrundfarbe und erhalte die Transparenz.
   * 
   * @param image
   *              monochrones Bild mit transparentem Hintergrund
   * @param color
   *              die neue Vordergrundfarbe
   */
  public static void modifyRGBA(BufferedImage image, Color color) {
    IntStream.range(0, image.getWidth()).forEach(x -> {
      IntStream.range(0, image.getHeight()).forEach(y -> {
        int pixel = image.getRGB(x, y);
        int alpha = (pixel >> 24) & 0xff;
        Color alphaColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
        image.setRGB(x, y, alphaColor.getRGB());
      });
    });
  }



  /**
   * Diese Methode findet ein Element in einer {@code java.util.Enumeration}.
   * 
   * @param <T>
   *                    dieser Suchtyp
   * @param enumeration
   *                    diese Enumeration
   * @param condition
   *                    diese Suchbedingung
   * 
   * @return das gefundene Element oder {@code null}, wenn nichts gefunden wurde
   */
  public static <T> T findInEnumeration(Enumeration<T> enumeration, Predicate<T> condition) {
    while (enumeration.hasMoreElements()) {
      T element = enumeration.nextElement();
      if (condition.test(element)) {
        return element; // Treffer gefunden, vorzeitig abbrechen
      }
    }
    return null; // Kein Treffer
  }

}
