
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;

  public static final int ANZAHL_NACHKOMMASTELLEN_DEFAULT = 1; // ELECT-16753

  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_DEFAULT Nachkommastellen
   */
  @Nullable
  public static BigDecimal percentGerundetNachkommastellen1(@Nullable Long alle, Long anzahl) {
    return percentGerundetNachkommastellen1(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_DEFAULT Nachkommastellen
   */
  @Nullable
  public static BigDecimal percentGerundetNachkommastellen1(@Nullable BigDecimal alle, Long anzahl) {
    return percent(alle, anzahl, ANZAHL_NACHKOMMASTELLEN_DEFAULT);
  }

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

  @Nullable
  public static BigDecimal percentGerundetNachkommastellen3(BigDecimal alle, Long anzahl) {
    if (anzahl == null) {
      return null;
    }

    BigDecimal anzahlBD = new BigDecimal(anzahl);
    int anzahlNachkommastellen = ANZAHL_NACHKOMMASTELLEN_DEFAULT + 2;

    return divide(alle, anzahlBD, anzahlNachkommastellen);
  }

  /**
   * 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 percentGerundetNachkommastellenExact(@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_EXACT Nachkommastellen
   */
  @Nullable
  public static BigDecimal percentGerundetNachkommastellenExact(@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);
  }

  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_DEFAULT);
  }

  @Nullable
  public static BigDecimal percentDifference(@Nullable Long anzahlGesamtAktuell, @Nullable Long anzahlAktuell, @Nullable Long anzahlGesamtVergleich, @Nullable Long anzahlVergleich,
      int anzahlNachkommastellen) {
    BigDecimal anteilAktuell = percentGerundetNachkommastellenExact(anzahlGesamtAktuell, anzahlAktuell);
    BigDecimal anteilVergleich = percentGerundetNachkommastellenExact(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 = percentGerundetNachkommastellenExact(anzahlGesamtAktuell, anzahlAktuell);
      BigDecimal anteilVergleichExact = percentGerundetNachkommastellenExact(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 percentAbweichungGerundetNachkommastellen1(Long sollwert, Long istwert) {
    if (sollwert == null || istwert == null) {
      return null;
    }
    long absoluteDifferenz = Math.abs(sollwert - istwert);
    return percentGerundetNachkommastellen1(sollwert, absoluteDifferenz);
  }

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