package cn.axzo.msg.center.message.service.todo.queryanalyze;

import cn.axzo.maokai.api.util.Ref;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLDoubleExpr;
import com.alibaba.druid.sql.ast.expr.SQLFloatExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLInListExpr;
import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr;
import com.alibaba.druid.sql.ast.expr.SQLNullExpr;
import com.alibaba.druid.sql.ast.expr.SQLNumberExpr;
import com.alibaba.druid.sql.ast.expr.SQLSmallIntExpr;
import com.alibaba.druid.sql.visitor.SQLASTVisitor;
import com.alibaba.druid.sql.visitor.SQLEvalVisitorUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;

import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;

import static java.util.stream.Collectors.toSet;

/**
 * @author yanglin
 */
@Slf4j
@RequiredArgsConstructor
class EvalValueVisitor implements SQLASTVisitor {

    private static final String VALUE = "value";

    private final Record record;

    // !! eval

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public boolean visit(SQLBinaryOpExpr x) {
        x.getLeft().accept(this);
        Object leftValue = x.getLeft().getAttribute(VALUE);
        Ref<Boolean> visitRight = Ref.create(true);
        Supplier<Object> rightValue = () -> {
            x.getRight().accept(this);
            visitRight.set(false);
            return x.getRight().getAttribute(VALUE);
        };
        SQLBinaryOperator operator = x.getOperator();
        Object value = false;
        if (operator == SQLBinaryOperator.Equality)
            value = Objects.equals(leftValue, rightValue.get());
        else if (operator == SQLBinaryOperator.NotEqual
                || operator == SQLBinaryOperator.LessThanOrGreater)
            value = !Objects.equals(leftValue, rightValue.get());
        else if (operator == SQLBinaryOperator.BooleanOr) {
            if ((boolean) leftValue)
                visitRight.set(false);
            value = (boolean) leftValue || (boolean) rightValue.get();
        } else if (operator == SQLBinaryOperator.BooleanAnd)
            value = (boolean) leftValue && (boolean) rightValue.get();
        else if (operator == SQLBinaryOperator.LessThan)
            value = ((Comparable) leftValue).compareTo(rightValue.get()) < 0;
        else if (operator == SQLBinaryOperator.LessThanOrEqual)
            value = ((Comparable) leftValue).compareTo(rightValue.get()) <= 0;
        else if (operator == SQLBinaryOperator.GreaterThan)
            value = ((Comparable) leftValue).compareTo(rightValue.get()) > 0;
        else if (operator == SQLBinaryOperator.GreaterThanOrEqual)
            value = ((Comparable) leftValue).compareTo(rightValue.get()) >= 0;
        else if (operator == SQLBinaryOperator.Like)
            value = SQLEvalVisitorUtils.like((String) leftValue, (String) rightValue.get());
         else if (operator == SQLBinaryOperator.NotLike)
            value = !SQLEvalVisitorUtils.like((String) leftValue, (String) rightValue.get());
        else if (x.getRight() instanceof SQLNullExpr) {
            if (operator == SQLBinaryOperator.Is)
                value = leftValue == null;
            else if (operator == SQLBinaryOperator.IsNot)
                value = leftValue != null;
        } else {
            log.warn("Unsupported bin expr: {}", SQLUtils.toMySqlString(x));
            // make it false to expose the issue
            value = false;
        }
        x.putAttribute(VALUE, value);
        return visitRight.get();
    }

    @Override
    public void endVisit(SQLInListExpr x) {
        List<SQLExpr> targetList = x.getTargetList();
        if (CollectionUtils.isEmpty(targetList)) {
            x.putAttribute(VALUE, false);
            return;
        }
        String column = findColumn(x.getExpr());
        Object value = column != null
                ? record.getValue(column, EvalTypeVisitor.getType(x))
                : x.getExpr().getAttribute(VALUE);
        Set<Object> listValues = targetList.stream()
                .map(t -> t.getAttribute(VALUE))
                .filter(Objects::nonNull)
                .collect(toSet());
        x.putAttribute(VALUE, listValues.contains(value));
    }

    // !! simple

    @Override
    public void endVisit(SQLNumberExpr x) {
        x.putAttribute(VALUE, x.getValue());
    }

    @Override
    public void endVisit(SQLSmallIntExpr x) {
        x.putAttribute(VALUE, x.getValue());
    }

    @Override
    public void endVisit(SQLIntegerExpr x) {
        x.putAttribute(VALUE, x.getValue());
    }

    @Override
    public void endVisit(SQLDoubleExpr x) {
        x.putAttribute(VALUE, x.getValue());
    }

    @Override
    public void endVisit(SQLFloatExpr x) {
        x.putAttribute(VALUE, x.getValue());
    }

    @Override
    public void endVisit(SQLCharExpr x) {
        Date date = EvalTypeVisitor.asDate(x);
        if (date != null)
            x.putAttribute(VALUE, date);
        else
            x.putAttribute(VALUE, x.getValue());
    }

    @Override
    public void endVisit(SQLIdentifierExpr x) {
        String column = findColumn(x);
        Object value = record.getValue(column, EvalTypeVisitor.getType(x.getParent()));
        x.putAttribute(VALUE, value);
    }

    static String findColumn(SQLExpr x) {
        if (!(x instanceof SQLIdentifierExpr))
            return null;
        return ((SQLIdentifierExpr) x).getName();
    }

    static Object getValue(SQLObject x) {
        return x.getAttribute(VALUE);
    }

}