
package de.ivu.elect.presentation.gebietsbaum.tree;

import de.ivu.elect.business.gebietsbaum.boundary.GebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.control.EntityMitWurzelgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPK;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.presentation.LayoutComponentsContext;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;
import org.primefaces.model.TreeNode;

import javax.annotation.Nullable;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@RequestScoped
public class GebietsbaumTreeBuilderLazy extends AbstractGebietsbaumTreeBuilder {

  private static final long serialVersionUID = 1L;

  @Inject
  private transient LayoutComponentsContext layoutComponentsContext;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  @Inject
  private transient Messages messages;

  @Inject
  protected transient GebietExtendedDAO gebietDao;

  public GebietsbaumTreeBuilderLazy() {
    super();
  }

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

  @Override
  public GebietsbaumTreeModel buildModel() {
    if (!layoutComponentsContext.isSelected()) {
      return buildEmptyModel();
    }

    boolean isGebietSelected = isGebietSelected();
    Gebiet selectedGebiet;

    if (isGebietSelected) {
      selectedGebiet = getGebietSelected();
    } else {
      selectedGebiet = getWurzelgebiet();

      if (selectedGebiet == null) {
        return buildEmptyModel();
      }
    }

    Map<Gebiet, GebietsbaumTreeNode> gebietTreeNodeMap = new HashMap<>();

    TreeNode root = convertToTreeNodeIncludingChildrenReturnRoot(selectedGebiet, gebietTreeNodeMap, selectedGebiet.isWurzelgebiet());

    for (Map.Entry<Gebiet, GebietsbaumTreeNode> entry : gebietTreeNodeMap.entrySet()) {
      GebietsbaumTreeNode treeNode = entry.getValue();
      Gebiet gebiet = entry.getKey();
      if (!treeNode.isChildrenLoaded()) {
        setLeaf(treeNode, gebiet);
      }

    }

    TreeNode selected = null;
    if (isGebietSelected) {
      selected = gebietTreeNodeMap.get(selectedGebiet);
      selected.setSelected(true);
    }

    return new GebietsbaumTreeModel(root, selected);
  }

  @Nullable
  protected Gebiet getWurzelgebiet() {
    return getWahlterminSelected().getWurzelgebiet(EntityMitWurzelgebietDAO.fromDao(gebietDao));
  }

  private void setLeaf(GebietsbaumTreeNode treeNode, Gebiet gebiet) {
    treeNode.setLeaf(getGebieteSortiertNachPosition(gebiet).isEmpty());
  }

  private GebietsbaumTreeNode convertToTreeNodeIncludingChildrenReturnRoot(Gebiet gebiet,
                                                                           Map<Gebiet, GebietsbaumTreeNode> gebietTreeNodeMap, boolean expand) {

    GebietsbaumTreeNode result = convertToTreeNode(gebiet);
    gebietTreeNodeMap.put(gebiet, result);

    if (expand) { // Untergebiete nur aufklappen, wenn oberstes Gebiet
      result.setExpanded(true);
      result.setChildrenLoaded(true);
      for (GebietPK childGebietPk : getGebieteSortiertNachPosition(gebiet)) {
        Gebiet childGebiet = loadGebiet(childGebietPk);
        GebietsbaumTreeNode childNode = gebietTreeNodeMap.get(childGebiet);
        if (childNode == null) {
          childNode = convertToTreeNode(childGebiet);
          gebietTreeNodeMap.put(childGebiet, childNode);
        }
        result.getChildren().add(childNode);
      }
    }

    if (gebiet.getUebergeordnetesGebiet() == null) {
      return result;
    }

    return convertToTreeNodeIncludingChildrenReturnRoot(gebiet.getUebergeordnetesGebiet(), gebietTreeNodeMap, true);
  }

  private final transient Map<GebietPK, List<GebietPK>> gebieteSortiertNachPosition = new HashMap<>();

  private List<GebietPK> getGebieteSortiertNachPosition(Gebiet gebiet) {
    if (gebiet == null) {
      return Collections.emptyList();
    }

    if (!gebieteSortiertNachPosition.containsKey(gebiet.getPk())) {
      gebieteSortiertNachPosition.put(gebiet.getPk(), loadUntergebiete(gebiet));
    }

    return gebieteSortiertNachPosition.get(gebiet.getPk());
  }

  protected List<GebietPK> loadUntergebiete(Gebiet gebiet) {
    return gebiet.getGebieteSortiertNachPosition(gebietDao).stream().map(Gebiet::getPk).collect(Collectors.toList());
  }

  @Nullable
  protected Gebiet loadGebiet(GebietPK pk) {
    if (pk == null) {
      return null;
    }
    return gebietDao.findByPrimaryKey(pk);
  }

  protected GebietsbaumTreeNode convertToTreeNode(Gebiet gebiet) {
    return new GebietsbaumTreeNode(gebiet, false, gebietsbaumTreeNodeIconResolver, messages, getGebietLoader(), gebietNummerFormatter,
        gebietSecurity.getEinsehenGebietSecurity().isRechtVorhanden(gebiet),
        gebietSecurity.getBearbeitenGebietSecurity().isRechtVorhanden(gebiet));
  }

  protected Function<GebietPK, Gebiet> getGebietLoader() {
    return gebietDao::findByPrimaryKey;
  }

  public void lazyLoad(GebietsbaumTreeNode gebietsbaumTreeNode) {
    gebietsbaumTreeNode.setChildrenLoaded(true);

    GebietsbaumTreeNodeData data = (GebietsbaumTreeNodeData) gebietsbaumTreeNode.getData();

    Gebiet gebiet = loadGebiet(data.getPk());
    for (GebietPK childGebietPk : getGebieteSortiertNachPosition(gebiet)) {
      Gebiet childGebiet = loadGebiet(childGebietPk);
      GebietsbaumTreeNode childNote = convertToTreeNode(childGebiet);
      setLeaf(childNote, childGebiet);
      gebietsbaumTreeNode.getChildren().add(childNote);
    }
  }

  @Override
  public void reset() {
    gebieteSortiertNachPosition.clear();
  }

}
