package com.eactive.httpmockserver.common.util;

import org.hibernate.query.criteria.internal.path.PluralAttributePath;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;
import java.util.Collection;

public class SpecificationUtil {


    private SpecificationUtil() {
        throw new IllegalStateException("Utility class");
    }

    public static <T> Path getPath(Root<T> root, String column) {

        Path p = root;

        if (column.contains(".")) {
            String[] columns = column.split("\\.");

            for (String s : columns) {
                Path<Object> c = p.get(s);

                if (c instanceof PluralAttributePath) {
                    p = root.joinList(s);
                } else {
                    p = p.get(s);
                }
            }
            return p;
        } else {
            return root.get(column);
        }

    }

    public static <T> Specification<T> notNull(String column) {
        return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.isNotNull(root.get(column));
    }

    public static <T> Specification<T> isNull(String column) {
        return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.isNull(root.get(column));
    }


    public static <T> Specification<T> equalTo(String column, Object value) {

        return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(getPath(root, column), value);
    }

    public static <T> Specification<T> equalToJoin(String table, String column, Object value) {

        return (root, criteriaQuery, criteriaBuilder) -> {
            Join join = root.join(table, JoinType.INNER);
            return criteriaBuilder.equal(join.get(column), value);
        };
    }

    public static <T> Specification<T> equalToLeftJoin(String table, String column, Object value) {

        return (root, criteriaQuery, criteriaBuilder) -> {
            Join join = root.join(table, JoinType.LEFT);
            return criteriaBuilder.equal(join.get(column), value);
        };
    }

    public static <T> Specification<T> notEqualTo(String column, Object value) {
        return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.notEqual(getPath(root, column), value);
    }

    public static <T> Specification<T> like(String column, String value) {
        return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.like(criteriaBuilder.lower(getPath(root, column)), "%" + value.toLowerCase() + "%");
    }

    public static <T> Specification<T> in(String column, Collection<Object> value) {
        return (root, criteriaQuery, criteriaBuilder) -> {
            CriteriaBuilder.In<Object> in = criteriaBuilder.in(root.get(column));
            value.forEach(in::value);
            return in;
        };
    }

    public static <T> Specification<T> greater(String column, Object value) {
        return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.greaterThan(getPath(root, column), (Comparable) value);
    }

    public static <T> Specification<T> greaterAndEqual(String column, Object value) {
        return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(getPath(root, column), (Comparable) value);
    }

    public static <T> Specification<T> less(String column, Object value) {
        return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.lessThan(getPath(root, column), (Comparable) value);
    }

    public static <T> Specification<T> lessAndEqual(String column, Object value) {
        return (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(getPath(root, column), (Comparable) value);
    }

}
