/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2018 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
 * or LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package org.glassfish.pfl.tf.tools.enhancer;

import org.glassfish.pfl.basic.contain.SynchronizedHolder;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.glassfish.pfl.tf.spi.EnhancedClassData;
import org.glassfish.pfl.tf.spi.MethodMonitorRegistry;
import org.glassfish.pfl.tf.spi.Util;
import org.glassfish.pfl.objectweb.asm.Label;

import org.glassfish.pfl.objectweb.asm.MethodVisitor;
import org.glassfish.pfl.objectweb.asm.Opcodes;
import org.glassfish.pfl.objectweb.asm.Type;
import org.glassfish.pfl.objectweb.asm.commons.LocalVariablesSorter;
import org.glassfish.pfl.objectweb.asm.tree.LabelNode;
import org.glassfish.pfl.objectweb.asm.tree.LocalVariableNode;

/**
 *
 * @author ken
 */
public class StaticInitVisitor extends LocalVariablesSorter {
    private final Util util ;
    private final EnhancedClassData ecd ;

    public StaticInitVisitor( final int access, final String desc,
        final MethodVisitor mv, Util util, EnhancedClassData ecd ) {

        super( access, desc, mv ) ;
        this.util = util ;
        this.ecd = ecd ;
        util.info( 2, "StaticInitVisitor created" ) ;
    }

    private LocalVariableNode defineLocal( MethodVisitor mv, String name, 
        Class<?> cls, Label start, Label end ) {

        Type type = Type.getType( cls ) ;
        int index = newLocal( type ) ;
        LabelNode snode = new LabelNode( start ) ;
        LabelNode enode = new LabelNode( end ) ;
        return new LocalVariableNode( name, type.getDescriptor(), null,
            snode, enode, index ) ;
    }

    private static final boolean ENABLED = false ;

    private void generateTraceMsg( MethodVisitor mv, String msg, int num ) {
        if (ENABLED && util.getDebug()) {
            final Label start = new Label() ;
            mv.visitLabel( start ) ;
            mv.visitLineNumber( num, start ) ;
            mv.visitFieldInsn( Opcodes.GETSTATIC, "java/lang/System", "out", 
                "Ljava/io/PrintStream;" ) ;
            mv.visitLdcInsn( msg );
            mv.visitMethodInsn( Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
                "println", "(Ljava/lang/String;)V");
        }
    }

    private static final boolean SHORT_FORM = true ;

    @Override
    public void visitCode() {
	if (SHORT_FORM) {
	    super.visitCode() ;
	    mv.visitLdcInsn( Type.getType( "L" + ecd.getClassName() + ";" ));
	    Type mmrType = Type.getType( MethodMonitorRegistry.class ) ;
	    String mdesc = "(Ljava/lang/Class;)V" ;
	    mv.visitMethodInsn( Opcodes.INVOKESTATIC,
		mmrType.getInternalName(), "registerClass", mdesc ) ;
	} else {
	    int line = 1 ;
	    util.info( 2, "StaticInitVisitor.visitCode" ) ;
	    super.visitCode() ;

	    Label start = new Label() ;
	    Label end = new Label() ;

	    mv.visitLabel( start ) ;

	    LocalVariableNode thisClass = defineLocal( mv, "thisClass",
		Class.class, start, end ) ;
	    LocalVariableNode mnameList = defineLocal( mv, "mnameList",
		List.class, start, end ) ;
	    LocalVariableNode holderMap = defineLocal( mv, "holderMap",
		Map.class, start, end ) ;

	    generateTraceMsg( mv, "initialize the holders", line++ ) ;
	    for (String str : ecd.getAnnotationToHolderName().values()) {
		generateTraceMsg( mv, "Generating to initialize holder " + str,
		    line++ ) ;
		util.info( 2, "Generating code to initialize holder " + str ) ;
		util.newWithSimpleConstructor( mv, SynchronizedHolder.class );
		mv.visitFieldInsn( Opcodes.PUTSTATIC,
		    ecd.getClassName(), str,
		    Type.getDescriptor(SynchronizedHolder.class ) ) ;
	    }

	    generateTraceMsg( mv, "Store the Class of this class", line++ );
	    mv.visitLdcInsn( Type.getType( "L" + ecd.getClassName() + ";" ));
	    mv.visitVarInsn( Opcodes.ASTORE, thisClass.index ) ;

	    generateTraceMsg( mv, "Create list of method names", line++ );
	    util.newWithSimpleConstructor( mv, ArrayList.class ) ;
	    mv.visitVarInsn( Opcodes.ASTORE, mnameList.index ) ;

	    for (String str : ecd.getMethodNames()) {
		util.info( 2, "Generating code to add " + str
                    + " to methodNames" ) ;
		mv.visitVarInsn( Opcodes.ALOAD, mnameList.index ) ;
		mv.visitLdcInsn( str );
		mv.visitMethodInsn( Opcodes.INVOKEINTERFACE,
		    "java/util/List", "add", "(Ljava/lang/Object;)Z" );
		mv.visitInsn( Opcodes.POP ) ;
	    }

	    generateTraceMsg( mv,
		"create map from MM annotation class to Holder and init",
                line++ ) ;
	    util.newWithSimpleConstructor( mv, HashMap.class ) ;
	    mv.visitVarInsn( Opcodes.ASTORE, holderMap.index ) ;

	    for (Map.Entry<String,String> entry :
		ecd.getAnnotationToHolderName().entrySet()) {

		util.info( 2, "Generating code to put " + entry.getKey() + "=>"
		    + entry.getValue() + " into holderMap" ) ;

		mv.visitVarInsn( Opcodes.ALOAD, holderMap.index ) ;

		Type annoType = Type.getType( "L" + entry.getKey() + ";" ) ;
		mv.visitLdcInsn( annoType );

		mv.visitFieldInsn( Opcodes.GETSTATIC, ecd.getClassName(),
		    entry.getValue(),
		    Type.getDescriptor(SynchronizedHolder.class ) ) ;

		mv.visitMethodInsn( Opcodes.INVOKEINTERFACE,
		    "java/util/Map", "put",
		    "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");

		mv.visitInsn( Opcodes.POP ) ;
	    }

	    generateTraceMsg( mv, "register with MethodMonitorRegistry",
                line++ ) ;
	    util.info( 2,
                "Generating code call MethodMonitorRegistry.registerClass" ) ;
	    mv.visitVarInsn( Opcodes.ALOAD, thisClass.index ) ;
	    mv.visitVarInsn( Opcodes.ALOAD, mnameList.index ) ;
	    mv.visitVarInsn( Opcodes.ALOAD, holderMap.index ) ;

	    Type mmrType = Type.getType( MethodMonitorRegistry.class ) ;
	    String mdesc =
                "(Ljava/lang/Class;Ljava/util/List;Ljava/util/Map;)V" ;
	    mv.visitMethodInsn( Opcodes.INVOKESTATIC,
		mmrType.getInternalName(), "registerClass", mdesc ) ;

	    mv.visitLabel( end ) ;

	    thisClass.accept( mv ) ;
	    mnameList.accept( mv ) ;
	    holderMap.accept( mv ) ;
	}
    }
}
