/**
 * Copyright (c) 2011-2014, hubin (jobob@qq.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.baomidou.mybatisplus.mapper;

import com.baomidou.mybatisplus.entity.TableFieldInfo;
import com.baomidou.mybatisplus.entity.TableInfo;
import com.baomidou.mybatisplus.enums.SqlMethod;
import com.baomidou.mybatisplus.toolkit.StringUtils;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.defaults.RawSqlSource;

import java.util.List;
import java.util.Map;

/**
 * <p>
 * SQL 自动注入逻辑处理器<br>
 * 1、支持逻辑删除
 * </p>
 *
 * @author hubin willenfoo
 * @Date 2017-09-09
 */
public class LogicSqlInjector extends AutoSqlInjector {

    /**
     * 根据 ID 删除
     */
    @Override
    protected void injectDeleteByIdSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
        if (table.isLogicDelete()) {
            // 逻辑删除注入
            SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE_BY_ID;
            SqlSource sqlSource;
            String idStr = table.getKeyProperty();
            if (batch) {
                sqlMethod = SqlMethod.LOGIC_DELETE_BATCH_BY_IDS;
                StringBuilder ids = new StringBuilder();
                ids.append("\n<foreach item=\"item\" index=\"index\" collection=\"coll\" separator=\",\">");
                ids.append("#{item}");
                ids.append("\n</foreach>");
                idStr = ids.toString();
            }
            String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlLogicSet(table),
                table.getKeyColumn(), idStr);
            sqlSource = languageDriver.createSqlSource(configuration, sql, Object.class);
            this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource);
        } else {
            // 正常删除
            super.injectDeleteByIdSql(batch, mapperClass, modelClass, table);
        }
    }

    /**
     * 根据 SQL 删除
     */
    @Override
    protected void injectDeleteSql(Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
        if (table.isLogicDelete()) {
            // 逻辑删除注入
            SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE;
            String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlLogicSet(table),
                sqlWhereEntityWrapper(table));
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
            this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource);
        } else {
            // 正常删除
            super.injectDeleteSql(mapperClass, modelClass, table);
        }
    }

    /**
     * 根据 MAP 删除
     */
    @Override
    protected void injectDeleteByMapSql(Class<?> mapperClass, TableInfo table) {
        if (table.isLogicDelete()) {
            // 逻辑删除注入
            SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE_BY_MAP;
            String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlLogicSet(table),
                sqlWhereByMap(table));
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Map.class);
            this.addUpdateMappedStatement(mapperClass, Map.class, sqlMethod.getMethod(), sqlSource);
        } else {
            // 正常删除
            super.injectDeleteByMapSql(mapperClass, table);
        }
    }

    /**
     * <p>
     * 注入查询 SQL 语句
     * </p>
     *
     * @param batch       是否为批量插入
     * @param mapperClass
     * @param modelClass
     * @param table
     */
    @Override
    protected void injectSelectByIdSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
        if (table.isLogicDelete()) {
            SqlMethod sqlMethod = SqlMethod.LOGIC_SELECT_BY_ID;
            SqlSource sqlSource;
            if (batch) {
                sqlMethod = SqlMethod.LOGIC_SELECT_BATCH_BY_IDS;
                StringBuilder ids = new StringBuilder();
                ids.append("\n<foreach item=\"item\" index=\"index\" collection=\"coll\" separator=\",\">");
                ids.append("#{item}");
                ids.append("\n</foreach>");
                sqlSource = languageDriver.createSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(table, false),
                    table.getTableName(), table.getKeyColumn(), ids.toString(), getLogicDeleteSql(table)), modelClass);
            } else {
                sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(table, false), table.getTableName(),
                    table.getKeyColumn(), table.getKeyProperty(), getLogicDeleteSql(table)), Object.class);
            }
            this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, table);
        } else {
            // 正常查询
            super.injectSelectByIdSql(batch, mapperClass, modelClass, table);
        }
    }

    /**
     * <p>
     * 注入更新 SQL 语句
     * </p>
     *
     * @param mapperClass
     * @param modelClass
     * @param table
     */
    @Override
    protected void injectUpdateByIdSql(boolean selective, Class<?> mapperClass, Class<?> modelClass, TableInfo table) {
        if (table.isLogicDelete()) {
            SqlMethod sqlMethod = selective ? SqlMethod.LOGIC_UPDATE_BY_ID : SqlMethod.LOGIC_UPDATE_ALL_COLUMN_BY_ID;
            String sql = String.format(sqlMethod.getSql(), table.getTableName(), sqlSet(selective, table, "et."),
                table.getKeyColumn(),
                "et." + table.getKeyProperty(),
                "<if test=\"et instanceof java.util.Map\">" +
                    "<if test=\"et.MP_OPTLOCK_VERSION_ORIGINAL!=null\">"
                    + "and ${et.MP_OPTLOCK_VERSION_COLUMN}=#{et.MP_OPTLOCK_VERSION_ORIGINAL}"
                    + "</if>"
                    + "</if>" +
                    getLogicDeleteSql(table)
            );
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
            this.addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource);
        } else {
            super.injectUpdateByIdSql(selective, mapperClass, modelClass, table);
        }
    }

    /**
     * <p>
     * SQL 更新 set 语句
     * </p>
     *
     * @param table 表信息
     * @return sql and 片段
     */
    public String getLogicDeleteSql(TableInfo table) {
        StringBuilder sql = new StringBuilder();
        List<TableFieldInfo> fieldList = table.getFieldList();
        for (TableFieldInfo fieldInfo : fieldList) {
            if (fieldInfo.isLogicDelete()) {
                sql.append(" AND ").append(fieldInfo.getColumn());
                if (StringUtils.isCharSequence(fieldInfo.getPropertyType())) {
                    sql.append("='").append(fieldInfo.getLogicNotDeleteValue()).append("'");
                } else {
                    sql.append("=").append(fieldInfo.getLogicNotDeleteValue());
                }
            }
        }
        return sql.toString();
    }

    /**
     * <p>
     * SQL 更新 set 语句
     * </p>
     *
     * @param table 表信息
     * @return sql set 片段
     */
    protected String sqlLogicSet(TableInfo table) {
        List<TableFieldInfo> fieldList = table.getFieldList();
        StringBuilder set = new StringBuilder("SET ");
        int i = 0;
        for (TableFieldInfo fieldInfo : fieldList) {
            if (fieldInfo.isLogicDelete()) {
                if (++i > 1) {
                    set.append(",");
                }
                set.append(fieldInfo.getColumn()).append("=");
                if (StringUtils.isCharSequence(fieldInfo.getPropertyType())) {
                    set.append("'").append(fieldInfo.getLogicDeleteValue()).append("'");
                } else {
                    set.append(fieldInfo.getLogicDeleteValue());
                }
            }
        }
        return set.toString();
    }

    // ------------ 处理逻辑删除条件过滤 ------------

    @Override
    protected String sqlWhere(TableInfo table) {
        if (table.isLogicDelete()) {
            StringBuilder where = new StringBuilder("\n<where>");
            // 过滤逻辑
            List<TableFieldInfo> fieldList = table.getFieldList();
            // EW 逻辑
            if (StringUtils.isNotEmpty(table.getKeyProperty())) {
                where.append("\n<if test=\"ew.").append(table.getKeyProperty()).append("!=null\">");
                where.append(" AND ").append(table.getKeyColumn()).append("=#{ew.");
                where.append(table.getKeyProperty()).append("}");
                where.append("</if>");
            }
            for (TableFieldInfo fieldInfo : fieldList) {
                where.append(convertIfTag(fieldInfo, "ew.", false));
                where.append(" AND ").append(this.sqlCondition(fieldInfo.getCondition(),
                    fieldInfo.getColumn(), "ew." + fieldInfo.getEl()));
                where.append(convertIfTag(fieldInfo, true));
            }
            // 过滤逻辑
            where.append("\n").append(getLogicDeleteSql(table));
            where.append("\n</where>");
            return where.toString();
        }
        // 正常逻辑
        return super.sqlWhere(table);
    }

    @Override
    protected String sqlWhereEntityWrapper(TableInfo table) {
        if (table.isLogicDelete()) {
            StringBuilder where = new StringBuilder(128);
            where.append("\n<where>");
            where.append("\n<choose><when test=\"ew!=null\">");
            where.append("\n<if test=\"ew.entity!=null\">");
            if (StringUtils.isNotEmpty(table.getKeyProperty())) {
                where.append("\n<if test=\"ew.entity.").append(table.getKeyProperty()).append("!=null\">");
                where.append(" AND ").append(table.getKeyColumn()).append("=#{ew.entity.");
                where.append(table.getKeyProperty()).append("}");
                where.append("</if>");
            }
            List<TableFieldInfo> fieldList = table.getFieldList();
            for (TableFieldInfo fieldInfo : fieldList) {
                where.append(convertIfTag(fieldInfo, "ew.entity.", false));
                where.append(" AND ").append(this.sqlCondition(fieldInfo.getCondition(),
                    fieldInfo.getColumn(), "ew.entity." + fieldInfo.getEl()));
                where.append(convertIfTag(fieldInfo, true));
            }
            where.append("\n</if>");
            where.append("\n").append(getLogicDeleteSql(table));
            where.append("\n<if test=\"ew.sqlSegment!=null\">${ew.sqlSegment}\n</if>");
            where.append("\n</when><otherwise>");
            where.append("\n").append(getLogicDeleteSql(table));
            where.append("\n</otherwise></choose>");
            where.append("\n</where>");
            return where.toString();
        }
        // 正常逻辑
        return super.sqlWhereEntityWrapper(table);
    }

    @Override
    protected String sqlWhereByMap(TableInfo table) {
        if (table.isLogicDelete()) {
            return "<where>\n" +
                "<if test=\"cm != null and !cm.isEmpty\">\n" +
                "<foreach collection=\"cm\" index=\"k\" item=\"v\" separator=\"AND\">\n" +
                "<choose>\n" +
                "<when test=\"v == null\">\n" +
                " ${k} IS NULL \n" +
                "</when>\n" +
                "<otherwise> ${k} = #{v} </otherwise>\n" +
                "</choose>\n" +
                "</foreach>\n" +
                "</if>\n" +
                " AND \n" + getLogicDeleteSql(table) +
                "</where>";
        }
        // 正常逻辑
        return super.sqlWhereByMap(table);
    }

}
