package de.ivu.elect.business.auffaelligkeit.entity;

import java.util.Arrays;
import java.util.List;

public enum BetrachteteStellenImAuffaelligkeitstestNachBenford {
  ERSTE_STELLE {
    int stelle = 1;
    @Override
    public boolean isSollZahlBetrachtetWerden(Long zahl) {
      return isZahlUngleichNull(zahl);
    }

    @Override
    public long[] getTatsaechlicheHaeufigkeitDerZiffern(List<Long> untersuchteZahlen) {
      return getTatsaechlicheHaeufigkeitDerZiffernAnEinerStelle(stelle, untersuchteZahlen);
    }

    @Override
    public double[] getBenfordsHaeufigkeitDerZiffern(List<Long> untersuchteZahlen, double[][] benfordsVerteilungDerZiffernNachStellenAnzahl) {
      return getBenfordsHaeufigkeitDerZiffernAnEinerStelle(stelle, benfordsVerteilungDerZiffernNachStellenAnzahl);
    }
  },



  ZWEITE_STELLE {
    int stelle = 2;
    @Override
    public boolean isSollZahlBetrachtetWerden(Long zahl) {
      return isZahlMindestensZweistellig(zahl);
    }

    @Override
    public long[] getTatsaechlicheHaeufigkeitDerZiffern(List<Long> untersuchteZahlen) {
      return getTatsaechlicheHaeufigkeitDerZiffernAnEinerStelle(stelle, untersuchteZahlen);
    }

    @Override
    public double[] getBenfordsHaeufigkeitDerZiffern(List<Long> untersuchteZahlen, double[][] benfordsVerteilungDerZiffernNachStellenAnzahl) {
      return getBenfordsHaeufigkeitDerZiffernAnEinerStelle(stelle, benfordsVerteilungDerZiffernNachStellenAnzahl);
    }
  },



  LETZTE_STELLE {
    @Override
    public boolean isSollZahlBetrachtetWerden(Long zahl) {
      return isZahlMindestensZweistellig(zahl);
    }

    @Override
    public long[] getTatsaechlicheHaeufigkeitDerZiffern(List<Long> untersuchteZahlen) {
      long[] tatsaechlicheHaeufigkeitDerZiffern = new long[10];
      for (Long zahl: untersuchteZahlen) {
        if (zahl != null) {
          String zahlAlsString = String.valueOf(Math.abs(zahl));
          tatsaechlicheHaeufigkeitDerZiffern[Character.getNumericValue(zahlAlsString.charAt(zahlAlsString.length() - 1))]++;
        }
      }
      return tatsaechlicheHaeufigkeitDerZiffern;
    }

    @Override
    public double[] getBenfordsHaeufigkeitDerZiffern(List<Long> untersuchteZahlen, double[][] benfordsVerteilungDerZiffernNachStellenAnzahl) {
      double[] result = new double[10];
      long[] frequenzStellenVorhanden = new long[10]; // keine Zahl wird mehr als 9 Stellen haben.
      for (Long zahl: untersuchteZahlen) {
        if (zahl != null) {
          frequenzStellenVorhanden[String.valueOf(Math.abs(zahl)).length()]++;
        }
      }
      for (int stelle = 1; stelle < Math.min(frequenzStellenVorhanden.length, benfordsVerteilungDerZiffernNachStellenAnzahl.length);stelle++) {
        for (int d = 0;d < 10;d++) {
          result[d] += frequenzStellenVorhanden[stelle] * benfordsVerteilungDerZiffernNachStellenAnzahl[stelle][d];
        }
      }
      normiereAufEins(result);
      return result;
    }
  },



  ALLE_STELLEN {
    @Override
    public boolean isSollZahlBetrachtetWerden(Long zahl) {
      return isZahlMindestensZweistellig(zahl);
    }

    @Override
    public long[] getTatsaechlicheHaeufigkeitDerZiffern(List<Long> untersuchteZahlen) {
      long[] tatsaechlicheHaeufigkeitDerZiffern = new long[10];
      for (Long zahl: untersuchteZahlen) {
        if (zahl != null) {
          String zahlAlsString = String.valueOf(Math.abs(zahl));
          for (int i = 0;i < zahlAlsString.length();i++) {
            tatsaechlicheHaeufigkeitDerZiffern[Character.getNumericValue(zahlAlsString.charAt(i))]++;
          }
        }
      }
      return tatsaechlicheHaeufigkeitDerZiffern;
    }

    @Override
    public double[] getBenfordsHaeufigkeitDerZiffern(List<Long> untersuchteZahlen, double[][] benfordsVerteilungDerZiffernNachStellenAnzahl) {
      double[] result = new double[10];
      long[] frequenzStellenVorhanden = new long[10]; // keine Zahl wird mehr als 9 Stellen haben.
      for (Long zahl: untersuchteZahlen) {
        if (zahl != null) {
          frequenzStellenVorhanden[String.valueOf(Math.abs(zahl)).length()]++;
        }
      }
      for (int i = frequenzStellenVorhanden.length - 1;i > 0;i--) {
        frequenzStellenVorhanden[i - 1] += frequenzStellenVorhanden[i];
      }
      for (int stelle = 1; stelle < Math.min(frequenzStellenVorhanden.length, benfordsVerteilungDerZiffernNachStellenAnzahl.length);stelle++) {
        for (int d = 0;d < 10;d++) {
          result[d] += frequenzStellenVorhanden[stelle] * benfordsVerteilungDerZiffernNachStellenAnzahl[stelle][d];
        }
      }
      normiereAufEins(result);
      return result;
    }
  };

  void normiereAufEins(double[] array) {
    double sum = Arrays.stream(array).sum();
    if (sum != 0) {
      for (int i = 0;i < array.length;i++) {
        array[i] /= sum;
      }
    }
  }

  double[] getBenfordsHaeufigkeitDerZiffernAnEinerStelle(int stelle, double[][] benfordsVerteilungDerZiffernNachStellenAnzahl) {
    return benfordsVerteilungDerZiffernNachStellenAnzahl[stelle];
  }

  long[] getTatsaechlicheHaeufigkeitDerZiffernAnEinerStelle(int stelle, List<Long> untersuchteZahlen) {
    long[] tatsaechlicheHaeufigkeitDerZiffern = new long[10];
    for (Long zahl: untersuchteZahlen) {
      if (zahl != null) {
        tatsaechlicheHaeufigkeitDerZiffern[getZahlAnEinerStelle(stelle, zahl)]++;
      }
    }
    return tatsaechlicheHaeufigkeitDerZiffern;
  }

  private int getZahlAnEinerStelle(int stelle, Long zahl) {
    return Character.getNumericValue(zahl.toString().charAt(stelle - (zahl >= 0? 1 : 0)));
  }

  private int getZahlAnLetztenStelle(Long zahl) {
    String zahlAlsString = zahl.toString();
    return Character.getNumericValue(zahlAlsString.charAt(zahlAlsString.length() - 1));
  }

  boolean isZahlUngleichNull(Long zahl) {
    return zahl != null && zahl != 0;
  }

  boolean isZahlMindestensZweistellig(Long zahl) {
    return zahl != null && Math.abs(zahl) >= 10;
  }

  boolean isZahlMindestensDreistellig(Long zahl) {
    return zahl != null && Math.abs(zahl) >= 100;
  }

  public abstract boolean isSollZahlBetrachtetWerden(Long zahl);
  public abstract long[] getTatsaechlicheHaeufigkeitDerZiffern(List<Long> untersuchteZahlen);
  public abstract double[] getBenfordsHaeufigkeitDerZiffern(List<Long> untersuchteZahlen, double[][] benfordsVerteilungDerZiffernNachStellenAnzahl);
}
