package ru.yandex.webmaster3.storage;

import com.google.common.collect.Sets;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.storage.util.clickhouse2.condition.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Created by ifilippov5 on 31.03.17.
 */
public class TextFilterUtil {
    public static final String DELIMITER_OF_DIFFERENT_FILTERS = "\n";
    private static final char CONTAINS_SYMBOL = '@';
    private static final char MATCH_SYMBOL = '~';
    private static final char NOT_SYMBOL = '!';
    private static final char WILDCARD_SYMBOL = '*';
    private static final char ESCAPE_SYMBOL = '\\';

    private static final Set<Character> SPECIAL_SYMBOLS = Sets.newHashSet(CONTAINS_SYMBOL,
            MATCH_SYMBOL, NOT_SYMBOL, WILDCARD_SYMBOL, ESCAPE_SYMBOL);

    public static Condition getTextCondition(AbstractFilter filter, String field) {
        switch (filter.getOperation()) {
            case EQUAL:
                return new TextCondition(field, Operator.EQUAL, filter.getValue());
            case TEXT_CONTAINS:
                return new TextCondition(field, Operator.TEXT_CONTAINS, filter.getValue());
            case TEXT_MATCH:
                return parseTextMatchCondition(filter, field);
            default:
                throw invalidFilterException(filter);
        }
    }

    private static Condition parseTextMatchCondition(AbstractFilter compositeFilter, String field) {
        if (compositeFilter.getOperation() != AbstractFilter.Operation.TEXT_MATCH)
            throw new IllegalArgumentException("Operation not equal TEXT_MATCH");

        String query = compositeFilter.getValue();
        if (query == null) {
            return Condition.trueCondition();
        }
        String[] filters = query.split(DELIMITER_OF_DIFFERENT_FILTERS);
        if (filters.length == 0) {
            return Condition.trueCondition();
        }
        List<Condition> conditions = new ArrayList<>();
        for (String filter : filters) {
            conditions.add(parseCondition(field, filter));
        }
        return new BoolOpCondition(compositeFilter.getLogicalOperator(), conditions);
    }

    public static Condition parseCondition(String field, String text) {
        text = text.trim(); // начальные и конечные пробелы в фильтре не учитываются
        if (text.isEmpty()) {
            return Condition.trueCondition();
        }
        if (text.charAt(0) == NOT_SYMBOL) {
            Condition nonNegative = parseNonNegativeCondition(field, text.substring(1));
            if (nonNegative != null) {
                return new NotCondition(nonNegative);
            } else {
                return Condition.trueCondition();
            }
        } else {
            Condition nonNegative = parseNonNegativeCondition(field, text);
            if (nonNegative != null) {
                return nonNegative;
            } else {
                return Condition.trueCondition();
            }
        }
    }

    private static Condition parseNonNegativeCondition(String field, String text) {
        if (text.isEmpty()) {
            return null;
        }
        // пробелы после ! не убираем
        // если сначала идут пробелы, а потом '@' или '~', то не считаем их управляющими символами
        char first = text.charAt(0);
        switch (first) {
            case CONTAINS_SYMBOL: //является ли подстрокой == positionCaseInsensitive
                return new TextPositionCondition(field, unescapeSpecialSymbols(text.substring(1)));
            case MATCH_SYMBOL: //режим регулярного выражения == match
                return new TextMatchRegularCondition(field, text.substring(1));
            default: //простая регулярка с возможностью поиска по * == like
                TextLikeCondition.Builder condBuilder = TextLikeCondition.newBuilder();
                int i = 0;
                while (i < text.length()) {
                    char ch = text.charAt(i++);
                    if (ch == ESCAPE_SYMBOL && i < text.length()) {
                        char nextCh = text.charAt(i++);
                        if (SPECIAL_SYMBOLS.contains(nextCh)) {
                            // если после обратного слэша - специальный символ - добавляем его нормальным образом
                            condBuilder.append(String.valueOf(nextCh));
                        } else {
                            condBuilder.append(new String(new char[]{ch, nextCh}));
                        }
                    } else if (ch == WILDCARD_SYMBOL) {
                        condBuilder.appendWildcard();
                    } else {
                        condBuilder.append(String.valueOf(ch));
                    }
                }
                return condBuilder.build(field);
        }
    }

    protected static String unescapeSpecialSymbols(String source) {
        int srcIndex = 0;
        int dstIndex = 0;
        char[] destination = new char[source.length()];
        int srcLength = source.length();
        while (srcIndex < srcLength) {
            char c = source.charAt(srcIndex++);
            destination[dstIndex++] = c;
            if (c == ESCAPE_SYMBOL && srcIndex < srcLength) {
                c = source.charAt(srcIndex++);
                if (SPECIAL_SYMBOLS.contains(c)) {
                    dstIndex--;
                }
                destination[dstIndex++] = c;
            }
        }
        return new String(destination, 0, dstIndex);
    }

    private static WebmasterException invalidFilterException(AbstractFilter<?> filter) {
        String message = "Bad filter: cannot apply operation " + filter.getOperation() + " to indicator " + filter.getIndicator() + " and value " + filter.getValue();
        return new WebmasterException(message,
                new WebmasterErrorResponse.IllegalParameterValueResponse(TextFilterUtil.class, null, null, message));
    }
}
