/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
package org.apache.myfaces.extensions.scripting.core.engine.dependencyScan.core;

import org.objectweb.asm.*;
import org.objectweb.asm.signature.SignatureReader;
import org.apache.myfaces.extensions.scripting.core.engine.dependencyScan.api.DependencyRegistry;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A scan visitor on method level
 * to cope with method scoped dependencies like inlined
 * fully qualified names, annotations, local variables
 * etc...
 *
 * @author Werner Punz (latest modification by $Author$)
 * @version $Revision$ $Date$
 */

class MethodScanVisitor implements MethodVisitor {

    // static final Logger log = Logger.getLogger("ClassScanVisitor");

    String _currentlyVisitedClass = null;
    String _rootClass;
    Integer _engineType = null;
    DependencyRegistry _dependencyRegistry = null;

    static Logger _log = Logger.getLogger(MethodScanVisitor.class.getName());

    public MethodScanVisitor(Integer engineType, String rootClass, String currentlyVisitedClass, DependencyRegistry registry) {
        _currentlyVisitedClass = currentlyVisitedClass;
        _dependencyRegistry = registry;
        _engineType = engineType;
        _rootClass = rootClass;
    }

    public AnnotationVisitor visitAnnotationDefault() {
        return null;
    }

    public AnnotationVisitor visitAnnotation(String description, boolean b) {
        if(description != null) {
            registerDependency(Type.getType(description));
        }
        return null;
    }

    public AnnotationVisitor visitParameterAnnotation(int opCode, String description, boolean b) {
        if(description != null) {
            registerDependency(Type.getType(description));
        }
        return null;
    }

    public void visitAttribute(Attribute attribute) {
        if (_log.isLoggable(Level.FINEST))
            _log.log(Level.FINEST, "visitAttribute {0}", attribute.type);
    }

    public void visitCode() {
        //log.log(Level.INFO, "Method code");
    }

    public void visitFrame(int opCode1, int opCode2, Object[] objects, int opCode3, Object[] objects1) {
        if (_log.isLoggable(Level.FINEST))
            _log.log(Level.FINEST, "visitFrame {0}", "");

    }

    public void visitInsn(int opCode) {
    }

    public void visitIntInsn(int opCode1, int opCode2) {
    }

    public void visitVarInsn(int opCode1, int opCode2) {
    }

    public void visitTypeInsn(int opCode, String castType) {
        //cast
        // log.log(Level.INFO, "TypeInsn: {0} ", new String[]{castType});
        if (castType != null) {
            registerDependency(Type.getObjectType(castType));
            if (_log.isLoggable(Level.FINEST))
                _log.log(Level.FINEST, "visitTypeInsn {0}", castType);
        }
    }

    private void registerDependency(Type dependency) {
        
        String className = dependency.getClassName();

        
        if (className.endsWith("[]")) {
            className = className.substring(0, className.indexOf("["));
        }

        if (_dependencyRegistry != null) {
            _dependencyRegistry.addDependency(_engineType, _rootClass, _currentlyVisitedClass, className);
        }
    }

    /**
     * @param opCode     the opCode of the insert statement
     * @param owner      hosting classname of field (always the calling class afaik)
     * @param name       internal descriptor
     * @param descriptor field type
     */
    public void visitFieldInsn(int opCode, String owner, String name, String descriptor) {
        //    log.log(Level.INFO, "visitFieldInsn {0} {1} {2}", new Object[]{owner, name, descriptor});
        //we have to deal with static imports as special case of field insertions
        if (name != null && name.length() > 6 && name.startsWith("class$")) {
            //special fallback for groovy static imports which are added as fields
            name = "L" + name.substring(6).replaceAll("\\$", ".") + ";";
            registerDependency(Type.getType(name));
        }
        if (descriptor != null) {
            registerDependency(Type.getType(descriptor));
        }

        if (_log.isLoggable(Level.FINEST))
            _log.log(Level.FINEST, "visitFieldInsn {0}", descriptor);

    }

    /**
     * Method call
     *
     * @param opc   internal opcode
     * @param owner hosting classname of the method
     * @param name  method name
     * @param desc  descriptor string
     */
    public void visitMethodInsn(int opc, String owner, String name, String desc) {
        //s2 arguments list
        if (desc != null) {
            registerDependency(Type.getReturnType(desc));
            Type[] argumentTypes = Type.getArgumentTypes(desc);
            if (argumentTypes != null) {
                for (Type argumentType : argumentTypes) {
                    registerDependency(argumentType);
                }
            }
        }

        if (owner != null)
            registerDependency(Type.getObjectType(owner));

    }

    public void visitJumpInsn(int i, Label label) {

    }

    public void visitLabel(Label label) {

    }

    public void visitLdcInsn(Object o) {

    }

    public void visitIincInsn(int i, int i1) {

    }

    public void visitTableSwitchInsn(int i, int i1, Label label, Label[] labels) {

    }

    public void visitLookupSwitchInsn(Label label, int[] ints, Label[] labels) {

    }

    public void visitMultiANewArrayInsn(String s, int i) {
        if (_log.isLoggable(Level.FINEST))
            _log.log(Level.FINEST, "visitMultiANewArrayInsn {0}", s);
    }

    public void visitTryCatchBlock(Label label, Label label1, Label label2, String catchType) {
        //try catch block type information in the last string
        //log.log(Level.INFO, "visitTryCatchBlock: {0} {1} {2} {3}", new Object[]{label.toString(), label1.toString(), label2.toString(), catchType});
        if (catchType != null) {
            registerDependency(Type.getObjectType(catchType));
        }
    }

    public void visitLocalVariable(String name, String description, String signature, Label label, Label label1, int i) {
        //local variable on method level
        if (description != null) {
            registerDependency(Type.getType(description));
        }
        if (signature != null) {
            handleGenerics(signature);
        }
    }

    public void visitLineNumber(int i, Label label) {

    }

    public void visitMaxs(int i, int i1) {

    }

    public void visitEnd() {

    }

    private void handleGenerics(String signature) {
        if (signature != null && signature.contains("<")) {
            SignatureReader reader = new SignatureReader(signature);
            reader.acceptType(new DependencySignatureVisitor(_dependencyRegistry, _engineType, _rootClass, _currentlyVisitedClass));
        }
    }
}
