package de.ivu.elect.presentation.gebietsbaum;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.enterprise.event.Observes;
import javax.enterprise.event.Reception;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.deltaspike.core.api.scope.ViewAccessScoped;

import de.ivu.elect.business.gebietsbaum.boundary.GebietEdit;
import de.ivu.elect.business.gebietsbaum.boundary.GebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.boundary.GebietSelectChangeEvent;
import de.ivu.elect.business.gebietsbaum.boundary.GebietSessionSelect;
import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPK;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import de.ivu.elect.jee.entity.customdata.EmptyCustomData;
import de.ivu.elect.jee.faces.PreConditionCheck;
import de.ivu.elect.jee.presentation.Icons;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.redirect.Redirect;
import de.ivu.elect.presentation.AbstractElectEditView;
import de.ivu.elect.presentation.View;
import de.ivu.elect.presentation.gebietsbaum.action.GebietDeleteAction;
import de.ivu.elect.presentation.gebietsbaum.tree.GebietsbaumTreeNodeIconResolver;
import de.ivu.elect.presentation.gebietsbaum.tree.LazyGebietsbaumView;
import de.ivu.elect.presentation.hilfe.OnlineHilfeId;

@ViewAccessScoped
@Named(GebietSortierenView.COMPONENT_NAME)
@View(viewId = GebietSortierenView.VIEW_ID, useCase = UseCasePermission.GEBIET_SORTIEREN)
@OnlineHilfeId("777")
public class GebietSortierenView extends AbstractElectEditView<Gebiet> {

  private static final long serialVersionUID = 1L;

  public static final String COMPONENT_NAME = "gebietSortierenView";

  public static final String VIEW_ID = "/gebietsbaum/gebiet-sortieren.xhtml";

  private static final int MAX_EXPAND_COUNT = 19;

  @Inject
  private transient GebietExtendedDAO gebietExtendedDAO;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  @Inject
  private transient GebietsbaumTreeNodeIconResolver gebietsbaumTreeNodeIconResolver;

  @Inject
  private transient GebietEdit gebietEdit;

  @Inject
  private transient Icons icons;

  @Inject
  private transient LazyGebietsbaumView lazyGebietsbaumView;

  @Inject
  private transient GebietSessionSelect gebietSessionSelect;

  @Inject
  private transient GebietDeleteAction gebietDeleteAction;

  @Nullable
  @PreConditionCheck
  public String checkCondition() {
    if (!isGebietSelected()) {
      return GebietSearchView.VIEW_ID;
    }

    return null;
  }

  private GebietSortierenTreeNode root1;

  private GebietSortierenTreeNode root2;

  protected void init() {
    if (root1 == null || root2 == null) {

      root1 = new GebietSortierenTreeNode(null, null);
      root2 = new GebietSortierenTreeNode(null, null);
      GebietSortierenTreeNode node = new GebietSortierenTreeNode(new GebietSortierenData(messages.getMessageForMessageKey("gebiet.sortieren.zuloeschen.node"), null, icons.getIconDelete()), root2);
      node.setExpanded(true);
      node.setSelectable(false);
      Gebiet rootGebiet = getSelectedGebiet();
      Map<Gebiet, List<Gebiet>> untergebiete = getUntergebiete(rootGebiet);

      addGebiet(rootGebiet, rootGebiet, root1, untergebiete);
    }
  }

  private Map<Gebiet, List<Gebiet>> getUntergebiete(Gebiet root) {
    List<Gebiet> gebiete = new ArrayList<>();
    Wahl wahl = getSelectedWahl();
    if (wahl != null) {
      gebiete = gebietExtendedDAO.findByWahl(wahl);
    } else {
      Wahltermin wahltermin = getSelectedWahltermin();
      if (wahltermin != null) {
        gebiete = gebietExtendedDAO.findByWahltermin(wahltermin);
      }
    }

    gebiete = gebiete.stream().filter(g -> g.isInGebietshierarchieUntergeordnetesGebietVon(root)).collect(Collectors.toList());

    if (!isBearbeitenRechtFuerAlleGebieteVorhanden(gebiete)) {
      addWarningMessage("gebiet.sortieren.recht.bearbeiten.nicht.fuer.alle.gebiete.vorhanden");
      return Collections.emptyMap();
    }

    gebiete.sort(new GebietByBreadthFirstComparator());
    Map<Gebiet, List<Gebiet>> untergebiete = new HashMap<>();

    for (Gebiet gebiet : gebiete) {
      untergebiete.computeIfAbsent(gebiet, x -> new ArrayList<>()); // damit alle Gebiete in der Map sind...
      if (gebiet.getUebergeordnetesGebiet() != null) {
        untergebiete.computeIfAbsent(gebiet.getUebergeordnetesGebiet(), x -> new ArrayList<>()).add(gebiet);
      }
    }
    return untergebiete;
  }

  private boolean isBearbeitenRechtFuerAlleGebieteVorhanden(List<Gebiet> gebiete) {
    return gebiete.stream().allMatch(gebiet -> getBearbeitenGebietSecurity().isRechtVorhanden(gebiet));
  }

  private void addGebiet(Gebiet root, Gebiet gebiet, GebietSortierenTreeNode parent, Map<Gebiet, List<Gebiet>> untergebiete) {
    GebietSortierenTreeNode gebietNode = new GebietSortierenTreeNode(new GebietSortierenData(gebietNummerFormatter.getDarstellung(gebiet), gebiet.getPk(), gebietsbaumTreeNodeIconResolver.getIcon(gebiet)), parent);
    gebietNode.setParent(parent);
    // ELECT-12700 gebietNode.setRowKey(gebiet.getPk().toString());
    gebietNode.setExpanded(gebiet.equals(root) || untergebiete.size() < MAX_EXPAND_COUNT);
    for (Gebiet untergebiet : untergebiete.getOrDefault(gebiet, Collections.emptyList())) {
      addGebiet(root, untergebiet, gebietNode, untergebiete);
    }

  }

  public GebietSortierenTreeNode getRoot1() {
    init();
    return root1;
  }

  public GebietSortierenTreeNode getRoot2() {
    init();
    return root2;
  }

  @Override
  protected void resetModel() {
    super.resetModel();
    root1 = null;
    root2 = null;
  }

  @Override
  protected Gebiet getEntity() {
    return new Gebiet(EmptyCustomData.INSTANCE);
  }

  @Override
  protected void addProperties(FormBuilder formBuilder, Gebiet entity) {

  }

  @Override
  public String delete() {
    throw new UnsupportedOperationException();
  }

  @Override
  protected boolean isGebietBearbeitenRechtPruefen() {
    return false;
  }

  @Override
  protected boolean isSavePermissionVorhanden() {
    return hasPermission(UseCasePermission.GEBIET_SORTIEREN);
  }

  @Override
  @Redirect
  public String save() {

    Gebiet rootGebiet = getSelectedGebiet();
    Map<Gebiet, List<Gebiet>> untergebiete = getUntergebiete(rootGebiet);

    Set<GebietPK> gebieteInDb = untergebiete.keySet().stream().map(Gebiet::getPk).collect(Collectors.toSet());
    Set<GebietPK> gebieteInTree = new HashSet<>();
    GebietSortierenTreeNode root1 = getRoot1();
    collect(root1, gebieteInTree);
    Set<GebietPK> gebieteZuLoeschenInTree = new HashSet<>();
    collect(getRoot2(), gebieteZuLoeschenInTree);
    gebieteInTree.addAll(gebieteZuLoeschenInTree);

    Set<String> nichtLoeschbar = new HashSet<>();
    for (GebietPK gebietPK : gebieteZuLoeschenInTree) {
      Gebiet gebiet = gebietExtendedDAO.findByPrimaryKey(gebietPK);
      if (gebietDeleteAction.isActionPossible(gebiet)){
        // TODO nichtLoeschbar.add(gebietNummerFormatter.getDarstellung(gebiet));
      }
    }
    if (!nichtLoeschbar.isEmpty()) {
        addErrorMessage("gebiete.sortieren.gebiete.nicht.loeschbar", String.join(", ", nichtLoeschbar));
    } else {

      if (isEmptyDataPresent(root1.getChildrenNodes()) || gebieteInDb.containsAll(gebieteInTree) && gebieteInTree.containsAll(gebieteInDb)) {

        if (root1.getChildren().size() > 1) {
          addErrorMessage("gebiete.sortieren.mehr.als.ein.wurzelgebiet");
        } else {
          Map<GebietPK, List<GebietPK>> neueHierarchie = new HashMap<>();
          Map<GebietPK, Long> neuePositionen = new HashMap<>();

          if (root1.getChildrenNodes().isEmpty()) {
            gebietEdit.speichereReihenfolge(rootGebiet, neueHierarchie, gebieteZuLoeschenInTree, neuePositionen);
            addInfoMessage("gebiete.sortieren.reihenfolge.gespeichert");
            gebietExtendedDAO.clear(); // neue Reihenfolgen?
            gebietSessionSelect.setEntity((Gebiet) null);

          } else {
            GebietSortierenTreeNode gebietRootNode = root1.getChildrenNodes().iterator().next();
            addNeueSortierung(gebietRootNode, neueHierarchie, neuePositionen);

            if (isGebieteUnterhalbVonBezirk(neueHierarchie)) {
              addErrorMessage("gebiete.sortieren.gebiete.unterhalb.von.bezirken.nicht.erlaubt");
            } else {

              gebietEdit.speichereReihenfolge(rootGebiet, neueHierarchie, gebieteZuLoeschenInTree, neuePositionen);
              addInfoMessage("gebiete.sortieren.reihenfolge.gespeichert");
              gebietExtendedDAO.clear(); // neue Reihenfolgen?
              if (gebietRootNode.isPkPresent()) {
                GebietPK gebietPK = gebietRootNode.getGebietPk();
                gebietSessionSelect.setEntity(gebietPK);
              }
            }
            reset();
            lazyGebietsbaumView.reset();
          }
        }
      } else {
        addErrorMessage("gebiete.sortieren.gebiete.veraendert");
        reset();
      }
    }
    return VIEW_ID;
  }

  private boolean isGebieteUnterhalbVonBezirk(Map<GebietPK, List<GebietPK>> neueHierarchie) {
    return neueHierarchie.entrySet().stream()
            .filter(entry -> !entry.getValue().isEmpty()).anyMatch(entry -> gebietExtendedDAO.findByPrimaryKey(entry.getKey()).getGebietsart().getGebietsartBezirksart().isBezirk());
  }


  private boolean isEmptyDataPresent(List<GebietSortierenTreeNode> children) {
    boolean result = true;
    for (GebietSortierenTreeNode child : children) {
      if (child.isPkPresent()) {
        result &= isEmptyDataPresent(child.getChildrenNodes());
      } else {
        return false; // Root2 nach root1 verschoben?
      }
    }
    return result;
  }

  private void addNeueSortierung(GebietSortierenTreeNode treeNode, Map<GebietPK, List<GebietPK>> neueSortierung, Map<GebietPK, Long> neuePositionen) {
    if (treeNode.isPkPresent()) {
      ArrayList<GebietPK> childPks = new ArrayList<>();
      neueSortierung.put(treeNode.getGebietPk(), childPks);
      long position = 0;
      for (GebietSortierenTreeNode child : treeNode.getChildrenNodes()) {
        if (child.isPkPresent()) {
          position++;
          GebietPK gebietPK = child.getGebietPk();
          childPks.add(gebietPK);
          neuePositionen.put(gebietPK, position);
          addNeueSortierung(child, neueSortierung, neuePositionen);
        }
      }
    }
  }


  private void collect(GebietSortierenTreeNode treeNode, Set<GebietPK> gebieteInTree) {
    if (treeNode.isPkPresent()) {
      gebieteInTree.add(treeNode.getGebietPk());
    }
    for (GebietSortierenTreeNode child : treeNode.getChildrenNodes()) {
      collect(child, gebieteInTree);
    }
  }

  @Override
  public boolean isBtnCancelRendered() {
    return true;
  }

  @Override
  @Redirect
  public String cancel() {
    return goBack(getStartViewId());
  }

  public void onGebietSelectChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) GebietSelectChangeEvent event) {
    reset();
  }

  public String getGebietName() {
    if (isGebietSelected()) {
      return gebietNummerFormatter.getDarstellung(getSelectedGebiet());
    }

    return "";
  }

  public boolean isDisabled() {
    return getRoot1().getChildren().isEmpty();
  }

  public boolean isLoeschenDisabled() {
    return isDisabled() || !hasPermission(UseCasePermission.GEBIET_DELETE);
  }

}
