/*
 *  Copyright 2019 wjybxx
 *
 *  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
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to iBn 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.wjybxx.fastjgame.apt.serializer;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.wjybxx.fastjgame.apt.core.MyAbstractProcessor;
import com.wjybxx.fastjgame.apt.db.DBEntityProcessor;
import com.wjybxx.fastjgame.apt.utils.AutoUtils;
import com.wjybxx.fastjgame.apt.utils.BeanUtils;

import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import java.util.*;

/**
 * 1. 对于普通类:必须包含无参构造方法,且field注解的number必须在 0-65535之间
 * 2. 对于枚举:必须实现 indexableEnum 接口,且提供非private的forNumber方法
 *
 * @author wjybxx
 * @version 1.0
 * date - 2019/8/23
 * github - https://github.com/hl845740757
 */
@AutoService(Processor.class)
public class SerializableClassProcessor extends MyAbstractProcessor {

    // 使用这种方式可以脱离对utils,net包的依赖
    private static final String SERIALIZABLE_CLASS_CANONICAL_NAME = "com.wjybxx.fastjgame.net.binary.SerializableClass";
    private static final String SERIALIZABLE_FIELD_CANONICAL_NAME = "com.wjybxx.fastjgame.net.binary.SerializableField";

    private static final String CODEC_CANONICAL_NAME = "com.wjybxx.fastjgame.net.binary.PojoCodecImpl";
    private static final String ABSTRACT_CODEC_CANONICAL_NAME = "com.wjybxx.fastjgame.net.binary.AbstractPojoCodecImpl";

    private static final String GET_ENCODER_CLASS_METHOD_NAME = "getEncoderClass";
    private static final String WRITE_OBJECT_METHOD_NAME = "writeObject";
    private static final String READ_OBJECT_METHOD_NAME = "readObject";

    private static final String NEW_INSTANCE_METHOD_NAME = "newInstance";
    private static final String READ_FIELDS_METHOD_NAME = "readFields";

    private TypeMirror mapTypeMirror;
    private TypeMirror collectionTypeMirror;
    private TypeMirror stringTypeMirror;
    TypeMirror enumSetRawTypeMirror;
    TypeMirror enumMapRawTypeMirror;

    private TypeElement serializableClassElement;
    private DeclaredType serializableFieldDeclaredType;

    private TypeElement dbEntityTypeElement;
    private DeclaredType dbFieldDeclaredType;
    private DeclaredType impDeclaredType;

    private DeclaredType indexableEnumDeclaredType;
    private DeclaredType indexableObjectDeclaredType;

    TypeElement serializerTypeElement;
    // 要覆盖的方法缓存,减少大量查询
    private ExecutableElement getEncoderClassMethod;
    private ExecutableElement writeObjectMethod;
    private ExecutableElement readObjectMethod;

    TypeElement abstractSerializerTypeElement;
    ExecutableElement newInstanceMethod;
    ExecutableElement readFieldsMethod;

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Set.of(SERIALIZABLE_CLASS_CANONICAL_NAME, DBEntityProcessor.DB_ENTITY_CANONICAL_NAME);
    }

    @Override
    protected void ensureInited() {
        if (serializableClassElement != null) {
            return;
        }

        mapTypeMirror = elementUtils.getTypeElement(Map.class.getCanonicalName()).asType();
        collectionTypeMirror = elementUtils.getTypeElement(Collection.class.getCanonicalName()).asType();
        stringTypeMirror = elementUtils.getTypeElement(String.class.getCanonicalName()).asType();
        enumSetRawTypeMirror = typeUtils.erasure(elementUtils.getTypeElement(EnumSet.class.getCanonicalName()).asType());
        enumMapRawTypeMirror = typeUtils.erasure(elementUtils.getTypeElement(EnumMap.class.getCanonicalName()).asType());

        serializableClassElement = elementUtils.getTypeElement(SERIALIZABLE_CLASS_CANONICAL_NAME);
        serializableFieldDeclaredType = typeUtils.getDeclaredType(elementUtils.getTypeElement(SERIALIZABLE_FIELD_CANONICAL_NAME));

        dbEntityTypeElement = elementUtils.getTypeElement(DBEntityProcessor.DB_ENTITY_CANONICAL_NAME);
        dbFieldDeclaredType = typeUtils.getDeclaredType(elementUtils.getTypeElement(DBEntityProcessor.DB_FIELD_CANONICAL_NAME));
        impDeclaredType = typeUtils.getDeclaredType(elementUtils.getTypeElement(DBEntityProcessor.IMPL_CANONICAL_NAME));

        indexableEnumDeclaredType = typeUtils.getDeclaredType(elementUtils.getTypeElement(BeanUtils.INDEXABLE_ENUM_CANONICAL_NAME));
        indexableObjectDeclaredType = typeUtils.getDeclaredType(elementUtils.getTypeElement(BeanUtils.INDEXABLE_OBJECT_CANONICAL_NAME));

        serializerTypeElement = elementUtils.getTypeElement(CODEC_CANONICAL_NAME);
        getEncoderClassMethod = AutoUtils.findMethodByName(serializerTypeElement, GET_ENCODER_CLASS_METHOD_NAME);
        writeObjectMethod = AutoUtils.findMethodByName(serializerTypeElement, WRITE_OBJECT_METHOD_NAME);
        readObjectMethod = AutoUtils.findMethodByName(serializerTypeElement, READ_OBJECT_METHOD_NAME);

        abstractSerializerTypeElement = elementUtils.getTypeElement(ABSTRACT_CODEC_CANONICAL_NAME);
        newInstanceMethod = AutoUtils.findMethodByName(abstractSerializerTypeElement, NEW_INSTANCE_METHOD_NAME);
        readFieldsMethod = AutoUtils.findMethodByName(abstractSerializerTypeElement, READ_FIELDS_METHOD_NAME);

    }

    /**
     * 如果保留策略修改为runtime,则需要调用进行过滤。
     * {@link AutoUtils#selectSourceFile(Set, Elements)}
     */
    @Override
    protected boolean doProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 该注解只有类可以使用
        @SuppressWarnings("unchecked") final Set<TypeElement> allTypeElements = (Set<TypeElement>) roundEnv.getElementsAnnotatedWithAny(serializableClassElement, dbEntityTypeElement);
        for (TypeElement typeElement : allTypeElements) {
            try {
                checkBase(typeElement);
                generateSerializer(typeElement);
            } catch (Throwable e) {
                messager.printMessage(Diagnostic.Kind.ERROR, AutoUtils.getStackTrace(e), typeElement);
            }
        }
        return true;
    }

    /**
     * 基础信息检查
     */
    private void checkBase(TypeElement typeElement) {
        if (!isClassOrEnum(typeElement)) {
            // 其它类型抛出编译错误
            messager.printMessage(Diagnostic.Kind.ERROR, "unsupported type", typeElement);
            return;
        }

        if (typeElement.getKind() == ElementKind.ENUM) {
            // 枚举需要放在最前面检查 - 因为它可能实现后面的特殊接口
            checkEnum(typeElement);
            return;
        }

        if (isindexableEnum(typeElement)) {
            // indexableEnum是IndexableObject的子类,需要放在前面检查
            checkindexableEnum(typeElement);
            return;
        }

        if (isIndexableObject(typeElement)) {
            checkIndexableObject(typeElement);
            return;
        }

        if (typeElement.getKind() == ElementKind.CLASS) {
            // 检查普通类
            checkNormalClass(typeElement);
        }
    }

    private boolean isClassOrEnum(TypeElement typeElement) {
        return typeElement.getKind() == ElementKind.CLASS
                || typeElement.getKind() == ElementKind.ENUM;
    }

    /**
     * 检查枚举 - 要序列化的枚举,必须实现 indexableEnum 接口,否则无法序列化,或自己手写serializer。
     */
    private void checkEnum(TypeElement typeElement) {
        if (!isindexableEnum(typeElement)) {
            messager.printMessage(Diagnostic.Kind.ERROR,
                    "serializable enum must implement " + indexableEnumDeclaredType.asElement().getSimpleName(),
                    typeElement);
        }
        checkindexableEnum(typeElement);
    }

    private boolean isindexableEnum(TypeElement typeElement) {
        return AutoUtils.isSubTypeIgnoreTypeParameter(typeUtils, typeElement.asType(), indexableEnumDeclaredType);
    }

    /**
     * 检查 indexableEnum 的子类是否有forNumber方法
     */
    private void checkindexableEnum(TypeElement typeElement) {
        if (!containStaticNotPrivateForNumberMethod(typeElement)) {
            messager.printMessage(Diagnostic.Kind.ERROR,
                    String.format("%s must contains a not private 'static %s forNumber(int)' method!", typeElement.getSimpleName(), typeElement.getSimpleName()),
                    typeElement);
        }
    }

    /**
     * 是否包含静态的非private的forNumber方法 - static T forNumber(int)
     * (一定有getNumber方法)
     */
    private boolean containStaticNotPrivateForNumberMethod(TypeElement typeElement) {
        return typeElement.getEnclosedElements().stream()
                .filter(e -> e.getKind() == ElementKind.METHOD)
                .map(e -> (ExecutableElement) e)
                .filter(method -> !method.getModifiers().contains(Modifier.PRIVATE))
                .filter(method -> method.getModifiers().contains(Modifier.STATIC))
                .filter(method -> method.getSimpleName().toString().equals(BeanUtils.FOR_NUMBER_METHOD_NAME))
                .filter(method -> method.getParameters().size() == 1)
                .anyMatch(method -> method.getParameters().get(0).asType().getKind() == TypeKind.INT);
    }

    private boolean isIndexableObject(TypeElement typeElement) {
        return AutoUtils.isSubTypeIgnoreTypeParameter(typeUtils, typeElement.asType(), indexableObjectDeclaredType);
    }

    /**
     * 检查可索引的实体,检查是否有forIndex方法
     */
    private void checkIndexableObject(TypeElement typeElement) {
        if (!containsGetIndexMethod(typeElement)) {
            messager.printMessage(Diagnostic.Kind.ERROR, "can't find getIndex() method", typeElement);
            return;
        }

        final TypeMirror indexTypeMirror = getIndexTypeMirror(typeElement);
        if (!containsStaticNotPrivateForIndexMethod(typeElement, indexTypeMirror)) {
            messager.printMessage(Diagnostic.Kind.ERROR,
                    String.format("%s must contains a not private 'static %s forIndex(%s)' method!",
                            typeElement.getSimpleName(), typeElement.getSimpleName(), indexTypeMirror.toString()),
                    typeElement);
        }
    }

    private static boolean containsGetIndexMethod(TypeElement typeElement) {
        return findGetIndexMethod(typeElement).isPresent();
    }

    private static Optional<ExecutableElement> findGetIndexMethod(TypeElement typeElement) {
        return typeElement.getEnclosedElements().stream()
                .filter(e -> e.getKind() == ElementKind.METHOD)
                .map(e -> (ExecutableElement) e)
                .filter(e -> e.getParameters().size() == 0)
                .filter(e -> ((Element) e).getSimpleName().toString().equals(BeanUtils.GET_INDEX_METHOD_NAME))
                .findFirst();
    }

    static TypeMirror getIndexTypeMirror(TypeElement typeElement) {
        final ExecutableElement getIndexMethod = findGetIndexMethod(typeElement).orElseThrow();
        return getIndexMethod.getReturnType();
    }

    /**
     * 是否包含配私有的静态forIndex方法
     *
     * @param typeElement     方法的返回值类型
     * @param indexTypeMirror 方法的参数类型
     */
    private boolean containsStaticNotPrivateForIndexMethod(final TypeElement typeElement, final TypeMirror indexTypeMirror) {
        return typeElement.getEnclosedElements().stream()
                .filter(e -> e.getKind() == ElementKind.METHOD)
                .map(e -> (ExecutableElement) e)
                .filter(method -> !method.getModifiers().contains(Modifier.PRIVATE))
                .filter(method -> method.getModifiers().contains(Modifier.STATIC))
                .filter(method -> method.getSimpleName().toString().equals(BeanUtils.FOR_INDEX_METHOD_NAME))
                .filter(method -> method.getParameters().size() == 1)
                .filter(method -> AutoUtils.isSameTypeIgnoreTypeParameter(typeUtils, method.getParameters().get(0).asType(), indexTypeMirror))
                .anyMatch(method -> AutoUtils.isSameTypeIgnoreTypeParameter(typeUtils, method.getReturnType(), typeElement.asType()));
    }

    private void checkNormalClass(TypeElement typeElement) {
        // 父类可能是不序列化的,但是有字段要序列化
        final List<? extends Element> allFieldsAndMethodWithInherit = BeanUtils.getAllFieldsAndMethodsWithInherit(typeElement);
        for (Element element : allFieldsAndMethodWithInherit) {
            // 非成员属性
            if (element.getKind() != ElementKind.FIELD) {
                continue;
            }

            final VariableElement variableElement = (VariableElement) element;
            // 不需要序列化
            if (!isSerializableField(variableElement)) {
                continue;
            }

            // 不能是static
            if (variableElement.getModifiers().contains(Modifier.STATIC)) {
                messager.printMessage(Diagnostic.Kind.ERROR, "serializable field can't be static", variableElement);
                continue;
            }

            // 必须包含非private的getter方法
            if (!containsNotPrivateGetterMethod(variableElement, allFieldsAndMethodWithInherit)) {
                messager.printMessage(Diagnostic.Kind.ERROR, "serializable field must contains a not private getter", variableElement);
                continue;
            }

            // map和集合类型
            if (isMapOrCollection(variableElement.asType())) {
                checkMapAndCollectionField(variableElement);
            }
        }

        if (!typeElement.getModifiers().contains(Modifier.ABSTRACT)) {
            // 无参构造方法检测
            checkNoArgsConstructor(typeElement);
        }
    }

    private void checkNoArgsConstructor(TypeElement typeElement) {
        if (!BeanUtils.containsNoArgsConstructor(typeElement)) {
            messager.printMessage(Diagnostic.Kind.ERROR,
                    "SerializableClass " + typeElement.getSimpleName() + " must contains no-args constructor, private is ok!",
                    typeElement);
        }
    }

    /**
     * 用于 {@link DBEntityProcessor#DB_FIELD_CANONICAL_NAME}注解和{@link #SERIALIZABLE_FIELD_CANONICAL_NAME}注解的字段是可以序列化的
     */
    boolean isSerializableField(VariableElement variableElement) {
        return AutoUtils.isAnnotationPresent(typeUtils, variableElement, serializableFieldDeclaredType)
                || AutoUtils.isAnnotationPresent(typeUtils, variableElement, dbFieldDeclaredType);
    }

    /**
     * 是否包含非private的getter方法
     *
     * @param allFieldsAndMethodWithInherit 所有的字段和方法,可能在父类中
     */
    private boolean containsNotPrivateGetterMethod(final VariableElement variableElement, final List<? extends Element> allFieldsAndMethodWithInherit) {
        return BeanUtils.containsNotPrivateGetterMethod(typeUtils, variableElement, allFieldsAndMethodWithInherit);
    }

    private void checkMapAndCollectionField(VariableElement variableElement) {
        final DeclaredType declaredType = AutoUtils.getDeclaredType(variableElement.asType());
        if (!declaredType.asElement().getModifiers().contains(Modifier.ABSTRACT)) {
            // 声明类型是具体类型
            return;
        }

        if (isEnumMap(declaredType) || isEnumSet(declaredType)) {
            // 声明类型是EnumMap 或 EnumSet (需要特殊处理)
            return;
        }

        // 其它抽象类型必须有imp注解
        final TypeMirror implTypeMirror = getFieldImplAnnotationValue(variableElement);
        if (implTypeMirror == null) {
            messager.printMessage(Diagnostic.Kind.ERROR,
                    "Abstract MapOrCollection must contains impl annotation " + DBEntityProcessor.IMPL_CANONICAL_NAME,
                    variableElement);
            return;
        }

        if (!AutoUtils.isSubTypeIgnoreTypeParameter(typeUtils, implTypeMirror, variableElement.asType())) {
            // 实现类型必须是声明类型的子类
            messager.printMessage(Diagnostic.Kind.ERROR, "MapOrCollectionImpl must be field sub type", variableElement);
            return;
        }

        if (isEnumMap(implTypeMirror) || isEnumSet(implTypeMirror)) {
            // 实现类型是EnumMap或EnumSet
            return;
        }

        final DeclaredType impDeclaredType = (DeclaredType) implTypeMirror;
        if (impDeclaredType.asElement().getModifiers().contains(Modifier.ABSTRACT)) {
            // 其它实现类型必须是具体类型
            messager.printMessage(Diagnostic.Kind.ERROR, "MapOrCollectionImpl must can't be abstract class", variableElement);
            return;
        }
    }

    boolean isString(TypeMirror typeMirror) {
        return typeUtils.isSameType(typeMirror, stringTypeMirror);
    }

    private boolean isMapOrCollection(TypeMirror typeMirror) {
        return isMap(typeMirror) || isCollection(typeMirror);
    }

    boolean isEnumSet(TypeMirror typeMirror) {
        return AutoUtils.isSubTypeIgnoreTypeParameter(typeUtils, typeMirror, enumSetRawTypeMirror);
    }

    boolean isEnumMap(TypeMirror typeMirror) {
        return AutoUtils.isSubTypeIgnoreTypeParameter(typeUtils, typeMirror, enumMapRawTypeMirror);
    }

    boolean isMap(TypeMirror typeMirror) {
        return AutoUtils.isSubTypeIgnoreTypeParameter(typeUtils, typeMirror, mapTypeMirror);
    }

    boolean isCollection(TypeMirror typeMirror) {
        return AutoUtils.isSubTypeIgnoreTypeParameter(typeUtils, typeMirror, collectionTypeMirror);
    }

    TypeMirror getFieldImplAnnotationValue(VariableElement variableElement) {
        final AnnotationMirror impAnnotationMirror = AutoUtils
                .findAnnotation(typeUtils, variableElement, impDeclaredType)
                .orElse(null);

        if (impAnnotationMirror == null) {
            return null;
        }

        final AnnotationValue annotationValue = AutoUtils.getAnnotationValue(impAnnotationMirror, "value");
        assert null != annotationValue;
        return AutoUtils.getAnnotationValueTypeMirror(annotationValue);
    }

    // ----------------------------------------------- 辅助类生成 -------------------------------------------

    private void generateSerializer(TypeElement typeElement) {
        if (isindexableEnum(typeElement)) {
            new IndexableEnumCodecGenerator(this, typeElement).execute();
        } else if (isIndexableObject(typeElement)) {
            new IndexableObjectCodecGenerator(this, typeElement).execute();
        } else {
            new DefaultCodecGenerator(this, typeElement).execute();
        }
    }

    /**
     * 创建writeObject方法
     */
    MethodSpec.Builder newWriteMethodBuilder(DeclaredType superDeclaredType) {
        return MethodSpec.overriding(writeObjectMethod, superDeclaredType, typeUtils);
    }

    /**
     * 创建readObject方法
     */
    MethodSpec.Builder newReadObjectMethodBuilder(DeclaredType superDeclaredType) {
        return MethodSpec.overriding(readObjectMethod, superDeclaredType, typeUtils);
    }

    /**
     * 创建返回负责被序列化的类对象的方法
     */
    MethodSpec newGetEncoderClassMethod(DeclaredType superDeclaredType) {
        return MethodSpec.overriding(getEncoderClassMethod, superDeclaredType, typeUtils)
                .addStatement("return $T.class", TypeName.get(superDeclaredType.getTypeArguments().get(0)))
                .build();
    }

    /**
     * 获取class对应的序列化工具类的类名
     */
    static String getCodecClassName(TypeElement typeElement) {
        return typeElement.getSimpleName().toString() + "Codec";
    }
}