/*******************************************************************************
 * Copyright (c) 2014 antoniomariasanchez at gmail.com.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 * 
 * Contributors:
 *     antoniomaria - initial API and implementation
 ******************************************************************************/
/*
 * (c) Copyright 2005-2012 JAXIO, www.jaxio.com Source code generated by Celerio, a Jaxio product Want to use Celerio
 * within your company? email us at [email protected] Follow us on twitter: @springfuse Template
 * pack-backend-sd:src/main/java/project/repository/support/RangeSpecification.p.vm.java
 */
package net.sf.gazpachoquest.qbe;

import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang3.Validate;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.domain.Specifications;

/**
 * Helper to create {@link Specification} out of {@link Range}s.
 */
@SuppressWarnings("unchecked")
public class RangeSpecification {
    
    public static <E,D extends Comparable<? super D>> Specifications<E> andRangeIfSet(Specifications<E> specifications, final List<Range<?, ?>> ranges) {
        for (Range<?, ?> r : ranges) {
            if (r.isSet()) {
                Range<E, D> range = (Range<E, D>) r;
                specifications = specifications.and(toSpecification(range));
            }
        }
        return specifications;
    }

    public static <E, D extends Comparable<? super D>> Specification<E> toSpecification(final Range<E, D> range) {
        Validate.isTrue(range.isSet(), "You must pass an exploitable range");
        return new Specification<E>() {
            @Override
            public Predicate toPredicate(final Root<E> root, final CriteriaQuery<?> query, final CriteriaBuilder builder) {
                Predicate rangePredicate = null;

                if (range.isBetween()) {
                    rangePredicate = builder.between(root.get(range.getField()), range.getFrom(), range.getTo());
                } else if (range.isFromSet()) {
                    // rangePredicate =
                    // builder.greaterThanOrEqualTo(root.get(range.getField()),
                    // range.getFrom());
                    rangePredicate = builder.greaterThan(root.get(range.getField()), range.getFrom());
                } else if (range.isToSet()) {
                    // rangePredicate =
                    // builder.lessThanOrEqualTo(root.get(range.getField()),
                    // range.getTo());
                    rangePredicate = builder.lessThan(root.get(range.getField()), range.getTo());
                }

                if (rangePredicate != null) {
                    if (!range.isIncludeNullSet() || Boolean.FALSE.equals(range.getIncludeNull())) {
                        return rangePredicate;
                    } else {
                        return builder.or(rangePredicate, builder.isNull(root.get(range.getField())));
                    }
                }

                // no range at all
                // take the opportunity to keep only null...
                if (Boolean.TRUE.equals(range.getIncludeNull())) {
                    return builder.isNull(root.get(range.getField()));
                }

                // ... or non-null only...
                if (Boolean.FALSE.equals(range.getIncludeNull())) {
                    return builder.isNotNull(root.get(range.getField()));
                }

                throw new IllegalStateException("You must pass an exploitable range (should not happen here)");
            }
        };
    }
    
}