package com.infoclinika.pdx.specification;

import com.infoclinika.pdx.domain.sample.SampleType;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.Arrays;

import static com.infoclinika.pdx.specification.SpecificationUtils.getPath;

/**
 * Specification for Entity.
 *
 * @param <T> - type of Entity
 */
public class EntitySpecification<T> implements Specification<T> {
    private static final long serialVersionUID = 6679508259298100906L;
    private static final String DELIMITER = ",";
    private static final String NULL_VALUE = "null";
    private static final String LIKE_SYMBOL = "%";

    private final SearchCriteria criteria;

    /**
     * Creates the specification using {@link SearchCriteria}.
     */
    public EntitySpecification(SearchCriteria criteria) {
        this.criteria = criteria;
    }

    @SuppressWarnings("ReturnOfNull")
    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        final String criteriaValue = criteria.getValue();
        final String criteriaKey = criteria.getKey();
        if (criteria.getOperation().equalsIgnoreCase(">")) {
            return builder.greaterThanOrEqualTo(root.get(criteriaKey), criteriaValue);
        }

        if (criteria.getOperation().equalsIgnoreCase("<")) {
            final Path<Object> path = getPath(root, criteriaKey);
            return builder.lessThan(path.as(String.class), criteriaValue);
        }

        if (criteria.getOperation().equalsIgnoreCase(":")) {
            final Path<Object> path = getPath(root, criteriaKey);
            return criteriaValue.equals(NULL_VALUE)
                   ? builder.isNull(path)
                   : path.in(getIterableValue(path, criteriaValue));
        }

        if (criteria.getOperation().equalsIgnoreCase("!")) {
            final Path<Object> path = getPath(root, criteriaKey);
            return criteriaValue.equals(NULL_VALUE)
                   ? builder.isNotNull(path)
                   : builder.not(path.in(getIterableValue(path, criteriaValue)));
        }

        if (criteria.getOperation().equalsIgnoreCase("%")) {
            final Path<Object> path = getPath(root, criteria.getKey());
            final String searchValue = LIKE_SYMBOL + criteriaValue.toUpperCase() + LIKE_SYMBOL;
            return builder.like(builder.upper(path.as(String.class)), searchValue);
        }

        return null;
    }

    private static Object[] getIterableValue(Path<Object> path, String value) {
        final String[] values = value.split(DELIMITER);
        final boolean isSampleType = path.getModel()
                                         .getBindableJavaType()
                                         .isAssignableFrom(SampleType.class);
        return isSampleType
               ? Arrays.stream(values)
                       .map(SampleType::valueOf)
                       .toArray()
               : values;
    }
}
