package com.yanzuoguang.db.impl;

import com.yanzuoguang.dao.DaoConst;
import com.yanzuoguang.dao.TableAnnotation;
import com.yanzuoguang.db.ConfigDb;
import com.yanzuoguang.util.base.MethodField;
import com.yanzuoguang.util.base.ObjectHelper;
import com.yanzuoguang.util.helper.StringHelper;
import com.yanzuoguang.util.log.Log;
import com.yanzuoguang.util.vo.MapRow;
import org.springframework.beans.*;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 将数据映射为结果
 *
 * @author 颜佐光
 */
public class AllBeanRowMapper<T> implements RowMapper<T> {

    /**
     * 需要映射的Class
     */
    private Class<T> mappedClass;

    /**
     * 是否属于映射方式
     */
    private boolean isMapping = false;

    /**
     * 获取字段映射关系
     */
    private Map<String, MethodField> typeField;

    /**
     * 配置信息
     */
    private ConfigDb configDb;

    /**
     * 构造函数
     *
     * @param mappedClass
     */
    public AllBeanRowMapper(Class<T> mappedClass, ConfigDb configDb) {
        initialize(mappedClass);
        this.configDb = configDb;
    }

    /**
     * 初始化元素之间的映射关系
     *
     * @param mappedClass the mapped class.
     */
    protected void initialize(Class<T> mappedClass) {
        this.mappedClass = mappedClass;
        if (ObjectHelper.isSub(MapRow.class, mappedClass) || ObjectHelper.isSub(Map.class, mappedClass)) {
            isMapping = true;
        } else {
            Map<String, MethodField> temp = ObjectHelper.getTypeField(mappedClass);
            typeField = new HashMap<>(temp.size());

            for (Map.Entry<String, MethodField> item : temp.entrySet()) {
                String name = item.getKey();

                // 获取字段值
                MethodField field = item.getValue();
                // 获取名称
                TableAnnotation annotation = null;
                if (field.getField() != null) {
                    annotation = field.getField().getAnnotation(TableAnnotation.class);
                } else if (field.getGetMethod() != null) {
                    annotation = field.getGetMethod().getAnnotation(TableAnnotation.class);
                }
                if (annotation != null && !StringHelper.isEmpty(annotation.value())) {
                    name = annotation.value();
                }
                String underscoredName = underscoreName(name);
                this.typeField.put(underscoredName, item.getValue());
            }
        }
    }

    /**
     * convert a name in camelCase to an underscored name in lower case.
     * Any upper case letters are converted to lower case with a preceding underscore.
     *
     * @param name the string containing original name
     * @return the converted name
     */
    public String underscoreName(String name) {
        return underscoreNameBase(name);
    }

    /**
     * 获取列明
     *
     * @param name
     * @return
     */
    private static String underscoreNameBase(String name) {
        if (!StringUtils.hasLength(name)) {
            return "";
        }
        return name.toLowerCase().replace("_", "");
    }

    /**
     * 将行进行转换
     *
     * @param from
     * @return
     */
    public static MapRow toLoweRow(MapRow from) {
        MapRow to = new MapRow();
        for (Map.Entry<String, Object> key : from.entrySet()) {
            to.put(underscoreNameBase(key.getKey()), key.getValue());
        }
        return to;
    }

    /**
     * 获取空值字段
     *
     * @param from
     * @return
     */
    public static Object getLoweRowField(MapRow from, String field) {
        field = underscoreNameBase(field);
        for (Map.Entry<String, Object> key : from.entrySet()) {
            if (underscoreNameBase(key.getKey()).equals(field)) {
                return key.getValue();
            }
        }
        return null;
    }

    /**
     * Extract the values for all columns in the current row.
     * <p>Utilizes public setters and result setByType metadata.
     *
     * @see ResultSetMetaData
     */
    @Override
    public T mapRow(ResultSet rs, int rowNumber) throws SQLException, TypeMismatchException {
        Assert.state(this.mappedClass != null, "Mapped class was not specified");
        T mappedObject = BeanUtils.instantiate(this.mappedClass);

        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();

        for (int index = 1; index <= columnCount; index++) {
            String column = JdbcUtils.lookupColumnName(rsmd, index);
            String underscoredName = underscoreName(column);
            if (this.isMapping) {
                Object value = JdbcUtils.getResultSetValue(rs, index);
                if (value instanceof Date) {
                    value = JdbcUtils.getResultSetValue(rs, index, String.class);
                }
                ((Map) mappedObject).put(getCamelCase(column), value);
            } else if (!this.typeField.containsKey(underscoredName)) {
                continue;
            } else {
                MethodField pd = this.typeField.get(underscoredName);
                Class<?> type;

                if (pd.getField() != null) {
                    type = pd.getField().getType();
                } else {
                    type = pd.getGetMethod().getReturnType();
                }

                Object value = null;
                try {
                    value = JdbcUtils.getResultSetValue(rs, index, type);
                    if (configDb.isPrintMapper() && rowNumber == 0) {
                        Log.info(AllBeanRowMapper.class, "Mapping column '%s' to property '%s' of type %s", column, pd.getName(), type);
                    }
                    ObjectHelper.setByType(mappedObject, pd, value);
                } catch (TypeMismatchException e) {
                    if (value == null) {
                        Log.info(AllBeanRowMapper.class,
                                "Intercepted TypeMismatchException for row %d and column '%s' with " +
                                        "value %s when setting property '%s' of type %s on object: %s",
                                rowNumber, column, StringHelper.EMPTY, pd.getName(), type, mappedObject);
                    } else {
                        throw e;
                    }
                } catch (Exception ex) {
                    throw new DataRetrievalFailureException("Unable to map column " + column + " to property " + pd.getName(), ex);
                }
            }
        }

        return mappedObject;
    }

    /**
     * 设置SameCase命名方式
     *
     * @param column
     * @return
     */
    private String getCamelCase(String column) {
        switch (configDb.getRowNameType()) {
            case DaoConst.ROW_NAME_TYPE_NO_CHANGE:
                break;
            case DaoConst.ROW_NAME_TYPE_CAMEL_CASE:
            default:
                column = StringHelper.getCamelCase(column);
                break;
        }
        return column;
    }

    /**
     * 缓存的处理类
     */
    private static Map<Class, Object> Cache = new HashMap<Class, Object>();

    /**
     * 获取可以转化实体
     *
     * @param cls 类型
     * @param <T> 泛型类型
     * @return 可转换的实体
     */
    public static <T extends Object> AllBeanRowMapper<T> getInstance(Class<T> cls, ConfigDb configDb) {
        if (!Cache.containsKey(cls)) {
            Cache.put(cls, new AllBeanRowMapper<T>(cls, configDb));
        }
        AllBeanRowMapper<T> ret = (AllBeanRowMapper<T>) Cache.get(cls);
        return ret;
    }
}