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

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.primefaces.model.charts.ChartData;
import org.primefaces.model.charts.axes.cartesian.CartesianScales;
import org.primefaces.model.charts.axes.cartesian.linear.CartesianLinearAxes;
import org.primefaces.model.charts.axes.cartesian.linear.CartesianLinearTicks;
import org.primefaces.model.charts.bar.BarChartDataSet;
import org.primefaces.model.charts.bar.BarChartModel;
import org.primefaces.model.charts.bar.BarChartOptions;
import org.primefaces.model.charts.line.LineChartDataSet;
import org.primefaces.model.charts.line.LineChartModel;
import org.primefaces.model.charts.line.LineChartOptions;
import org.primefaces.model.charts.optionconfig.title.Title;

import de.ivu.elect.jee.faces.Messages;

public class AuffaelligkeitBenfordTestChartErgebnis {
  private final BetrachteteStellenImAuffaelligkeitstestNachBenford betrachteteStellen;

  private List<Long> untersuchteZahlenSortiert;

  private List<Long> nichtBetrachteZahlenSortiert; // weil sie z. B. kleiner als 10 oder null sind.

  private long[] tatsaechlicheHaeufigkeitDerZiffern;

  private double[] benfordsHaeufigkeitDerZiffern;

  private Messages messages;

  public AuffaelligkeitBenfordTestChartErgebnis(BetrachteteStellenImAuffaelligkeitstestNachBenford betrachteteStellen, List<Long> untersuchteZahlen, List<Long> nichtBetrachteZahlen,
                                                long[] tatsaechlicheHaeufigkeitDerZiffern, double[] benfordsHaeufigkeitDerZiffern) {
    assert tatsaechlicheHaeufigkeitDerZiffern.length == benfordsHaeufigkeitDerZiffern.length && tatsaechlicheHaeufigkeitDerZiffern.length == 10 : "erwartete/tatsaechliche Haeufigkeitsarray hat falsche Groesse";
    this.betrachteteStellen = betrachteteStellen;
    this.untersuchteZahlenSortiert = untersuchteZahlen.stream().sorted(this::compareLongObjects).collect(Collectors.toList());
    this.nichtBetrachteZahlenSortiert = nichtBetrachteZahlen.stream().sorted(this::compareLongObjects).collect(Collectors.toList());
    this.tatsaechlicheHaeufigkeitDerZiffern = tatsaechlicheHaeufigkeitDerZiffern;
    this.benfordsHaeufigkeitDerZiffern = benfordsHaeufigkeitDerZiffern;
  }

  public void setMessages(Messages messages) {
    this.messages = messages;
  }

  public BetrachteteStellenImAuffaelligkeitstestNachBenford getBetrachteteStellen() {
    return betrachteteStellen;
  }

  public String getBetrachteteStellenLabel() {
    return messages.getMessageForMessageKey("stimmeingabe.auffaelligkeit.visualisiert.benford.test.auf." + getBetrachteteStellen().toString().toLowerCase());
  }

  public List<Long> getUntersuchteZahlenSortiert() {
    return untersuchteZahlenSortiert;
  }

  public List<Long> getNichtBetrachteZahlenSortiert() {
    return nichtBetrachteZahlenSortiert;
  }

  private List<Number> getValuesTatsaechlicheHaeufigkeitDerZiffern() {
    return Arrays.stream(tatsaechlicheHaeufigkeitDerZiffern).boxed().collect(Collectors.toList());
  }

  private List<Number> getValuesBenfordsHaeufigkeitDerZiffern() {
    long anzahlVonBeobachtungen = Arrays.stream(tatsaechlicheHaeufigkeitDerZiffern).sum();
    int anzahlAnzuzeigendeNachkommazahlen = 2;
    return Arrays.stream(benfordsHaeufigkeitDerZiffern)
        .map(wahrscheinlichkeit -> BigDecimal.valueOf(wahrscheinlichkeit * anzahlVonBeobachtungen).setScale(anzahlAnzuzeigendeNachkommazahlen, RoundingMode.HALF_EVEN).doubleValue())
        .boxed().collect(Collectors.toList());
  }

  public double getPValueNachFlaechenRatio () {
    long[] observed = Arrays.copyOf(tatsaechlicheHaeufigkeitDerZiffern, tatsaechlicheHaeufigkeitDerZiffern.length);
    long observations = Arrays.stream(observed).sum();
    double[] expected = Arrays.stream(benfordsHaeufigkeitDerZiffern).map(x -> x * observations).toArray();

    return BenfordStatistischerTestArt.FLAECHEN_RATIO.berechneP_value(expected, observed);
  }

  public String getPValueFormatted(double p_value) {
    if (Double.isNaN(p_value) || Double.isInfinite(p_value)) {
      return null;
    }
    return BigDecimal.valueOf(p_value * 100).setScale(2, RoundingMode.HALF_EVEN).toPlainString() + "%";
  }

  public String getPValueNachFlaechenRatioLabel () {
    return messages.getMessageForMessageKey("stimmeingabe.auffaelligkeit.visualisiert.benford.test.p.value.flaechen.ratio.label");
  }


  public double getPValueNachGTest () {
    double expected[] = benfordsHaeufigkeitDerZiffern;
    long observed[] = tatsaechlicheHaeufigkeitDerZiffern;
    long observations = Arrays.stream(observed).sum();
    if (betrachteteStellen.equals(BetrachteteStellenImAuffaelligkeitstestNachBenford.ERSTE_STELLE)) {
      expected = Arrays.copyOfRange(benfordsHaeufigkeitDerZiffern, 1, benfordsHaeufigkeitDerZiffern.length);
      observed = Arrays.copyOfRange(tatsaechlicheHaeufigkeitDerZiffern, 1, tatsaechlicheHaeufigkeitDerZiffern.length);
    }

    if (observations > 5000) {
      long finalObservations = observations;
      observed = Arrays.stream(observed).map(count -> Math.round((1.0 * count / finalObservations) * 5000)).toArray();
      observations = 5000;
    }

    return BenfordStatistischerTestArt.G_TEST.berechneP_value(expected, observed);
  }
  public double getPValueNachChiQuadratTest () {
    double expected[] = benfordsHaeufigkeitDerZiffern;
    long observed[] = tatsaechlicheHaeufigkeitDerZiffern;
    long observations = Arrays.stream(observed).sum();
    if (betrachteteStellen.equals(BetrachteteStellenImAuffaelligkeitstestNachBenford.ERSTE_STELLE)) {
      expected = Arrays.copyOfRange(benfordsHaeufigkeitDerZiffern, 1, benfordsHaeufigkeitDerZiffern.length);
      observed = Arrays.copyOfRange(tatsaechlicheHaeufigkeitDerZiffern, 1, tatsaechlicheHaeufigkeitDerZiffern.length);
    }
    if (observations > 5000) {
      long finalObservations = observations;
      observed = Arrays.stream(observed).map(count -> Math.round((1.0 * count / finalObservations) * 5000)).toArray();
      observations = 5000;
    }
    return BenfordStatistischerTestArt.CHI_QUADRAT_ANPASSUNGSTEST.berechneP_value(expected, observed);
  }

  private long notNull(Long x) {
    return x == null ? 0L : x;
  }



  public LineChartModel getLineModel() {
    LineChartModel lineModel = new LineChartModel();
    ChartData data = new ChartData();

    double alpha = 1;

    LineChartDataSet dataSetTatsaechlicheHaeufigkeit = creatLineChartDataSet(
        messages.getMessageForMessageKey("stimmeingabe.auffaelligkeit.visualisiert.benford.tatsaechliche.haeufigkeiten.der.ziffern"),
        getValuesTatsaechlicheHaeufigkeitDerZiffern(),
        "rgba(0, 0, 128" + ",  " + alpha + ")");
    LineChartDataSet dataSetBenfordsHaeufigkeit = creatLineChartDataSet(
        messages.getMessageForMessageKey("stimmeingabe.auffaelligkeit.visualisiert.benford.benfords.haeufigkeiten.der.ziffern"),
        getValuesBenfordsHaeufigkeitDerZiffern(),
        "rgba(255, 0, 0" + ",  " + alpha + ")");

    data.addChartDataSet(dataSetTatsaechlicheHaeufigkeit);
    data.addChartDataSet(dataSetBenfordsHaeufigkeit);
    data.setLabels(getLebels());

    lineModel.setData(data);

    lineModel.setOptions(getLineModelOptions(getBetrachteteStellenLabel()));

    return lineModel;
  }

  public BarChartModel getBarModel() {
    BarChartModel barModel = new BarChartModel();
    ChartData data = new ChartData();

    double alpha = 1;

    BarChartDataSet dataSetTatsaechlicheHaeufigkeit = creatBarChartDataSet(
        messages.getMessageForMessageKey("stimmeingabe.auffaelligkeit.visualisiert.benford.tatsaechliche.haeufigkeiten.der.ziffern"),
        getValuesTatsaechlicheHaeufigkeitDerZiffern(),
        alpha * 1);
    BarChartDataSet dataSetBenfordsHaeufigkeit = creatBarChartDataSet(
        messages.getMessageForMessageKey("stimmeingabe.auffaelligkeit.visualisiert.benford.benfords.haeufigkeiten.der.ziffern"),
        getValuesBenfordsHaeufigkeitDerZiffern(),
        alpha * .5);

    data.addChartDataSet(dataSetTatsaechlicheHaeufigkeit);
    data.addChartDataSet(dataSetBenfordsHaeufigkeit);
    data.setLabels(getLebels());

    barModel.setData(data);

    barModel.setOptions(getBarModelOptions(getBetrachteteStellenLabel()));

    return barModel;
  }

  private LineChartDataSet creatLineChartDataSet(String dataSetLabel, List<Number> values, String borderColorRGBA) {

    LineChartDataSet dataSet = new LineChartDataSet();

    dataSet.setLabel(dataSetLabel);
    dataSet.setData(new ArrayList<>(values));

    dataSet.setFill(false);
    dataSet.setBorderColor(borderColorRGBA);
    dataSet.setLineTension(0.1);

    return dataSet;
  }

  private BarChartDataSet creatBarChartDataSet(String dataSetLabel, List<Number> values, double alphaFuerFarben) {
    BarChartDataSet dataSet = new BarChartDataSet();

    dataSet.setLabel(dataSetLabel);
    dataSet.setData(values);

    List<String> bgColor = getColors(alphaFuerFarben * .8);
    dataSet.setBackgroundColor(bgColor);
    List<String> borderColor = getColors(alphaFuerFarben * 1);
    dataSet.setBorderColor(borderColor);
    dataSet.setBorderWidth(1);

    return dataSet;
  }

  private LineChartOptions getLineModelOptions(String lineChartTitle) {
    LineChartOptions options = new LineChartOptions();
    Title title = new Title();
    title.setDisplay(true);
    title.setText(lineChartTitle);

    CartesianScales cScales = new CartesianScales();
    CartesianLinearAxes linearAxes = new CartesianLinearAxes();
    CartesianLinearTicks ticks = new CartesianLinearTicks();
    ticks.setBeginAtZero(true);
    linearAxes.setTicks(ticks);
    cScales.addYAxesData(linearAxes);
    options.setScales(cScales);

    options.setTitle(title);
    return options;
  }

  private BarChartOptions getBarModelOptions(String barChartTitle) {
    BarChartOptions options = new BarChartOptions();
    CartesianScales cScales = new CartesianScales();
    CartesianLinearAxes linearAxes = new CartesianLinearAxes();
    CartesianLinearTicks ticks = new CartesianLinearTicks();
    ticks.setBeginAtZero(true);
    linearAxes.setTicks(ticks);
    cScales.addYAxesData(linearAxes);
    options.setScales(cScales);

    Title title = new Title();
    title.setDisplay(true);
    title.setText(barChartTitle);
    options.setTitle(title);

    return options;
  }

  private int compareLongObjects(Long o1, Long o2) {
    if (o1 == null) {
      if (o2 == null) {
        return 0;
      }
      return -1;
    } else if (o2 == null) {
      return 1;
    }
    return o1.compareTo(o2);
  }

  private List<String> getLebels() {
    return Stream.of("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")
        .map(ziffer -> messages.getMessageForMessageKey("stimmeingabe.auffaelligkeit.visualisiert.benford.haeufigkeit.der.ziffer", ziffer)).collect(
            Collectors.toList());
  }

  private List<String> getColors(double alpha) {
    List<String> colors = new ArrayList<>();

    colors.add("rgba(0, 0, 128" + ",  " + alpha + ")"); // navy
    colors.add("rgba(255, 0, 0" + ",  " + alpha + ")"); // red
    colors.add("rgba(255, 215, 0" + ",  " + alpha + ")"); // gold
    colors.add("rgba(0, 0, 255" + ",  " + alpha + ")"); // blue
    colors.add("rgba(0, 128, 0" + ",  " + alpha + ")"); // green
    colors.add("rgba(255, 255, 0" + ",  " + alpha + ")"); // yellow
    colors.add("rgba(255, 0, 255" + ",  " + alpha + ")"); // magenta
    colors.add("rgba(139, 69, 19" + ",  " + alpha + ")"); // brown
    colors.add("rgba(0, 0, 0" + ",  " + alpha + ")"); // black
    colors.add("rgba(128, 0, 128" + ",  " + alpha + ")"); // purple

    return colors;
  }
}
