package act.mail.bytecode;

/*-
 * #%L
 * ACT Framework
 * %%
 * Copyright (C) 2014 - 2017 ActFramework
 * %%
 * 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 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.
 * #L%
 */

import act.app.AppByteCodeScannerBase;
import act.asm.*;
import act.asm.signature.SignatureReader;
import act.asm.signature.SignatureVisitor;
import act.controller.annotation.TemplateContext;
import act.controller.meta.HandlerParamMetaInfo;
import act.controller.meta.ParamAnnoInfoTrait;
import act.mail.Mailer;
import act.mail.meta.MailerClassMetaInfo;
import act.mail.meta.MailerClassMetaInfoManager;
import act.mail.meta.SenderMethodMetaInfo;
import act.util.AsmTypes;
import act.util.ByteCodeVisitor;
import act.util.GeneralAnnoInfo;
import org.osgl.$;
import org.osgl.logging.L;
import org.osgl.logging.Logger;

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

/**
 * Mailer scanner implementation
 */
public class MailerByteCodeScanner extends AppByteCodeScannerBase {

    private final static Logger logger = L.get(MailerByteCodeScanner.class);
    private MailerClassMetaInfo classInfo;
    private volatile MailerClassMetaInfoManager classInfoBase;

    @Override
    protected boolean shouldScan(String className) {
        classInfo = new MailerClassMetaInfo();
        return true;
    }

    @Override
    public ByteCodeVisitor byteCodeVisitor() {
        return new _ByteCodeVisitor();
    }

    @Override
    public void scanFinished(String className) {
        classInfoBase().registerMailerMetaInfo(classInfo);
    }

    private MailerClassMetaInfoManager classInfoBase() {
        if (null == classInfoBase) {
            synchronized (this) {
                if (null == classInfoBase) {
                    classInfoBase = app().classLoader().mailerClassMetaInfoManager();
                }
            }
        }
        return classInfoBase;
    }

    public static boolean isMailerAnno(String desc) {
        return Type.getType(Mailer.class).getDescriptor().equals(desc);
    }

    public static boolean isTemplateContextAnno(String desc) {
        return Type.getType(TemplateContext.class).getDescriptor().equals(desc);
    }

    private class _ByteCodeVisitor extends ByteCodeVisitor {
        @Override
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            logger.trace("Scanning %s", name);
            classInfo.className(name);
            if (isAbstract(access)) {
                classInfo.setAbstract();
            }
            super.visit(version, access, name, signature, superName, interfaces);
        }

        @Override
        public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
            if (classInfo.isMailer() && AsmTypes.ACTION_CONTEXT_DESC.equals(desc)) {
                classInfo.ctxField(name, isPrivate(access));
            }
            return super.visitField(access, name, desc, signature, value);
        }

        @Override
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            AnnotationVisitor av = super.visitAnnotation(desc, visible);
            if (isMailerAnno(desc)) {
                classInfo.isMailer(true);
                return new MailerAnnotationVisitor(av);
            } else if (isTemplateContextAnno(desc)) {
                return new TemplateContextAnnotationVisitor(av);
            }
            return super.visitAnnotation(desc, visible);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
            if (!classInfo.isMailer() || !isEligibleMethod(access, name, desc)) {
                return mv;
            }
            return new SenderMethodVisitor(mv, access, name, desc, signature, exceptions);
        }

        private boolean isEligibleMethod(int access, String name, String desc) {
            return isPublic(access) && !isAbstract(access) && !isConstructor(name) && name.startsWith("send");
        }

        private class MailerAnnotationVisitor extends AnnotationVisitor {
            MailerAnnotationVisitor(AnnotationVisitor av) {
                super(ASM5, av);
            }

            @Override
            public void visit(String name, Object value) {
                if ("value".equals(name)) {
                    classInfo.configId(value.toString());
                }
                super.visit(name, value);
            }
        }

        private class TemplateContextAnnotationVisitor extends AnnotationVisitor {
            TemplateContextAnnotationVisitor(AnnotationVisitor av) {
                super(ASM5, av);
            }

            @Override
            public void visit(String name, Object value) {
                if ("value".equals(name)) {
                    classInfo.templateContext(value.toString());
                }
                super.visit(name, value);
            }
        }


        private class SenderMethodVisitor extends MethodVisitor implements Opcodes {

            private String methodName;
            private int access;
            private String desc;
            private String signature;
            private boolean requireScan;
            private SenderMethodMetaInfo methodInfo;
            private Map<Integer, List<ParamAnnoInfoTrait>> paramAnnoInfoList = new HashMap<>();
            private Map<Integer, List<GeneralAnnoInfo>> genericParamAnnoInfoList = new HashMap<>();

            SenderMethodVisitor(MethodVisitor mv, int access, String methodName, String desc, String signature, String[] exceptions) {
                super(ASM5, mv);
                this.access = access;
                this.methodName = methodName;
                this.desc = desc;
                this.signature = signature;
            }

            @Override
            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
                AnnotationVisitor av = super.visitAnnotation(desc, visible);
                if (isMailerAnno(desc)) {
                    if (null == methodInfo) {
                        methodInfo = new SenderMethodMetaInfo(classInfo);
                        classInfo.addSender(methodInfo);
                    }
                    return new SenderAnnotationVisitor(av);
                } else if (isTemplateContextAnno(desc)) {
                    if (null == methodInfo) {
                        methodInfo = new SenderMethodMetaInfo(classInfo);
                        classInfo.addSender(methodInfo);
                    }
                    return new _ByteCodeVisitor.TemplateContextAnnotationVisitor(av);
                }
                return av;
            }

            @Override
            public void visitEnd() {
                if (null == methodInfo) {
                    methodInfo = new SenderMethodMetaInfo(classInfo);
                    classInfo.addSender(methodInfo);
                }
                final SenderMethodMetaInfo info = methodInfo;
                info.name(methodName);
                boolean isStatic = AsmTypes.isStatic(access);
                if (isStatic) {
                    info.invokeStaticMethod();
                } else {
                    info.invokeInstanceMethod();
                }
                info.returnType(Type.getReturnType(desc));
                Type[] argTypes = Type.getArgumentTypes(desc);
                for (int i = 0; i < argTypes.length; ++i) {
                    Type type = argTypes[i];
                    HandlerParamMetaInfo param = new HandlerParamMetaInfo().type(type);
                    List<ParamAnnoInfoTrait> paraAnnoList = paramAnnoInfoList.get(i);
                    if (null != paraAnnoList) {
                        for (ParamAnnoInfoTrait trait : paraAnnoList) {
                            trait.attachTo(param);
                        }
                    }
                    List<GeneralAnnoInfo> list = genericParamAnnoInfoList.get(i);
                    if (null != list) {
                        param.addGeneralAnnotations(list);
                    }
                    info.addParam(param);
                }
                if (null != signature) {
                    SignatureReader sr = new SignatureReader(signature);
                    final $.Var<Integer> id = new $.Var<Integer>(-1);
                    sr.accept(new SignatureVisitor(ASM5) {

                        boolean startParsing;

                        @Override
                        public SignatureVisitor visitParameterType() {
                            id.set(id.get() + 1);
                            return this;
                        }

                        @Override
                        public SignatureVisitor visitTypeArgument(char wildcard) {
                            if (wildcard == '=') {
                                startParsing = true;
                            }
                            return this;
                        }

                        @Override
                        public void visitClassType(String name) {
                            if (startParsing) {
                                Type type = Type.getObjectType(name);
                                int n = id.get();
                                if (n < 0) {
                                    info.returnComponentType(type);
                                } else {
                                    info.param(n).componentType(type);
                                }
                            }
                            startParsing = false;
                        }
                    });
                }
                super.visitEnd();
            }

            private class SenderAnnotationVisitor extends AnnotationVisitor implements Opcodes {

                public SenderAnnotationVisitor(AnnotationVisitor av) {
                    super(ASM5, av);
                }

                @Override
                public void visit(String name, Object value) {
                    if ("value".equals(name)) {
                        methodInfo.configId(value.toString());
                    }
                    super.visit(name, value);
                }

            }

            private class TemplateContextAnnotationVisitor extends AnnotationVisitor implements Opcodes {

                public TemplateContextAnnotationVisitor(AnnotationVisitor av) {
                    super(ASM5, av);
                }

                @Override
                public void visit(String name, Object value) {
                    if ("value".equals(name)) {
                        methodInfo.templateContext(value.toString());
                    }
                    super.visit(name, value);
                }

            }

        }
    }

}
