package ru.yandex.webmaster3.storage.util.clickhouse2.condition;

import com.google.common.base.Preconditions;
import com.google.re2j.Pattern;
import ru.yandex.webmaster3.storage.util.clickhouse2.ClickhouseEscapeUtils;

import java.util.Set;
import java.util.function.Predicate;

/**
 * Created by Oleg Bazdyrev on 02/05/2017.
 */
public abstract class AbstractCondition<T extends Comparable<T>> extends Condition {
    private final Class<T> clazz;
    protected final Operator operator;
    protected final String fieldName;
    protected final T value;
    protected Pattern patternCache;

    public AbstractCondition(ConditionType type, Class<T> clazz, String fieldName, Operator operator, T value) {
        super(type);
        this.clazz = clazz;
        Preconditions.checkArgument(getSupportedOperators().contains(operator));
        this.operator = operator;
        this.fieldName = fieldName;
        this.value = value;
    }

    public Operator getOperator() {
        return operator;
    }

    public String getFieldName() {
        return fieldName;
    }

    public T getValue() {
        return value;
    }

    protected abstract Set<Operator> getSupportedOperators();

    protected String valueAsString() {
        if (value instanceof String) {
            return "'" + ClickhouseEscapeUtils.escape(value.toString(), false) + "'";
        }
        return value.toString();
    }

    @Override
    public String toQuery() {
        switch (operator) {
            case GREATER_EQUAL:
                return fieldName + " >= " + valueAsString();
            case GREATER_THAN:
                return fieldName + " > " + valueAsString();
            case LESS_EQUAL:
                return fieldName + " <= " + valueAsString();
            case LESS_THAN:
                return fieldName + " < " + valueAsString();
            case EQUAL:
                return fieldName + " = " + valueAsString();
            case TEXT_LIKE:
                return fieldName + " LIKE " + valueAsString();
            case TEXT_CONTAINS:
                return "positionCaseInsensitive(" + fieldName + "," + valueAsString() + ") > 0";
            case TEXT_MATCH:
                return "match(" + fieldName + "," + valueAsString() + ")";
        }
        throw new IllegalStateException();
    }

    public Predicate<T> toPredicate() {
        return toPredicate(ConditionFieldExtractor.identity(clazz));
    }

    @Override
    public <O> Predicate<O> toPredicate(ConditionFieldExtractor<O> fieldExtractor) {
        switch (operator) {
            case GREATER_EQUAL:
                return x -> fieldExtractor.extract(x, fieldName, clazz).compareTo(value) >= 0;
            case GREATER_THAN:
                return x -> fieldExtractor.extract(x, fieldName, clazz).compareTo(value) > 0;
            case LESS_EQUAL:
                return x -> fieldExtractor.extract(x, fieldName, clazz).compareTo(value) <= 0;
            case LESS_THAN:
                return x -> fieldExtractor.extract(x, fieldName, clazz).compareTo(value) < 0;
            case EQUAL:
                return x -> fieldExtractor.extract(x, fieldName, clazz).equals(value);
            case TEXT_LIKE:
                if (patternCache == null) {
                    patternCache = Pattern.compile("^" + Pattern.quote(value.toString()).replace("%", ".*") + "$");
                }
                return x -> patternCache.matcher(fieldExtractor.extract(x, fieldName, String.class)).find();
            case TEXT_CONTAINS:
                return x -> fieldExtractor.extract(x, fieldName, String.class).contains(value.toString());
            case TEXT_MATCH:
                if (patternCache == null) {
                    patternCache = Pattern.compile(value.toString());
                }
                return x -> patternCache.matcher(fieldExtractor.extract(x, fieldName, String.class)).find();
        }
        throw new IllegalStateException();
    }

}
