package de.ivu.elect.presentation.stimmeingabe;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

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.optionconfig.animation.Animation;
import org.primefaces.model.charts.optionconfig.title.Title;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabeHochgerechnet;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.presentation.system.PercentUtil;


import static de.ivu.elect.presentation.charts.BarChartDataset.convertToRGBA;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

public class StimmeingabeHochgerechnetBarChartBuilder {

  public List<BarChartModel> getBarChartModels(List<StimmeingabeHochgerechnet> stimmeingabenHochgerechnet, Messages messages) {

    List<StimmeingabeHochgerechnet> stimmeingabenHochgerechnetFiltered = filtereStimmeingabeHochgerechnet(stimmeingabenHochgerechnet);

    Set<Stimmenart> stimmenarten = stimmeingabenHochgerechnetFiltered.stream()
        .filter(Objects::nonNull)
        .map(StimmeingabeHochgerechnet::getStimmenart)
        .filter(Objects::nonNull).collect(Collectors.toSet());

    List<BarChartModel> result = new ArrayList<>();

    stimmenarten.forEach(stimmenart -> result.add(createBarChartModelFuerStimmenart(stimmenart, stimmeingabenHochgerechnetFiltered, messages)));

    return result;
  }

  private List<StimmeingabeHochgerechnet> filtereStimmeingabeHochgerechnet(List<StimmeingabeHochgerechnet> stimmeingabenHochgerechnet) {
    return stimmeingabenHochgerechnet.stream()
        .filter(Objects::nonNull)
        .filter(isStimmeingabenHochgerechnetVonPolitischeGruppe())
        .collect(Collectors.toList());
  }

  private BarChartModel createBarChartModelFuerStimmenart(Stimmenart stimmenart, List<StimmeingabeHochgerechnet> stimmeingabenHochgerechnet, Messages messages) {
    List<StimmeingabeHochgerechnet> anzuzeigendeStimmeingabeHochgerechnet = stimmeingabenHochgerechnet.stream()
        .filter(Objects::nonNull)
        .filter(isStimmeingabenHochgerechnetVomStimmenart(stimmenart))
        .collect(Collectors.toList());

    return createBarChartModel(stimmenart, anzuzeigendeStimmeingabeHochgerechnet, messages);
  }

  private BarChartModel createBarChartModel(Stimmenart stimmenart, List<StimmeingabeHochgerechnet> anzuzeigendeStimmeingabeHochgerechnet, Messages messages) {
    BarChartModel model = new BarChartModel();

    ChartData data = new ChartData();
    model.setData(data);

    List<String> labels = getLabels(anzuzeigendeStimmeingabeHochgerechnet);
    data.setLabels(labels);

    BarChartDataSet dataSetHochgerechnet = getHochgerechneteStimmenBarChartDataSet(anzuzeigendeStimmeingabeHochgerechnet, messages);
    BarChartDataSet dataSetVorperiode = getVorperiodeStimmenBarChartDataSet(anzuzeigendeStimmeingabeHochgerechnet, messages);

    data.addChartDataSet(dataSetHochgerechnet);
    data.addChartDataSet(dataSetVorperiode);

    model.setOptions(getBarChartModelOptions(messages, stimmenart));

    return model;
  }

  private List<String> getLabels(List<StimmeingabeHochgerechnet> anzuzeigendeStimmeingabeHochgerechnet) {
    return anzuzeigendeStimmeingabeHochgerechnet.stream()
        .map(this::getGruppenName)
        .collect(Collectors.toList());
  }

  private BarChartDataSet getHochgerechneteStimmenBarChartDataSet(List<StimmeingabeHochgerechnet> anzuzeigendeStimmeingabeHochgerechnet, Messages messages) {
    double alpha = .9;
    String dataSetLabel = messages.getMessageForMessageKey("stimmeingabe.hochgerechnet.stimmen.anteil.hochgerechnet");
    return getBarChartDataSet(anzuzeigendeStimmeingabeHochgerechnet, alpha, dataSetLabel, getStimmeingabeHochgerechnetInProzentGetter());
  }

  private BarChartDataSet getVorperiodeStimmenBarChartDataSet(List<StimmeingabeHochgerechnet> anzuzeigendeStimmeingabeHochgerechnet, Messages messages) {
    double alpha = .3;
    String dataSetLabel = messages.getMessageForMessageKey("stimmeingabe.hochgerechnet.stimmen.anteil.vorperiode");
    return getBarChartDataSet(anzuzeigendeStimmeingabeHochgerechnet, alpha, dataSetLabel, getStimmeingabeVorperiodeInProzentGetter());
  }

  private BarChartDataSet getBarChartDataSet(List<StimmeingabeHochgerechnet> anzuzeigendeStimmeingabeHochgerechnet, double alpha, String dataSetLabel, Function<StimmeingabeHochgerechnet, Number> stimmenGetter) {
    BarChartDataSet dataSet = new BarChartDataSet();

    dataSet.setLabel(dataSetLabel);

    List<Number> values = new ArrayList<>();
    dataSet.setData(values);

    List<String> bgColor = new ArrayList<>();
    dataSet.setBackgroundColor(bgColor);

    List<String> borderColor = new ArrayList<>();
    dataSet.setBorderColor(borderColor);
    dataSet.setBorderWidth(1);

    anzuzeigendeStimmeingabeHochgerechnet.forEach(stimmeingabeHochgerechnet -> {
      values.add(stimmenGetter.apply(stimmeingabeHochgerechnet));
      bgColor.add(convertToRGBA(getFarbe(stimmeingabeHochgerechnet), alpha));
      borderColor.add(convertToRGBA(getFarbe(stimmeingabeHochgerechnet), 1));
    });
    return dataSet;
  }

  private BarChartOptions getBarChartModelOptions(Messages messages, Stimmenart stimmenart) {
    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);
    String stimmenartName = messages.getMessageForMessageKey("Stimmenart." + stimmenart);
    title.setText(messages.getMessageForMessageKey("stimmeingabe.hochgerechnet.stimmen.chart.bar.title", stimmenartName));
    title.setFontStyle("bold");
    title.setFontColor("#2980B9");
    title.setFontSize(18);
    options.setTitle(title);

    Animation animation = new Animation();
    animation.setDuration(2000);
    options.setAnimation(animation);

    return options;
  }

  private String getGruppenName(StimmeingabeHochgerechnet stimmeingabeHochgerechnet) {
    Gruppe gruppe = stimmeingabeHochgerechnet.getGruppe();
    if (gruppe == null) {
      return "";
    }
    String kurzname = gruppe.getKurzname();
    if (isNotBlank(kurzname)) {
      return kurzname;
    }
    String name = gruppe.getName();
    if (isNotBlank(name)) {
      return name;
    }
    return "";
  }

  private String getFarbe(StimmeingabeHochgerechnet stimmeingabeHochgerechnet) {
    Gruppe gruppe = stimmeingabeHochgerechnet.getGruppe();
    if (gruppe == null) {
      return getGraueFarbe();
    }
    String farbe = gruppe.getFarbe();
    if (farbe == null) {
      return getGraueFarbe();
    }
    return farbe;
  }

  private String getGraueFarbe() {
    return "d6cdb4";
  }

  private Function<StimmeingabeHochgerechnet, Number> getStimmeingabeVorperiodeInProzentGetter() {
    return stimmeingabeHochgerechnet -> PercentUtil.percent(stimmeingabeHochgerechnet.getGueltigeVorperiode(), stimmeingabeHochgerechnet.getAnzahlVorperiode());
  }

  private Function<StimmeingabeHochgerechnet, Number> getStimmeingabeHochgerechnetInProzentGetter() {
    return stimmeingabeHochgerechnet -> PercentUtil.percent(stimmeingabeHochgerechnet.getGueltigeHochgerechhnet(), stimmeingabeHochgerechnet.getAnzahlHochgerechnet());
  }

  private Predicate<StimmeingabeHochgerechnet> isStimmeingabenHochgerechnetVonPolitischeGruppe() {
    return stimmeingabeHochgerechnet -> stimmeingabeHochgerechnet.getGruppe() != null && stimmeingabeHochgerechnet.getGruppe().isPolitischeGruppe();
  }

  private Predicate<StimmeingabeHochgerechnet> isStimmeingabenHochgerechnetVomStimmenart(Stimmenart stimmenart) {
    return stimmeingabeHochgerechnet -> stimmeingabeHochgerechnet.getStimmenart() != null && stimmeingabeHochgerechnet.getStimmenart() == stimmenart;
  }
}
