RequestsByStatus.java

package org.silverpeas.components.formsonline.model;

import org.silverpeas.core.admin.PaginationPage;
import org.silverpeas.core.util.PaginationList;
import org.silverpeas.core.util.Pair;
import org.silverpeas.core.util.SilverpeasArrayList;
import org.silverpeas.core.util.SilverpeasList;

import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.Comparator.naturalOrder;
import static org.silverpeas.components.formsonline.model.FormInstance.*;
import static org.silverpeas.components.formsonline.model.FormInstanceValidationType.HIERARCHICAL;

/**
 * @author Nicolas Eysseric
 */
public class RequestsByStatus {

  private static final Comparator<FormInstance> FORM_INSTANCE_COMPARATOR = (a, b) -> {
    int c = b.getCreationDate().compareTo(a.getCreationDate());
    if (c == 0) {
      c = b.getIdAsInt() - a.getIdAsInt();
    }
    return c;
  };

  static final BiConsumer<Pair<Set<FormInstanceValidationType>,
        Set<FormInstanceValidationType>>, RequestValidationCriteria> toValidateCriteriaConfigurer =
      (f, t) -> {
    t.andAvoidValidatedByValidator();
    final Set<FormInstanceValidationType> possibleFormValidationTypes = f.getFirst();
    final Set<FormInstanceValidationType> possibleValidatorValidationTypes = f.getSecond().stream()
        .filter(possibleFormValidationTypes::contains)
        .collect(Collectors.toSet());
    setLastValidationTypeCriteria(possibleFormValidationTypes, possibleValidatorValidationTypes, t);
    final boolean isHierarchicalFormValidation = possibleFormValidationTypes.contains(HIERARCHICAL);
    if (isHierarchicalFormValidation) {
      if (possibleValidatorValidationTypes.contains(HIERARCHICAL)) {
        t.orValidatorIsHierarchicalOne();
      }
    } else if (possibleValidatorValidationTypes.stream()
        .filter(v -> v != HIERARCHICAL)
        .anyMatch(v -> possibleFormValidationTypes.stream().findFirst().filter(p -> p == v).isPresent())) {
      t.orNoValidator();
    }
  };

  static final BiConsumer<Pair<Set<FormInstanceValidationType>,
      Set<FormInstanceValidationType>>, RequestValidationCriteria> concernedByValidationCriteriaConfigurer = (f, t) -> {
    final Set<FormInstanceValidationType> possibleFormValidationTypes = f.getFirst();
    final boolean isLastValidatorCase = isLastValidatorCase(possibleFormValidationTypes, f.getSecond());
    if (isLastValidatorCase) {
      toValidateCriteriaConfigurer.accept(f, t);
      t.invert();
    } else {
      t.andStillNeedValidation();
      setLastValidationTypeCriteria(possibleFormValidationTypes, possibleFormValidationTypes, t);
    }
  };

  static final BiConsumer<Pair<Set<FormInstanceValidationType>,
      Set<FormInstanceValidationType>>, RequestValidationCriteria> skipValidationCriteriaIfLastValidatorConfigurer = (f, t) -> {
    if (isLastValidatorCase(f.getFirst(), f.getSecond())) {
      t.skipValidationFiltering();
    }
  };

  static final BiConsumer<Pair<Set<FormInstanceValidationType>,
      Set<FormInstanceValidationType>>, RequestValidationCriteria> canceledCriteriaConfigurer =
      (f, t) -> {
    skipValidationCriteriaIfLastValidatorConfigurer.accept(f, t);
    if (!t.isSkipValidationFiltering()) {
      t.orNoValidator();
    }
  };

  static final List<MergeRuleByStates> MERGING_RULES_BY_STATES = asList(
    new MergeRuleByStates(singletonList(STATE_DRAFT), RequestsByStatus::addDraft),
    new MergeRuleByStates(singletonList(STATE_REFUSED), RequestsByStatus::addDenied),
    new MergeRuleByStates(singletonList(STATE_VALIDATED), RequestsByStatus::addValidated),
    new MergeRuleByStates(singletonList(STATE_ARCHIVED), RequestsByStatus::addArchived),
    new MergeRuleByStates(singletonList(STATE_CANCELED), RequestsByStatus::addCanceled),
    new MergeRuleByStates(asList(STATE_UNREAD, STATE_READ), RequestsByStatus::addToValidate));

  static final List<ValidationMergeRuleByStates> VALIDATION_MERGING_RULES_BY_STATES = asList(
    new ValidationMergeRuleByStates(singletonList(STATE_REFUSED), skipValidationCriteriaIfLastValidatorConfigurer, RequestsByStatus::addDenied),
    new ValidationMergeRuleByStates(singletonList(STATE_VALIDATED), skipValidationCriteriaIfLastValidatorConfigurer, RequestsByStatus::addValidated),
    new ValidationMergeRuleByStates(singletonList(STATE_ARCHIVED), skipValidationCriteriaIfLastValidatorConfigurer, RequestsByStatus::addArchived),
    new ValidationMergeRuleByStates(singletonList(STATE_CANCELED), canceledCriteriaConfigurer, RequestsByStatus::addCanceled),
    new ValidationMergeRuleByStates(asList(STATE_UNREAD, STATE_READ), toValidateCriteriaConfigurer, RequestsByStatus::addToValidate),
    new ValidationMergeRuleByStates(asList(STATE_UNREAD, STATE_READ), concernedByValidationCriteriaConfigurer, RequestsByStatus::addConcernedByValidation));

  private final PaginationPage paginationPage;
  private SilverpeasList<FormInstance> draftList = new SilverpeasArrayList<>();
  private SilverpeasList<FormInstance> toValidateList = new SilverpeasArrayList<>();
  private SilverpeasList<FormInstance> concernedByValidationList = new SilverpeasArrayList<>();
  private SilverpeasList<FormInstance> validatedList = new SilverpeasArrayList<>();
  private SilverpeasList<FormInstance> deniedList = new SilverpeasArrayList<>();
  private SilverpeasList<FormInstance> archivedList = new SilverpeasArrayList<>();
  private SilverpeasList<FormInstance> canceledList = new SilverpeasArrayList<>();
  private SilverpeasList<FormInstance> all = null;

  RequestsByStatus(final PaginationPage paginationPage) {
    this.paginationPage = paginationPage;
  }

  private static void setLastValidationTypeCriteria(
      final Set<FormInstanceValidationType> possibleFormValidationTypes,
      final Set<FormInstanceValidationType> possibleValidatorValidationTypes,
      final RequestValidationCriteria criteria) {
    final Set<FormInstanceValidationType> lastValidationFilter = new TreeSet<>();
    possibleValidatorValidationTypes.stream()
        .filter(v -> v != HIERARCHICAL)
        .forEach(v -> Stream.of(FormInstanceValidationType.values())
            .sorted(Comparator.reverseOrder())
            .filter(l -> l.ordinal() < v.ordinal())
            .filter(possibleFormValidationTypes::contains)
            .findFirst()
            .ifPresent(lastValidationFilter::add));
    criteria.orLastValidationType(lastValidationFilter);
  }

  private static boolean isLastValidatorCase(
      final Set<FormInstanceValidationType> possibleFormValidationTypes,
      final Set<FormInstanceValidationType> possibleValidatorValidationTypes) {
    return possibleValidatorValidationTypes.stream()
        .filter(possibleFormValidationTypes::contains)
        .max(naturalOrder())
        .filter(v -> possibleFormValidationTypes.stream().max(naturalOrder()).orElse(null) == v)
        .isPresent();
  }

  /**
   * Gets the possible request validations from given requests.
   * <p>
   *   BE CAREFUL of that following methods MUST have been called before using this method:
   *   <ul>
   *     <li>{@link FormDetail#setIntermediateReceiversAsUsers(List)}</li>
   *     <li>{@link FormDetail#setIntermediateReceiversAsGroups(List)}</li>
   *     <li>{@link FormDetail#setReceiversAsUsers(List)}</li>
   *     <li>{@link FormDetail#setReceiversAsGroups(List)}</li>
   *   </ul>
   * </p>
   * @return a set of possible request validations sorted as the {@link FormInstanceValidationType} enum.
   */
  public static Set<FormInstanceValidationType> possibleRequestValidationsFrom(
      final Collection<FormInstance> requests) {
    return requests.stream()
        .map(FormInstance::getForm)
        .distinct()
        .flatMap(f -> f.getPossibleRequestValidations().keySet().stream())
        .collect(Collectors.toCollection(TreeSet::new));
  }

  private void addDraft(final SilverpeasList<FormInstance> formInstances) {
    resetAll();
    draftList = merge(formInstances, draftList);
  }

  private void addArchived(final SilverpeasList<FormInstance> formInstances) {
    resetAll();
    archivedList = merge(formInstances, archivedList);
  }

  private void addDenied(final SilverpeasList<FormInstance> formInstances) {
    resetAll();
    deniedList = merge(formInstances, deniedList);
  }

  private void addValidated(final SilverpeasList<FormInstance> formInstances) {
    resetAll();
    validatedList = merge(formInstances, validatedList);
  }

  private void addToValidate(final SilverpeasList<FormInstance> formInstances) {
    resetAll();
    toValidateList = merge(formInstances, toValidateList);
  }

  private void addConcernedByValidation(final SilverpeasList<FormInstance> formInstances) {
    resetAll();
    concernedByValidationList = merge(formInstances, concernedByValidationList);
  }

  private void addCanceled(final SilverpeasList<FormInstance> formInstances) {
    resetAll();
    canceledList = merge(formInstances, canceledList);
  }

  public SilverpeasList<FormInstance> getDraft() {
    return draftList;
  }

  public SilverpeasList<FormInstance> getToValidate() {
    return toValidateList;
  }

  public SilverpeasList<FormInstance> getConcernedByValidation() {
    return concernedByValidationList;
  }

  public SilverpeasList<FormInstance> getDenied() {
    return deniedList;
  }

  public SilverpeasList<FormInstance> getValidated() {
    return validatedList;
  }

  public SilverpeasList<FormInstance> getArchived() {
    return archivedList;
  }

  public SilverpeasList<FormInstance> getCanceled() {
    return canceledList;
  }

  public boolean isEmpty() {
    return getAll().isEmpty();
  }

  public SilverpeasList<FormInstance> getAll() {
    if (all == null) {
      all = merge(getDraft(), getToValidate(), getConcernedByValidation(), getValidated(),
          getDenied(), getArchived(), getCanceled());
    }
    return all;
  }

  private void resetAll() {
    all = null;
  }

  /**
   * Merges the two given lists without modifying them into a new one.
   * @param lists the lists to merge.
   * @return the list which is the result of merge.
   */
  @SafeVarargs
  private final SilverpeasList<FormInstance> merge(final SilverpeasList<FormInstance>... lists) {
    int size = 0;
    long maxSize = 0;
    for (SilverpeasList<FormInstance> list : lists) {
      size += list.size();
      maxSize += list.originalListSize();
    }
    final Map<String, FormInstance> merge = new HashMap<>(size);
    for (SilverpeasList<FormInstance> list : lists) {
      for (FormInstance formInstance : list) {
        merge.putIfAbsent(formInstance.getId(), formInstance);
      }
    }
    Stream<FormInstance> resultStream = merge.values().stream().sorted(FORM_INSTANCE_COMPARATOR);
    if (paginationPage != null) {
      resultStream = resultStream.limit(paginationPage.getPageSize());
    }
    return PaginationList.from(resultStream.collect(Collectors.toList()), maxSize - (size - merge.values().size()));
  }

  public static class MergeRuleByStates {
    private final List<Integer> states;
    private final BiConsumer<RequestsByStatus, SilverpeasList<FormInstance>> merger;

    public MergeRuleByStates(final List<Integer> states,
        final BiConsumer<RequestsByStatus, SilverpeasList<FormInstance>> merger) {
      this.states = states;
      this.merger = merger;
    }

    public List<Integer> getStates() {
      return states;
    }

    public BiConsumer<RequestsByStatus, SilverpeasList<FormInstance>> getMerger() {
      return merger;
    }
  }

  public static class ValidationMergeRuleByStates extends MergeRuleByStates {
    private final BiConsumer<Pair<Set<FormInstanceValidationType>, Set<FormInstanceValidationType>>, RequestValidationCriteria> validationCriteriaConfigurer;

    public ValidationMergeRuleByStates(final List<Integer> states,
        final BiConsumer<Pair<Set<FormInstanceValidationType>, Set<FormInstanceValidationType>>, RequestValidationCriteria> validationCriteriaConfigurer,
        final BiConsumer<RequestsByStatus, SilverpeasList<FormInstance>> merger) {
      super(states, merger);
      this.validationCriteriaConfigurer = validationCriteriaConfigurer;
    }

    public BiConsumer<Pair<Set<FormInstanceValidationType>,
        Set<FormInstanceValidationType>>, RequestValidationCriteria> getValidationCriteriaConfigurer() {
      return validationCriteriaConfigurer;
    }
  }
}