
package de.ivu.elect.business.wahlvorschlag.control;

import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.LongStream;

import javax.inject.Inject;
import javax.persistence.NoResultException;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppeimgebiet.boundary.AbstractGruppeImGebietDAO;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietDAO;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietHierarchyBuilder;
import de.ivu.elect.business.gruppeimgebiet.control.GruppeImGebietCache;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.kandidatur.control.DirektkandidaturCreate;
import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.jee.faces.Messages;

public class DirektkandidatCreate extends AbstractDirektkandidatCreate {

  @Inject
  protected GruppeImGebietDAO gruppeImGebietDao;

  @Inject
  protected GruppeImGebietHierarchyBuilder gruppeImGebietHierarchyBuilder;

  @Inject
  protected DirektkandidaturCreate direktkandidaturCreate;

  @Inject
  protected UniqueWahlvorschlagUkValidator uniqueWahlvorschlagUkValidator;

  @Inject
  private transient Messages messages;

  public DirektkandidatCreate() {
    super();
  }

  public void saveAndCreateDirektkandidatur(Direktkandidat direktkandidat, Direktkandidatur direktkandidatur) {
    save(direktkandidat);
    save(direktkandidatur);
  }

  @Override
  public void save(Direktkandidat direktkandidat) {
    saveAndUpdateGruppeImGebiet(direktkandidat.getGruppeImGebiet(), direktkandidat);
  }

  private void saveAndUpdateGruppeImGebiet(GruppeImGebiet gruppeImGebiet, Direktkandidat direktkandidat) {
    // NOTE that the GebietUK is part of the WahlvorschlagUK and must be set before validating the unique WahlvorschlagUK!
    updateGruppeImGebiet(gruppeImGebiet, direktkandidat);

    assertUniqueWahlvorschlagUk(direktkandidat);

    super.save(direktkandidat);
  }

  private void updateGruppeImGebiet(GruppeImGebiet template, Direktkandidat direktkandidat) {
    GruppeImGebiet gruppeImGebiet = template;

    // WASDE-635
    gruppeImGebietHierarchyBuilder.updateGruppeImGebietNamen(gruppeImGebiet, direktkandidat);

    try {
      gruppeImGebiet = gruppeImGebietDao.findByGruppeAndGebiet(template.getGruppe(), template.getGebiet());

      template.copyEntityAttributesUsingSetter(gruppeImGebiet);
    } catch (NoResultException ignored) {
      // Do nothing, but save a new entity.
    }

    direktkandidat.setGruppeImGebiet(gruppeImGebiet);

    bestimmeUndSetzeRelativePosition(gruppeImGebiet);

    gruppeImGebietHierarchyBuilder.saveAndCompleteHierarchy(direktkandidat.getGruppeImGebiet());
  }

  protected void bestimmeUndSetzeRelativePosition(GruppeImGebiet gruppeImGebiet) {
    if (gruppeImGebiet.getRelativePosition() == null) {
      gruppeImGebiet.setRelativePosition(bestimmeMoeglicheRelativePosition(gruppeImGebiet, gruppeImGebietDao));
    }
  }

  public static Long bestimmeMoeglicheRelativePosition(GruppeImGebiet gruppeImGebiet, AbstractGruppeImGebietDAO abstractGruppeImGebietDAO) {
    Gebiet gebiet = gruppeImGebiet.getGebiet();
    if (gebiet == null) {
      return null;
    }

    Set<Long> vergebeneNummern = abstractGruppeImGebietDAO.findByGebiet(gebiet).stream()
        .filter(g -> g.getRelativePosition() != null)
        .filter(g -> g.getGruppe().isPolitischeGruppe())
        .map(GruppeImGebiet::getRelativePosition)
        .collect(Collectors.toSet());

    Gruppe gruppe = gruppeImGebiet.getGruppe();
    if (gruppe != null) {
      if (gruppe.getVorgegebenePosition() != null && !vergebeneNummern.contains(gruppe.getVorgegebenePosition())) {
        return gruppe.getVorgegebenePosition();
      }
      if (gruppe.getRelativePosition() != null && !vergebeneNummern.contains(gruppe.getRelativePosition())) {
        return gruppe.getRelativePosition();
      }
    }

    return LongStream.range(1, vergebeneNummern.size() + 2)
        .filter(i -> !vergebeneNummern.contains(i))
        .findFirst()
        .orElse(0L); // 0L cannot happen
  }

  public void save(GruppeImGebiet gruppeImGebiet, Direktkandidat direktkandidat, GruppeImGebietCache gruppeImGebietCache) {
    // NOTE that the GebietUK is part of the WahlvorschlagUK and must be set before validating the unique WahlvorschlagUK!
    direktkandidat.setGruppeImGebiet(gruppeImGebietCache.findOrCreate(gruppeImGebiet));

    assertUniqueWahlvorschlagUk(direktkandidat);

    super.save(direktkandidat);
  }

  private void save(Direktkandidatur direktkandidatur) {
    direktkandidaturCreate.save(direktkandidatur);
  }

  private void assertUniqueWahlvorschlagUk(Direktkandidat entity) {
    if (!isUniqueWahlvorschlagUk(entity)) {
      throw new NonUniqueWahlvorschlagUkException(entity.getWahlvorschlagUK(), messages);
    }
  }

  protected boolean isUniqueWahlvorschlagUk(Direktkandidat entity) {
    return uniqueWahlvorschlagUkValidator.isUniqueWahlvorschlagUk(entity);
  }
}
