package com.jy.framework.mybatis.join;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import org.apache.ibatis.reflection.property.PropertyNamer;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.StringJoiner;

public abstract class AbstractQueryColumn extends AbstractSql {

    /**
     * 计算列表
     */
    protected final List<Compute> computeList = new ArrayList<>();

    // ===========================================
    // 取别名
    // ===========================================

    public <T> QueryColumnAlias as(SFunction<T, ?> function) {
        LambdaMeta meta = LambdaUtils.extract(function);
        String alias = StrUtil.toUnderlineCase(PropertyNamer.methodToProperty(meta.getImplMethodName()));
        return new QueryColumnAlias(this, alias);
    }

    public QueryColumnAlias as(String alias) {
        return new QueryColumnAlias(this, alias);
    }

    // ===========================================
    // 算术运算
    // ===========================================

    public AbstractQueryColumn add(AbstractQueryColumn queryColumn) {
        return compute(SqlConstant.ADD, queryColumn);
    }

    public AbstractQueryColumn add(Number number) {
        return compute(SqlConstant.ADD, number);
    }

    public AbstractQueryColumn subtract(AbstractQueryColumn queryColumn) {
        return compute(SqlConstant.SUBTRACT, queryColumn);
    }

    public AbstractQueryColumn subtract(Number number) {
        return compute(SqlConstant.SUBTRACT, number);
    }

    public AbstractQueryColumn multiply(AbstractQueryColumn queryColumn) {
        return compute(SqlConstant.MULTIPLY, queryColumn);
    }

    public AbstractQueryColumn multiply(Number number) {
        return compute(SqlConstant.MULTIPLY, number);
    }

    public AbstractQueryColumn divide(AbstractQueryColumn queryColumn) {
        return compute(SqlConstant.DIVIDE, queryColumn);
    }

    public AbstractQueryColumn divide(Number number) {
        return compute(SqlConstant.DIVIDE, number);
    }

    public AbstractQueryColumn remainder(AbstractQueryColumn queryColumn) {
        return compute(SqlConstant.REMAINDER, queryColumn);
    }

    public AbstractQueryColumn remainder(Number number) {
        return compute(SqlConstant.REMAINDER, number);
    }

    /**
     * 复制当前对象
     */
    protected abstract AbstractQueryColumn copy();

    /**
     * 拼接计算列
     */
    protected String spliceCompute(String sql) {
        if (ObjectUtil.isEmpty(computeList)) {
            return sql;
        }

        StringJoiner joiner = new StringJoiner(StringPool.EMPTY, StringPool.LEFT_BRACKET, StringPool.RIGHT_BRACKET);
        joiner.add(sql);
        computeList.forEach(item -> joiner.add(item.format()));
        return joiner.toString();
    }

    private AbstractQueryColumn compute(String symbol, Object obj) {
        AbstractQueryColumn queryColumn = copy();
        queryColumn.computeList.addAll(this.computeList);
        queryColumn.computeList.add(new Compute(symbol, obj));
        return queryColumn;
    }

    // ===========================================
    // 比较运算
    // ===========================================

    public QueryCondition eq(Object obj) {
        return new QueryCondition(SqlConstant.EQ, this, obj);
    }

    public QueryCondition ne(Object obj) {
        return new QueryCondition(SqlConstant.NE, this, obj);
    }

    public QueryCondition gt(Object obj) {
        return new QueryCondition(SqlConstant.GT, this, obj);
    }

    public QueryCondition lt(Object obj) {
        return new QueryCondition(SqlConstant.LT, this, obj);
    }

    public QueryCondition ge(Object obj) {
        return new QueryCondition(SqlConstant.GE, this, obj);
    }

    public QueryCondition le(Object obj) {
        return new QueryCondition(SqlConstant.LE, this, obj);
    }

    public QueryCondition like(Object obj) {
        return new QueryCondition(SqlConstant.LIKE, this, obj);
    }

    public QueryCondition notLike(Object obj) {
        return new QueryCondition(SqlConstant.NOT_LIKE, this, obj);
    }

    public QueryCondition in(Collection<?> coll) {
        if (ArrayUtil.isEmpty(coll)) {
            return new QueryCondition(SqlConstant.IN, this, null);
        }
        return new QueryCondition(SqlConstant.IN, this, coll.toArray());
    }

    public QueryCondition in(Object... objs) {
        if (ArrayUtil.isEmpty(objs)) {
            return new QueryCondition(SqlConstant.IN, this, null);
        }
        return new QueryCondition(SqlConstant.IN, this, objs);
    }

    public QueryCondition notIn(Collection<?> coll) {
        if (ArrayUtil.isEmpty(coll)) {
            return new QueryCondition(SqlConstant.NOT_IN, this, null);
        }
        return new QueryCondition(SqlConstant.NOT_IN, this, coll.toArray());
    }

    public QueryCondition notIn(Object... objs) {
        if (ArrayUtil.isEmpty(objs)) {
            return new QueryCondition(SqlConstant.NOT_IN, this, null);
        }
        return new QueryCondition(SqlConstant.NOT_IN, this, objs);
    }

    public <T> QueryCondition between(T[] objs) {
        if (ObjectUtil.isEmpty(objs) || objs.length != 2) {
            return new QueryCondition(SqlConstant.BETWEEN, this, null);
        }
        return new QueryCondition(SqlConstant.BETWEEN, this, objs);
    }

    public <T> QueryCondition between(List<T> list) {
        if (ObjectUtil.isEmpty(list) || list.size() != 2) {
            return new QueryCondition(SqlConstant.BETWEEN, this, null);
        }
        return new QueryCondition(SqlConstant.BETWEEN, this, new Object[]{list.get(0), list.get(1)});
    }

    public QueryCondition between(Object obj1, Object obj2) {
        if (ObjectUtil.isEmpty(obj1) || ObjectUtil.isEmpty(obj2)) {
            return new QueryCondition(SqlConstant.BETWEEN, this, null);
        }
        return new QueryCondition(SqlConstant.BETWEEN, this, new Object[]{obj1, obj2});
    }

    public <T> QueryCondition notBetween(T[] objs) {
        if (ObjectUtil.isEmpty(objs) || objs.length != 2) {
            return new QueryCondition(SqlConstant.NOT_BETWEEN, this, null);
        }
        return new QueryCondition(SqlConstant.NOT_BETWEEN, this, objs);
    }

    public <T> QueryCondition notBetween(List<T> list) {
        if (ObjectUtil.isEmpty(list) || list.size() != 2) {
            return new QueryCondition(SqlConstant.NOT_BETWEEN, this, null);
        }
        return new QueryCondition(SqlConstant.NOT_BETWEEN, this, new Object[]{list.get(0), list.get(1)});
    }

    public QueryCondition notBetween(Object obj1, Object obj2) {
        if (ObjectUtil.isEmpty(obj1) || ObjectUtil.isEmpty(obj2)) {
            return new QueryCondition(SqlConstant.NOT_BETWEEN, this, null);
        }
        return new QueryCondition(SqlConstant.NOT_BETWEEN, this, new Object[]{obj1, obj2});
    }

    public QueryCondition likeLeft(Object obj) {
        return new QueryCondition(SqlConstant.LIKE_LEFT, this, obj);
    }

    public QueryCondition notLikeLeft(Object obj) {
        return new QueryCondition(SqlConstant.NOT_LIKE_LEFT, this, obj);
    }

    public QueryCondition likeRight(Object obj) {
        return new QueryCondition(SqlConstant.LIKE_RIGHT, this, obj);
    }

    public QueryCondition notLikeRight(Object obj) {
        return new QueryCondition(SqlConstant.NOT_LIKE_RIGHT, this, obj);
    }

    public QueryCondition isNull() {
        return new QueryCondition(SqlConstant.IS_NULL, this, SqlConstant.IS_NULL);
    }

    public QueryCondition isNotNull() {
        return new QueryCondition(SqlConstant.IS_NOT_NULL, this, SqlConstant.IS_NOT_NULL);
    }

    // ===========================================
    // 排序
    // ===========================================

    public OrderBy asc() {
        return new OrderBy(SqlConstant.ASC, this);
    }

    public OrderBy desc() {
        return new OrderBy(SqlConstant.DESC, this);
    }

}
