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

import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;

import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;

import ru.yandex.webmaster3.core.util.W3CollectionUtils;
import ru.yandex.webmaster3.storage.util.clickhouse2.condition.TextLikeCondition;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Case;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Eq;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Gt;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Gte;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.In;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.InSubquery;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Like;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Lt;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Lte;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Match;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Ne;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Not;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.NotIn;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.RegionIn;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.TripleIn;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.True;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.TupleIn;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.cases.Value;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.Any;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.ArgMax;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.BitAnd;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.Condition;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.Count;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.Divide;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.Max;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.Min;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.SafeDivide;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.Sum;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.ToFloat32;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.ToRelativeDayNum;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.ToRelativeMonthNum;
import ru.yandex.webmaster3.storage.util.clickhouse2.query.methods.ToRelativeWeekNum;

/**
 * @author tsyplyaev
 */
public class QueryBuilder {
    public static Select select(String... fields) {
        return new Select(fields);
    }

    public static Select select(Collection<String> fields) {
        return new Select(fields.toArray(new String[fields.size()]));
    }

    public static SelectDistinct selectDistinct(String... fields) {
        return new SelectDistinct(fields);
    }

    public static InsertInto insertInto(String db, String table) {
        return new InsertInto(db, table);
    }

    public static CaseWrapper case_(Case case_) {
        return new CaseWrapper(case_);
    }

    public static Scopes scopes(Statement inner) {
        return new Scopes(inner);
    }

    public static Not not(Case case_) {
        return new Not(case_);
    }

    public static Eq eq(String field, Object value) {
        return new Eq(field, value);
    }

    public static Ne ne(String field, Object value) {
        return new Ne(field, value);
    }

    /**
     * Пришлось поменять имя, потому что иначе .in("f", someCollection)
     * приводит к передаче коллекции первым аргументом vararg'а и увлекательному дебагу
     */
    public static In inVarArg(String field, Object... values) {
        return new In(field, Arrays.asList(values));
    }

    public static In in(String field, Collection<?> values) {
        return new In(field, values);
    }

    public static <T1, T2> TupleIn tupleIn(String field1, String field2, Collection<Pair<T1, T2>> values) {
        return new TupleIn(field1, field2, values
                .stream()
                .map(pair -> Pair.of(new Value(pair.getLeft()), new Value(pair.getRight())))
                .collect(Collectors.toList()));
    }

    public static <T1, T2, T3> TripleIn tripleIn(String field1, String field2, String field3, Collection<Triple<T1, T2, T3>> values) {
        return new TripleIn(field1, field2, field3, values
                .stream()
                .map(pair -> Triple.of(new Value(pair.getLeft()), new Value(pair.getMiddle()), new Value(pair.getRight())))
                .collect(Collectors.toList()));
    }

    public static InSubquery inSubquery(Statement subquery, String f1, String... fields) {
        return new InSubquery(subquery.toString(), W3CollectionUtils.varargToList(f1, fields));
    }

    public static Gt gt(String field, Object value) {
        return new Gt(field, value);
    }

    public static Gte gte(String field, Object value) {
        return new Gte(field, value);
    }

    public static Lt lt(String field, Object value) {
        return new Lt(field, value);
    }

    public static Lte lte(String field, Object value) {
        return new Lte(field, value);
    }

    public static Like startsWith(String field, String value) {
        //return new Like(field, ClickhouseEscapeUtils.escapeString(value) + "%%");
        TextLikeCondition.Builder builder = TextLikeCondition.newBuilder();
        builder.append(value);
        builder.appendWildcard();
        return new Like(field, builder.toString());
    }

    public static Like containsSubstring(String field, String value) {
        //return new Like(field, "%%" + ClickhouseEscapeUtils.escapeString(value) + "%%");
        TextLikeCondition.Builder builder = TextLikeCondition.newBuilder();
        builder.appendWildcard();
        builder.append(value);
        builder.appendWildcard();
        return new Like(field, builder.toString());
    }

    public static Clause applyAndCase(Statement st, Case case_) {
        if (st instanceof From) {
            return ((From) st).where(case_);
        } else if (st instanceof Clause) {
            return ((Clause) st).and(case_);
        }
        return null;
    }

    public static NotIn notIn(String field, Collection values) {
        return new NotIn(field, values);
    }

    public static As as(Object exp, String name) {
        return new As(exp, name);
    }

    public static RegionIn regionIn(Object child, Object parent) {
        return new RegionIn(child, parent);
    }

    //ops
    public static Divide divide(Object a, Object b) {
        return new Divide(a, b);
    }

    public static SafeDivide safeDivide(Object a, Object b, Object ifZero) {
        return new SafeDivide(a, b, ifZero);
    }

    public static Condition condition(Case case_, Object a, Object b) {
        return new Condition(case_, a, b);
    }

    //methods
    public static Sum sum(Object field) {
        return new Sum(field);
    }

    public static Min min(Object field) {
        return new Min(field);
    }

    public static Max max(Object field) {
        return new Max(field);
    }

    public static Any any(Object field) {
        return new Any(field);
    }

    public static Count count() {
        return new Count();
    }

    public static ToRelativeDayNum toRelativeDayNum(Object field) {
        return new ToRelativeDayNum(field);
    }

    public static ToRelativeWeekNum toRelativeWeekNum(Object field) {
        return new ToRelativeWeekNum(field);
    }

    public static ToRelativeMonthNum toRelativeMonthNum(Object field) {
        return new ToRelativeMonthNum(field);
    }

    public static ToFloat32 toFloat32(Object v) {
        return new ToFloat32(v);
    }

    public static ArgMax argMax(Object arg, Object value, String resultName) {
        return new ArgMax(arg, value, resultName);
    }

    public static ArgMax argMax(Object arg, Object value) {
        return new ArgMax(arg, value);
    }

    public static BitAnd bitAnd(Object arg, Object value) {
        return new BitAnd(arg, value);
    }

    public static True trueCondition() {
        return new True();
    }

    public static Match match(String arg, String value) {
        return new Match(arg, value);
    }
}
