/**
 * Copyright (c) 2015 Development Gateway, Inc and others.
 * <p>
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the MIT License (MIT)
 * which accompanies this distribution, and is available at
 * https://opensource.org/licenses/MIT
 * <p>
 * Contributors:
 * Development Gateway - initial API and implementation
 */
/**
 *
 */
package org.devgateway.toolkit.forms.wicket.providers;

import org.apache.wicket.model.IModel;
import org.devgateway.toolkit.forms.WebConstants;
import org.devgateway.toolkit.persistence.dao.GenericPersistable;
import org.devgateway.toolkit.persistence.dao.Labelable;
import org.devgateway.toolkit.persistence.service.TextSearchableService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.wicketstuff.select2.ChoiceProvider;
import org.wicketstuff.select2.Response;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
 * @author mpostelnicu
 */
public class GenericPersistableJpaTextChoiceProvider<T extends GenericPersistable & Labelable & Serializable>
        extends ChoiceProvider<T> {
    private static final long serialVersionUID = -643286578944834690L;

    private static final Logger logger = LoggerFactory.getLogger(GenericPersistableJpaTextChoiceProvider.class);

    private T newObject;

    private Sort sort;

    private Boolean addNewElements = false;

    private Class<T> clazz;

    private IModel<Collection<T>> restrictedToItemsModel;

    private final TextSearchableService<T> textSearchableService;

    public GenericPersistableJpaTextChoiceProvider(final TextSearchableService<T> textSearchableService) {
        this.textSearchableService = textSearchableService;
    }

    public GenericPersistableJpaTextChoiceProvider(final TextSearchableService<T> textSearchableService,
                                                   final Class<T> clazz,
                                                   final Boolean addNewElements) {
        this.textSearchableService = textSearchableService;
        this.clazz = clazz;
        this.addNewElements = addNewElements;
    }

    public GenericPersistableJpaTextChoiceProvider(final TextSearchableService<T> textSearchableService,
                                                   final IModel<Collection<T>> restrictedToItemsModel) {
        this(textSearchableService);
        this.restrictedToItemsModel = restrictedToItemsModel;
    }

    @Override
    public String getIdValue(final T choice) {
        // if the object is not null but it hasn't an ID return 0
        if (choice != null && choice.getId() == null && addNewElements) {
            return "0";
        }

        return choice.getId().toString();
    }

    @Override
    public void query(final String term, final int page, final Response<T> response) {
        Page<T> itemsByTerm;
        if (term == null || term.isEmpty()) {
            itemsByTerm = findAll(page);
            response.setHasMore(itemsByTerm.hasNext());
        } else {
            itemsByTerm = getItemsByTerm(term.toLowerCase(), page);
        }

        if (itemsByTerm != null) {
            if (itemsByTerm.getContent().size() == 0 && addNewElements) {
                // add new element dynamically
                // the new element should extend Category so that we can attache
                // a 'label' to it
                try {
                    newObject = clazz.newInstance();
                    newObject.setLabel(term);
                } catch (InstantiationException e) {
                    logger.error("Error creating a new Item", e);
                } catch (IllegalAccessException e) {
                    logger.error("Error creating a new Item", e);
                }

                List<T> newElementsList = new ArrayList<>();
                newElementsList.add(newObject);

                response.addAll(newElementsList);
            } else {
                response.setHasMore(itemsByTerm.hasNext());
                response.addAll(itemsByTerm.getContent());
            }
        }
    }

    private Page<T> getItemsByTerm(final String term, final int page) {
        final PageRequest pageRequest = sort == null
                ? PageRequest.of(page, WebConstants.SELECT_PAGE_SIZE)
                : PageRequest.of(page, WebConstants.SELECT_PAGE_SIZE, sort);
        return textSearchableService.searchText(term, pageRequest);
    }

    private Page<T> findAll(final int page) {
        final PageRequest pageRequest = sort == null
                ? PageRequest.of(page, WebConstants.SELECT_PAGE_SIZE)
                : PageRequest.of(page, WebConstants.SELECT_PAGE_SIZE, sort);
        return textSearchableService.findAll(pageRequest);
    }

    @Override
    public Collection<T> toChoices(final Collection<String> ids) {
        ArrayList<String> idsList = new ArrayList<>();

        for (String id : ids) {
            // create new element
            if (Long.parseLong(id) == 0 && addNewElements) {
                if (newObject != null && newObject.getId() == null) {
                    textSearchableService.save(newObject);
                }

                id = newObject.getId().toString();
            }

            idsList.add(id);
        }

        ArrayList<T> response = new ArrayList<>();
        for (String s : idsList) {
            Long id = Long.parseLong(s);
            Optional<T> findOne = textSearchableService.findById(id);
            if (!findOne.isPresent()) {
                logger.error("Cannot find entity with id=" + id + " in service "
                        + textSearchableService.getClass());
            } else {
                response.add(findOne.get());
            }
        }
        return response;
    }

    @Override
    public String getDisplayValue(final T choice) {
        if (addNewElements && choice.getId() == null) {
            return choice.toString() + " ---> (press enter to create new element)";
        }

        return choice.toString();
    }
}

@@ -1152,16 +1152,74 @@
 ptional;
+%0Aimport java.util.Set;%0Aimport java.util.stream.Collectors;
 %0A%0A/**%0A *
@@ -1821,24 +1821,60 @@
 leService;%0A%0A
+    private Set%3CLong%3E excludedIds;%0A%0A
     public G
@@ -2745,32 +2745,216 @@
 msModel;%0A    %7D%0A%0A
+    public Set%3CLong%3E getExcludedIds() %7B%0A        return excludedIds;%0A    %7D%0A%0A    public void setExcludedIds(final Set%3CLong%3E excludedIds) %7B%0A        this.excludedIds = excludedIds;%0A    %7D%0A%0A
     @Override%0A  
@@ -4514,16 +4514,37 @@
 .addAll(
+filterByRestrictions(
 itemsByT
@@ -4556,24 +4556,25 @@
 etContent())
+)
 ;%0A          
@@ -4586,32 +4586,315 @@
        %7D%0A    %7D%0A%0A
+    private List%3CT%3E filterByRestrictions(final List%3CT%3E elements) %7B%0A        if (excludedIds != null && !excludedIds.isEmpty()) %7B%0A            return elements.stream().filter(e -%3E !excludedIds.contains(e.getId())).collect(Collectors.toList());%0A        %7D%0A        return elements;%0A    %7D%0A%0A
     private Page

allow to exclude persistable choice options by ids