
package de.ivu.elect.presentation.dokumente.exchange;

import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;

import javax.annotation.Nullable;

import de.ivu.elect.business.datei.entity.DateiExport;
import de.ivu.elect.business.datei.entity.DateiExportPK;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.dokumente.boundary.AbstractDokumentVorlageDAO;
import de.ivu.elect.business.dokumente.control.DokumentVorlageByImportiertAmComparator;
import de.ivu.elect.business.dokumente.entity.DokumentVorlage;
import de.ivu.elect.business.dokumente.entity.DokumentVorlageArt;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.control.ElectCommonPrincipal;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.presentation.dynamicform.AdvancedDynaFormModel;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.presentation.dokumente.AbstractDokumentErzeugenView;
import de.ivu.elect.presentation.exchange.ExportView;

public abstract class AbstractDokumentBulkExportSubView<W extends Wahl, T extends ExportView<C>, C> {

  private final T exportView;

  private final DokumentBulkExportSearchEntryTemplate dokumentBulkExportSearchEntryTemplate;

  private final AbstractDokumentVorlageDAO dokumentVorlageDao;

  private final Messages messages;

  private AdvancedDynaFormModel model;

  public AbstractDokumentBulkExportSubView(T exportView, DokumentBulkExportSearchEntryTemplate dokumentBulkExportSearchEntryTemplate, Messages messages,
      AbstractDokumentVorlageDAO dokumentVorlageDao) {
    super();

    requireNonNull(exportView, "View may NOT be null!");

    this.exportView = exportView;
    this.dokumentBulkExportSearchEntryTemplate = dokumentBulkExportSearchEntryTemplate;
    this.dokumentVorlageDao = dokumentVorlageDao;
    this.model = null;
    this.messages = messages;
  }

  public T getExportView() {
    return exportView;
  }

  public boolean hasUseCasePermissions(ElectCommonPrincipal principal) {
    return getUseCasePermissions().stream().allMatch(principal::hasUseCasePermission) || principal.hasUseCasePermission(getExportView().getUseCasePermission());
  }

  protected abstract Collection<UseCasePermission> getUseCasePermissions();

  public List<FileFormat> getFileFormats() {
    return getExportView().getSupportedFileFormats();
  }

  public boolean isSupportsFinaleKonvertierung(FileType fileType) {
    return getExportView().isSupportsFinaleKonvertierung(fileType);
  }

  public abstract boolean isExportEnabled();

  public Optional<DateiExportPK> export(DokumentBulkExportSearchEntry dokumentBulkExportSearchEntry, FileType finaleKonvertierung) {
    if (dokumentBulkExportSearchEntry.isExportConfigured()) {
      C context = createExportContext(dokumentBulkExportSearchEntry);

      if (context != null) {
        return getExportView().export(dokumentBulkExportSearchEntry.getFileFormat(), context, finaleKonvertierung).getDateiExport();
      }
    }

    return Optional.empty();
  }

  protected abstract C createExportContext(DokumentBulkExportSearchEntry dokumentBulkExportSearchEntry);

  public abstract List<DokumentBulkExportSearchEntry> collectDokumentBulkExportSearchEntries();

  protected List<DokumentBulkExportSearchEntry> collectDokumentBulkExportSearchEntriesFromDokumentErzeugenView(AbstractDokumentErzeugenView< ? , ? > dokumentErzeugenView, W wahl,
      List<FileFormat> fileFormats, Collection<FileType> fileTypesRelevant, List<String> dokumentVorlagenRequiredBeschreibungen, Predicate< ? super DokumentVorlage> dokumentVorlageFilter) {
    DokumentVorlageArt dokumentVorlageArt = dokumentErzeugenView.getDokumentVorlageArt();

    int dokumentVorlagenRequiredAnzahl = 1; // Cf. AbstractDokumentErzeugenView#getSelectedDokumentVorlage!!
    List<DokumentVorlage> dokumentVorlagen = collectLatestDokumentVorlageMatching(wahl, dokumentVorlageArt, dokumentVorlageFilter);

    List<FileFormat> fileFormatsRelevant = new ArrayList<>(fileFormats);
    fileFormatsRelevant.removeIf(fileFormat -> !fileTypesRelevant.contains(fileFormat.getFileType()));

    if (fileFormatsRelevant.isEmpty()) {
      fileFormatsRelevant.addAll(fileFormats);
    }

    return createDokumentBulkExportSearchEntries(wahl, fileFormatsRelevant, dokumentVorlageArt, dokumentVorlagenRequiredAnzahl, dokumentVorlagenRequiredBeschreibungen, dokumentVorlagen);
  }

  protected List<DokumentBulkExportSearchEntry> createDokumentBulkExportSearchEntries(W wahl, List<FileFormat> fileFormats, @Nullable DokumentVorlageArt dokumentVorlagenArt,
      int dokumentVorlagenRequiredAnzahl, List<String> dokumentVorlagenRequiredBeschreibungen, List<DokumentVorlage> dokumentVorlagen) {
    List<DokumentBulkExportSearchEntry> result = new ArrayList<>();

    if (fileFormats.isEmpty()) {
      // Eine Zeile trotz fehlenden/noch nicht aktiven Mapper anzeigen?
    } else {
      for (FileFormat fileFormat : fileFormats) {
        if (dokumentVorlagen.isEmpty()) {
          result.add(createDokumentBulkExportSearchEntry(wahl, fileFormat, dokumentVorlagenArt, dokumentVorlagenRequiredAnzahl, dokumentVorlagenRequiredBeschreibungen, emptyList()));

          if (dokumentVorlagenRequiredAnzahl > 0) {
            break; // Nur eine Zeile mit Hinweis auf fehlende Dokumentvorlage anzeigen.
          }
        } else {
          result.add(createDokumentBulkExportSearchEntry(wahl, fileFormat, dokumentVorlagenArt, dokumentVorlagenRequiredAnzahl, dokumentVorlagenRequiredBeschreibungen, dokumentVorlagen));
        }
      }
    }

    return result;
  }

  private DokumentBulkExportSearchEntry createDokumentBulkExportSearchEntry(W wahl, FileFormat fileFormat, DokumentVorlageArt dokumentVorlagenArt, int dokumentVorlagenRequiredAnzahl,
      List<String> dokumentVorlagenRequiredBeschreibungen, List<DokumentVorlage> dokumentVorlagen) {
    return new DokumentBulkExportSearchEntry(this, formatDokumentBulkExportBeschreibung(), getDokumentBulkExportModel(), formatDokumentBezeichnung(wahl), formatDokumentBeschreibung(wahl), fileFormat,
        dokumentVorlagenArt, dokumentVorlagenRequiredAnzahl, dokumentVorlagenRequiredBeschreibungen, dokumentVorlagen);
  }

  /**
   * @return Optional vor der Dokumentengenerierung anzuzeigender Hinweis...
   *
   * @see AbstractDokumentBulkExportSubView#addDokumentBulkExportParameter
   */
  protected String formatDokumentBulkExportBeschreibung() {
    return ""; // To be overridden...
  }

  private AdvancedDynaFormModel getDokumentBulkExportModel() {
    if (model == null) {
      FormBuilder formBuilder = new FormBuilder();

      addDokumentBulkExportParameter(formBuilder);

      model = formBuilder.createModel();
    }

    return model;
  }

  /**
   * @return Optional vor der Dokumentengenerierung zu erfassende Parameter...
   *
   * @see AbstractDokumentBulkExportSubView#formatDokumentBulkExportBeschreibung
   */
  protected void addDokumentBulkExportParameter(FormBuilder formBuilder) {
    // To be overridden...
  }

  protected String formatDokumentBezeichnung(W wahl) {
    return getDokumentBulkExportSearchEntryTemplate().getBezeichnung(wahl, wahl.getWahltermin(), getMessages());
  }

  protected String formatDokumentBeschreibung(W wahl) {
    return getDokumentBulkExportSearchEntryTemplate().getBeschreibung(wahl, wahl.getWahltermin(), getMessages());
  }

  protected DokumentBulkExportSearchEntryTemplate getDokumentBulkExportSearchEntryTemplate() {
    return dokumentBulkExportSearchEntryTemplate;
  }

  @Nullable
  protected DokumentVorlage extractDokumentVorlage(Collection<DokumentVorlage> dokumentVorlagen, Predicate< ? super DokumentVorlage> dokumentVorlageFilter) {
    return findLatestDokumentVorlageMatching(dokumentVorlagen, dokumentVorlageFilter).orElse(null);
  }

  protected List<DokumentVorlage> collectLatestDokumentVorlageMatching(W wahl, @Nullable DokumentVorlageArt dokumentVorlageArt, Predicate< ? super DokumentVorlage> dokumentVorlageFilter) {
    return findLatestDokumentVorlageMatching(wahl, dokumentVorlageArt, dokumentVorlageFilter).map(Arrays::asList).orElse(emptyList());
  }

  protected Optional<DokumentVorlage> findLatestDokumentVorlageMatching(W wahl, @Nullable DokumentVorlageArt dokumentVorlageArt, Predicate< ? super DokumentVorlage> dokumentVorlageFilter) {
    return findLatestDokumentVorlageMatching(collectDokumentVorlagen(wahl, dokumentVorlageArt), dokumentVorlageFilter);
  }

  private Optional<DokumentVorlage> findLatestDokumentVorlageMatching(Collection<DokumentVorlage> dokumentVorlagen, Predicate< ? super DokumentVorlage> dokumentVorlageFilter) {
    return dokumentVorlagen.stream() //
        .filter(dokumentVorlageFilter) //
        .sorted(DokumentVorlageByImportiertAmComparator.DESCENDING) //
        .findFirst();
  }

  private List<DokumentVorlage> collectDokumentVorlagen(W wahl, @Nullable DokumentVorlageArt dokumentVorlageArt) {
    if (dokumentVorlageArt != null) {
      return dokumentVorlageDao.findByWahlAndArtOrderByName(wahl, dokumentVorlageArt);
    }

    return emptyList();
  }

  protected String getMessage(Enum< ? > enumValue) {
    return getMessages().lokalisiere(enumValue);
  }

  protected String getMessage(String msgKey, Object... params) {
    return getMessages().getMessageForMessageKey(msgKey, params);
  }

  protected Messages getMessages() {
    return messages;
  }

  protected List<DokumentVorlage> attach(List<DokumentVorlage> dokumentVorlagen) {
    return dokumentVorlagen.stream().map(this::attach).collect(toList());
  }

  protected DokumentVorlage attach(DokumentVorlage dokumentVorlage) {
    if (dokumentVorlage != null && dokumentVorlage.isPersistent()) {
      return dokumentVorlageDao.attach(dokumentVorlage);
    }

    return dokumentVorlage;
  }
}
