package com.atlassian.stash.internal.hibernate;

import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageProvider;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.bitbucket.util.PageUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;

import java.util.List;
import java.util.function.Predicate;

/**
 * Utility methods for constructing {@link Page pages} from Hibernate using {@code Criteria} and {@code Query}.
 * For simplicity, this utility class extends {@link PageUtils} and pulls in all of its utilities as well.
 * @since 2.4
 */
public class HibernatePageUtils extends PageUtils {
    
    private HibernatePageUtils() {
        //Superclass constructor throws
    }

    public static <T> Page<T> pageCriteria(Criteria criteria, PageRequest request, Predicate<? super T> predicate) {
        return filterPages(new CriteriaPageProvider<T>(criteria), predicate, request);
    }

    public static <T> Page<T> pageCriteria(Criteria criteria, PageRequest pageRequest) {
        @SuppressWarnings("unchecked")
        List<T> values = criteria.setFirstResult(pageRequest.getStart())
                .setMaxResults(pageRequest.getLimit() + 1)
                .list();

        return createPage(values, pageRequest);
    }

    public static <T> Page<T> pageQuery(Query query, PageRequest request, Predicate<? super T> predicate) {
        return filterPages(new QueryPageProvider<T>(query), predicate, request);
    }

    public static <T> Page<T> pageQuery(Query query, PageRequest pageRequest) {
        @SuppressWarnings("unchecked")
        List<T> values = query.setFirstResult(pageRequest.getStart())
                .setMaxResults(pageRequest.getLimit() + 1)
                .list();

        return createPage(values, pageRequest);
    }

    /**
     * {@link PageProvider} implementation for wrapping a Hibernate {@code Criteria} for pagination. This class is
     * implemented in terms of {@link #pageCriteria(org.hibernate.Criteria, PageRequest)}.
     * <p>
     * Note: This implementation is protected to allow it to be used in derived DAOs, but it is not intended to be a
     * base class for other implementations. Hence, it is declared final.
     */
    private static class CriteriaPageProvider<T> implements PageProvider<T> {

        private final Criteria criteria;

        public CriteriaPageProvider(Criteria criteria) {
            this.criteria = criteria;
        }

        @Override
        public Page<T> get(PageRequest request) {
            return pageCriteria(criteria, request);
        }
    }

    /**
     * {@link PageProvider} implementation for wrapping a Hibernate {@code Query} for pagination. This class is
     * implemented in terms of {@link #pageQuery(org.hibernate.Query, PageRequest)}.
     * <p>
     * Note: This implementation is protected to allow it to be used in derived DAOs, but it is not intended to be a
     * base class for other implementations. Hence, it is declared final.
     */
    private static class QueryPageProvider<T> implements PageProvider<T> {

        private final Query query;

        public QueryPageProvider(Query query) {
            this.query = query;
        }

        @Override
        public Page<T> get(PageRequest request) {
            return pageQuery(query, request);
        }
    }
}
