/*
 * Copyright (c) 2015-2020, www.dibo.ltd (service@dibo.ltd).
 * <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>
 * https://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.diboot.core.binding.parser;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.diboot.core.binding.query.BindQuery;
import com.diboot.core.binding.query.dynamic.AnnoJoiner;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import com.diboot.core.vo.Status;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 *  对象中的绑定注解 缓存管理类
 * @author mazc@dibo.ltd<br>
 * @version 2.0<br>
 * @date 2019/04/03 <br>
 */
@Slf4j
public class ParserCache {
    /**
     * VO类-绑定注解缓存
     */
    private static Map<Class, BindAnnotationGroup> allVoBindAnnotationCacheMap = new ConcurrentHashMap<>();
    /**
     * 表及相关信息的缓存
     */
    private static Map<String, TableLinkage> tableToLinkageCacheMap = new ConcurrentHashMap<>();
    /**
     * entity类-表名的缓存
     */
    private static Map<String, String> entityClassTableCacheMap = new ConcurrentHashMap<>();
    /**
     * entity类小驼峰实例名-entity类
     */
    private static Map<String, Class<?>> entityName2EntityClassCacheMap = new ConcurrentHashMap<>();
    /**
     * dto类-BindQuery注解的缓存
     */
    private static Map<String, List<AnnoJoiner>> dtoClassBindQueryCacheMap = new ConcurrentHashMap<>();

    /**
     * 获取指定class对应的Bind相关注解
     * @param voClass
     * @return
     */
    public static BindAnnotationGroup getBindAnnotationGroup(Class voClass){
        BindAnnotationGroup group = allVoBindAnnotationCacheMap.get(voClass);
        if(group == null){
            // 获取注解并缓存
            group = new BindAnnotationGroup();
            // 获取当前VO的注解
            List<Field> fields = BeanUtils.extractAllFields(voClass);
            if(fields != null){
                for (Field field : fields) {
                    //遍历属性
                    Annotation[] annotations = field.getDeclaredAnnotations();
                    if (V.isEmpty(annotations)) {
                        continue;
                    }
                    for (Annotation annotation : annotations) {
                        Class<?> setterObjClazz = field.getType();
                        if(setterObjClazz.equals(java.util.List.class) || setterObjClazz.equals(java.util.Collections.class)){
                            // 如果是集合，获取其泛型参数class
                            Type genericType = field.getGenericType();
                            if(genericType instanceof ParameterizedType){
                                ParameterizedType pt = (ParameterizedType) genericType;
                                setterObjClazz = (Class<?>)pt.getActualTypeArguments()[0];
                            }
                        }
                        group.addBindAnnotation(field.getName(), setterObjClazz, annotation);
                    }
                }
            }
            allVoBindAnnotationCacheMap.put(voClass, group);
        }
        // 返回归类后的注解对象
        return group;
    }

    /**
     * 初始化Table的相关对象信息
     */
    private static void initTableToLinkageCacheMap(){
        if(tableToLinkageCacheMap.isEmpty()){
            SqlSessionFactory sqlSessionFactory = ContextHelper.getBean(SqlSessionFactory.class);
            Collection<Class<?>> mappers = sqlSessionFactory.getConfiguration().getMapperRegistry().getMappers();
            if(V.notEmpty(mappers)){
                mappers.forEach(m->{
                    Type[] types = m.getGenericInterfaces();
                    try{
                        if(types != null && types.length > 0 && types[0] != null){
                            ParameterizedType genericType = (ParameterizedType) types[0];
                            Type[] superTypes = genericType.getActualTypeArguments();
                            if(superTypes != null && superTypes.length > 0 && superTypes[0] != null){
                                String entityClassName = superTypes[0].getTypeName();
                                if(entityClassName.length() > 1){
                                    Class<?> entityClass = Class.forName(entityClassName);
                                    TableLinkage linkage = new TableLinkage(entityClass, m);
                                    tableToLinkageCacheMap.put(linkage.getTable(), linkage);
                                    entityName2EntityClassCacheMap.put(entityClass.getSimpleName(), entityClass);
                                }
                            }
                        }
                    }
                    catch (Exception e){
                        log.warn("解析mapper异常", e);
                    }
                });
            }
        }
    }

    /**
     * 是否有is_deleted列
     * @return
     */
    public static boolean hasDeletedColumn(String table){
        TableLinkage linkage = getTableLinkage(table);
        return linkage != null && linkage.isHasDeleted();
    }

    /**
     * 获取table相关信息
     * @return
     */
    public static TableLinkage getTableLinkage(String table){
        initTableToLinkageCacheMap();
        TableLinkage linkage = tableToLinkageCacheMap.get(table);
        return linkage;
    }

    /**
     * 获取entity对应的表名
     * @param entityClass
     * @return
     */
    public static String getEntityTableName(Class<?> entityClass){
        String entityClassName = entityClass.getName();
        String tableName = entityClassTableCacheMap.get(entityClassName);
        if(tableName == null){
            TableName tableNameAnno = AnnotationUtils.findAnnotation(entityClass, TableName.class);
            if(tableNameAnno != null){
                tableName = tableNameAnno.value();
            }
            else{
                tableName = S.toSnakeCase(entityClass.getSimpleName());
            }
            entityClassTableCacheMap.put(entityClassName, tableName);
        }
        return tableName;
    }

    /**
     * 根据entity类获取mapper实例
     * @return
     */
    public static BaseMapper getMapperInstance(Class<?> entityClass){
        String tableName = getEntityTableName(entityClass);
        TableLinkage linkage = getTableLinkage(tableName);
        if(linkage == null){
            throw new BusinessException(Status.FAIL_INVALID_PARAM, "未找到 "+entityClass.getName()+" 的Mapper定义！");
        }
        BaseMapper mapper = (BaseMapper) ContextHelper.getBean(linkage.getMapperClass());
        return mapper;
    }

    /**
     * 根据类的entity类名获取EntityClass
     * @return
     */
    public static Class<?> getEntityClassByClassName(String className){
        initTableToLinkageCacheMap();
        return entityName2EntityClassCacheMap.get(className);
    }

    /**
     * 当前DTO是否有Join绑定
     * @param dto dto对象
     * @param fieldNameSet 有值属性集合
     * @param <DTO>
     * @return
     */
    public static <DTO> boolean hasJoinTable(DTO dto, Collection<String> fieldNameSet){
        List<AnnoJoiner> annoList = getBindQueryAnnos(dto.getClass());
        if(V.notEmpty(annoList)){
            for(AnnoJoiner anno : annoList){
                if(V.notEmpty(anno.getJoin()) && fieldNameSet != null && fieldNameSet.contains(anno.getFieldName())){
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 获取dto类中定义的BindQuery注解
     * @param dtoClass
     * @return
     */
    public static List<AnnoJoiner> getBindQueryAnnos(Class<?> dtoClass){
        String dtoClassName = dtoClass.getName();
        if(dtoClassBindQueryCacheMap.containsKey(dtoClassName)){
            return dtoClassBindQueryCacheMap.get(dtoClassName);
        }
        // 初始化
        List<AnnoJoiner> annos = null;
        List<Field> declaredFields = BeanUtils.extractFields(dtoClass, BindQuery.class);
        int index = 1;
        Map<String, String> joinOn2Alias = new HashMap<>();
        for (Field field : declaredFields) {
            BindQuery query = field.getAnnotation(BindQuery.class);
            if(query == null || query.ignore()){
                continue;
            }
            if(annos == null){
                annos = new ArrayList<>();
            }
            AnnoJoiner annoJoiner = new AnnoJoiner(field, query);
            // 关联对象，设置别名
            if(V.notEmpty(annoJoiner.getJoin())){
                String key = annoJoiner.getJoin() + ":" + annoJoiner.getCondition();
                String alias = joinOn2Alias.get(key);
                if(alias == null){
                    alias = "r"+index;
                    annoJoiner.setAlias(alias);
                    index++;
                    joinOn2Alias.put(key, alias);
                }
                else{
                    annoJoiner.setAlias(alias);
                }
                annoJoiner.parse();
            }
            annos.add(annoJoiner);
        }
        if(annos == null){
            annos = Collections.emptyList();
        }
        dtoClassBindQueryCacheMap.put(dtoClassName, annos);
        return annos;
    }

    /**
     * 获取注解joiner
     * @param dtoClass
     * @param fieldNames
     * @return
     */
    public static List<AnnoJoiner> getAnnoJoiners(Class<?> dtoClass, Collection<String> fieldNames) {
        List<AnnoJoiner> annoList = getBindQueryAnnos(dtoClass);
        // 不过滤  返回全部
        if(fieldNames == null){
            return annoList;
        }
        // 过滤
        if(V.notEmpty(annoList)){
            List<AnnoJoiner> matchedAnnoList = new ArrayList<>();
            for(AnnoJoiner anno : annoList){
                if(fieldNames.contains(anno.getFieldName())){
                    matchedAnnoList.add(anno);
                }
            }
            return matchedAnnoList;
        }
        return Collections.emptyList();
    }

    /**
     * 获取注解joiner
     * @param dtoClass
     * @param key
     * @return
     */
    public static AnnoJoiner getAnnoJoiner(Class<?> dtoClass, String key) {
        List<AnnoJoiner> annoList = getBindQueryAnnos(dtoClass);
        if(V.notEmpty(annoList)){
            for(AnnoJoiner anno : annoList){
                if(key.equals(anno.getFieldName())){
                    return anno;
                }
            }
        }
        return null;
    }

}
