package ru.simplex_software.wicket_springdata;

import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.springframework.data.domain.Persistable;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Модель хранимой коллекции.
 */
public class PersistableCollectionModel<P extends Persistable<? extends Serializable>> extends LoadableDetachableModel<Collection<IModel<P>>> {

    /**
     * Коллекция моделей.
     */
    private Collection<IModel<P>> models;

    public PersistableCollectionModel(Collection<IModel<P>> collection) {
        this.models = collection;
    }

    @Override
    protected Collection<IModel<P>> load() {
        models.forEach(model -> ((HibernateModel<P>) model).load());
        return models;
    }

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

    public void detach() {
        models.forEach(IModel::detach);
    }

    /**
     * Добавление модели.
     */
    public void add(IModel<P> model) {
        models.add(model);
    }

    /**
     * Оборачивание элемента в модель и добавление в коллецию.
     */
    public void add(P object) {
        models.add(new HibernateModel<>(object));
    }

    /**
     * Оборачивание элементов коллекции в модели и добавление в коллецию.
     *
     * @param items коллекция элементов.
     */
    public void addAll(Collection<P> items) {
        items.forEach(this::add);
    }

    /**
     * Оборачивание элементов коллекции в модели и добавление в текущую коллекцию.
     *
     * @param objects исходная коллекция.
     */
    public void wrapAndSet(Collection<P> objects) {
        models.clear();
        objects.stream()
                .map(HibernateModel::new)
                .map(hm -> (IModel<P>) hm)
                .forEach(models::add);
    }

    /**
     * Удаление модели из коллекции.
     */
    public void remove(IModel<P> model) {
        models.remove(model);
    }

    /**
     * Очистка коллекции моделей.
     */
    public void clear() {
        models.clear();
    }

    /**
     * Фильтрация списка модклей.
     */
    public Collection<IModel<P>> filter(Predicate<P> predicate, Supplier<Collection<IModel<P>>> supplier) {
        return models.stream()
                .filter(model -> predicate.test(model.getObject()))
                .collect(Collectors.toCollection(supplier));
    }

    /**
     * Получение потока элементов.
     */
    public Stream<P> getElementsStream() {
        return models.stream()
                .map(IModel::getObject);
    }

    /**
     * Проверка наличия модели в коллекции.
     */
    public boolean containsModel(IModel<P> model) {
        return models.contains(model);
    }

    /**
     * Проверка наличия объекта в коллекции.
     */
    public boolean containsModelObject(P object) {
        return !unwrapAndFilter(item -> item.equals(object), HashSet::new).isEmpty();
    }

    /**
     * Получение итератора коллеции моделей.
     */
    public Iterator<IModel<P>> getModelsIterator() {
        return models.iterator();
    }

    /**
     * Получение развернутых элементов коллекции.
     *
     * @param supplier поставщик коллекции элементов.
     * @return коллекция развернутых элементов.
     */
    public <C extends Collection<P>> C unwrapAndGet(Supplier<C> supplier) {
        return models.stream()
                .map(IModel::getObject)
                .collect(Collectors.toCollection(supplier));
    }

    /**
     * Получение развернутых элементов с учетом фильтра.
     *
     * @param predicate условие фильтрации.
     * @param supplier  поставщик коллекции элементов.
     * @return коллекция развернутых элементов.
     */
    public <C extends Collection<P>> C unwrapAndFilter(Predicate<P> predicate, Supplier<C> supplier) {
        return models.stream()
                .map(IModel::getObject)
                .filter(predicate)
                .collect(Collectors.toCollection(supplier));
    }
}
