
package de.ivu.elect.presentation.system;

import java.math.BigDecimal;
import java.math.RoundingMode;

import javax.annotation.Nullable;

import de.ivu.elect.util.NotNull;

public class PercentUtil {

  private static final Long HUNDERT = 100L;

  private static final int ANZAHL_NACHKOMMASTELLEN = 1;
  public static final int ANZAHL_NACHKOMMASTELLEN_EXACT = 20;

  /**
   * Liefert null, wenn entweder alle oder anzahl null ist. Liefert BigDecimal.ZERO, wenn alle gleich 0 ist.
   *
   * @return ((anzahl * 100) / alle) gerundet auf ANZAHL_NACHKOMMASTELLEN Nachkommastellen
   */
  @Nullable
  public static BigDecimal percent(@Nullable Long alle, Long anzahl) {
    return percent((alle == null) ? null : new BigDecimal(alle), anzahl);
  }

  @Nullable
  public static BigDecimal percent01(@Nullable Long alle, Long anzahl) {
    return percent01((alle == null) ? null : new BigDecimal(alle), anzahl);
  }

  /**
   * Liefert null, wenn entweder alle oder anzahl null ist. Liefert BigDecimal.ZERO, wenn alle gleich 0 ist.
   *
   * @return ((anzahl * 100) / alle) gerundet auf ANZAHL_NACHKOMMASTELLEN_EXACT Nachkommastellen
   */
  @Nullable
  public static BigDecimal percentExact(@Nullable Long alle, Long anzahl) {
    if ((alle == null) || (anzahl == null)) {
      return null;
    }
    return percent(new BigDecimal(alle), anzahl, ANZAHL_NACHKOMMASTELLEN_EXACT);
  }

  /**
   * Liefert null, wenn entweder alle oder anzahl null ist. Liefert BigDecimal.ZERO, wenn alle gleich 0 ist.
   *
   * @return ((anzahl * 100) / alle) gerundet auf ANZAHL_NACHKOMMASTELLEN Nachkommastellen
   */
  @Nullable
  public static BigDecimal percent(@Nullable BigDecimal alle, Long anzahl) {
    return percent(alle, anzahl, ANZAHL_NACHKOMMASTELLEN);
  }

  /**
   * Liefert null, wenn entweder alle oder anzahl null ist. Liefert BigDecimal.ZERO, wenn alle gleich 0 ist.
   *
   * @return ((anzahl * 100) / alle) gerundet auf ANZAHL_NACHKOMMASTELLEN_EXACT Nachkommastellen
   */
  @Nullable
  public static BigDecimal percentExact(@Nullable BigDecimal alle, BigDecimal anzahl) {
    if (anzahl == null) {
      return null;
    }
    return divide(alle, anzahl.multiply(new BigDecimal(HUNDERT)), ANZAHL_NACHKOMMASTELLEN_EXACT);
  }

  @Nullable
  public static BigDecimal percent(@Nullable Long alle, Long anzahl, int anzahlNachkommaStellen) {
    if (alle == null || anzahl == null) {
      return null;
    }

    return percent(new BigDecimal(alle), anzahl, anzahlNachkommaStellen);
  }

  public static BigDecimal percent(@Nullable BigDecimal alle, Long anzahl, int anzahlNachkommaStellen) {
    if (anzahl == null) {
      return null;
    }
    BigDecimal anzahlBD = new BigDecimal(anzahl * HUNDERT);
    return divide(alle, anzahlBD, anzahlNachkommaStellen);

  }

  @Nullable
  public static BigDecimal percent01(BigDecimal alle, Long anzahl) {
    if (anzahl == null) {
      return null;
    }
    BigDecimal anzahlBD = new BigDecimal(anzahl);
    return divide(alle, anzahlBD, ANZAHL_NACHKOMMASTELLEN + 2);
  }

  private static BigDecimal divide(@Nullable BigDecimal alle, BigDecimal anzahl, int anzahlNachkommaStellen) {
    if (alle == null || anzahl == null) {
      return null;
    }
    if (BigDecimal.ZERO.compareTo(alle) == 0) {
      return BigDecimal.ZERO;
    }
    return anzahl.divide(alle, anzahlNachkommaStellen, RoundingMode.HALF_UP);
  }

  /**
   * Liefert null, wenn beide Brüche null sind. Wenn einer der Brüche null ist, wird dies wie 0 behandelt. Ein Bruch ist
   * null, wenn Zähler oder Nenner null sind.
   *
   * @return (( 100 * anzahlAktuell / anzahlGesamtAktuell) - (100 * anzahlVergleich / anzahlGesamtVergleich)) gerundet
   * auf ANZAHL_NACHKOMMASTELLEN Nachkommastellen
   */
  @Nullable
  public static BigDecimal percentDifference(@Nullable Long anzahlGesamtAktuell, @Nullable Long anzahlAktuell,
      @Nullable Long anzahlGesamtVergleich, @Nullable Long anzahlVergleich) {
    return percentDifference(anzahlGesamtAktuell, anzahlAktuell, anzahlGesamtVergleich, anzahlVergleich, ANZAHL_NACHKOMMASTELLEN);
  }

  @Nullable
  public static BigDecimal percentDifference(@Nullable Long anzahlGesamtAktuell, @Nullable Long anzahlAktuell,
      @Nullable Long anzahlGesamtVergleich, @Nullable Long anzahlVergleich, int anzahlNachkommaStellen) {
    BigDecimal anteilAktuell = percentExact(anzahlGesamtAktuell, anzahlAktuell);
    BigDecimal anteilVergleich = percentExact(anzahlGesamtVergleich, anzahlVergleich);

    if (anteilVergleich == null) {
      if (anteilAktuell == null) {
        return null;
      }
      return anteilAktuell.setScale(anzahlNachkommaStellen, RoundingMode.HALF_UP);
    } else if (anteilAktuell == null) {
      return anteilVergleich.multiply(BigDecimal.valueOf(-1)).setScale(anzahlNachkommaStellen, RoundingMode.HALF_UP);
    }

    // Jetzt die Differenz neu ausrechnen, damit sich die Rundungsfehler nicht addieren
    try {
      long zaehler = Math.multiplyExact(anzahlAktuell, anzahlGesamtVergleich)
              - Math.multiplyExact(anzahlVergleich, anzahlGesamtAktuell);
      long nenner = Math.multiplyExact(anzahlGesamtAktuell, anzahlGesamtVergleich);
      return percent(nenner, zaehler, anzahlNachkommaStellen);
    } catch (ArithmeticException e) {

      // Wenn bei exakter Berechnung der Long-Bereich überschritten wird, dann berechne auf 20 Stellen genau.
      BigDecimal anteilAktuellExact = percentExact(anzahlGesamtAktuell, anzahlAktuell);
      BigDecimal anteilVergleichExact = percentExact(anzahlGesamtVergleich, anzahlVergleich);
      BigDecimal percentDifference = anteilAktuellExact.subtract(anteilVergleichExact);
      return percentDifference.setScale(anzahlNachkommaStellen, RoundingMode.HALF_UP);
    }
  }

  public static int percentDifferenceSignum(@Nullable Long anzahlGesamtAktuell, @Nullable Long anzahlAktuell,
      @Nullable Long anzahlGesamtVergleich, @Nullable Long anzahlVergleich) {

    long zaehler1 = NotNull.notNull(anzahlAktuell);
    long nenner1 = NotNull.notNull(anzahlGesamtAktuell);
    long zaehler2 = NotNull.notNull(anzahlVergleich);
    long nenner2 = NotNull.notNull(anzahlGesamtVergleich);

    if (nenner1 == 0L) {
      if (nenner2 == 0L) {
        // Beide Vergleichswerte 0, vergleiche nur die Zähler
        return Long.compare(zaehler1, zaehler2);
      } else {
        if (zaehler1 == 0) {
          // Behandle 0/0 wie 0/1
          nenner1 = 1L;
        }
      }
    } else {
      if (nenner2 == 0L) {
        if (zaehler2 == 0) {
          // Behandle 0/0 wie 0/1
          nenner2 = 1L;
        }
      }
    }

    return Long.compare(Math.multiplyExact(zaehler1, nenner2), Math.multiplyExact(nenner1, zaehler2)) * Long.signum(nenner1) * Long.signum(nenner2);
  }

  @Nullable
  public static BigDecimal percentAbweichung(Long sollwert, Long istwert) {
    if (sollwert == null || istwert == null) {
      return null;
    }
    long absoluteDifferenz = Math.abs(sollwert - istwert);
    return percent(sollwert, absoluteDifferenz);
  }

  @Nullable
  public static BigDecimal percentAbweichungExact(Long sollwert, Long istwert) {
    if (sollwert == null || istwert == null) {
      return null;
    }
    long absoluteDifferenz = Math.abs(sollwert - istwert);
    return percentExact(sollwert, absoluteDifferenz);
  }
}
