/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */
package org.jboss.as.ejb3.iiop.stub;
// because it calls some ProxyAssembler
// methods that currently are package
// accessible

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.rmi.RemoteException;

import org.jboss.as.server.deployment.ModuleClassFactory;
import org.jboss.as.ejb3.logging.EjbLogger;
import org.jboss.classfilewriter.ClassFile;
import org.jboss.classfilewriter.ClassMethod;
import org.jboss.classfilewriter.code.CodeAttribute;
import org.jboss.classfilewriter.util.Boxing;
import org.jboss.classfilewriter.util.DescriptorUtils;
import org.wildfly.iiop.openjdk.rmi.AttributeAnalysis;
import org.wildfly.iiop.openjdk.rmi.ExceptionAnalysis;
import org.wildfly.iiop.openjdk.rmi.InterfaceAnalysis;
import org.wildfly.iiop.openjdk.rmi.OperationAnalysis;
import org.wildfly.iiop.openjdk.rmi.RMIIIOPViolationException;
import org.wildfly.iiop.openjdk.rmi.marshal.CDRStream;
import org.wildfly.iiop.openjdk.rmi.marshal.strategy.StubStrategy;

/**
 * Utility class responsible for the dynamic generation of bytecodes of
 * IIOP stub classes.
 *
 * @author Stuart Douglas
 * @author <a href="mailto:reverbel@ime.usp.br">Francisco Reverbel</a>
 * @author <a href="mailto:ropalka@redhat.com">Richard Opalka</a>
 */
public class IIOPStubCompiler {

    public static final String ID_FIELD_NAME = "$ids";

    /**
     * Returns the name of the stub strategy field associated with the method
     * whose index is <code>methodIndex</code>.
     */
    private static String strategy(int methodIndex) {
        return "$s" + methodIndex;
    }

    /**
     * Returns the name of static initializer method associated with the method
     * whose index is <code>methodIndex</code>.
     */
    private static String init(int methodIndex) {
        return "$i" + methodIndex;
    }

    /**
     * Generates the code of a given method within a stub class.
     *
     * @param asm           the <code>ProxyAssembler</code> used to assemble
     *                      the method code
     * @param superclass    the superclass of the stub class within which the
     *                      method will be generated
     * @param m             a <code>Method</code> instance describing the
     *                      method declaration by an RMI/IDL interface
     * @param idlName       a string with the method name mapped to IDL
     * @param strategyField a string with the name of the strategy field that
     *                      will be associated with the generated method
     * @param initMethod    a string with the name of the static initialization
     *                      method that will be associated with the generated
     *                      method.
     */
    private static void generateMethodCode(ClassFile asm,
                                           Class<?> superclass,
                                           Method m,
                                           String idlName,
                                           String strategyField,
                                           String initMethod) {
        Class<?> returnType = m.getReturnType();
        Class<?>[] paramTypes = m.getParameterTypes();
        Class<?>[] exceptions = m.getExceptionTypes();

        // Generate a static field with the StubStrategy for the method
        asm.addField(Modifier.PRIVATE + Modifier.STATIC, strategyField, StubStrategy.class);

        // Generate the method code
        final CodeAttribute ca = asm.addMethod(m).getCodeAttribute();

        // The method code issues a call
        // super.invoke*(idlName, strategyField, args)
        ca.aload(0);
        ca.ldc(idlName);
        ca.getstatic(asm.getName(), strategyField, StubStrategy.class);

        // Push args
        if (paramTypes.length == 0) {
            ca.iconst(0);
            ca.anewarray(Object.class.getName());
            //asm.pushField(Util.class, "NOARGS");
        } else {
            ca.iconst(paramTypes.length);
            ca.anewarray(Object.class.getName());
            int index = 1;
            for (int j = 0; j < paramTypes.length; j++) {

                Class<?> type = paramTypes[j];

                ca.dup();
                ca.iconst(j);
                if (!type.isPrimitive()) {
                    // object or array
                    ca.aload(index);
                } else if (type.equals(double.class)) {
                    ca.dload(index);
                    Boxing.boxDouble(ca);
                    index++;
                } else if (type.equals(long.class)) {
                    ca.lload(index);
                    Boxing.boxLong(ca);
                    index++;
                } else if (type.equals(float.class)) {
                    ca.fload(index);
                    Boxing.boxFloat(ca);
                } else {
                    ca.iload(index);
                    Boxing.boxIfNessesary(ca, DescriptorUtils.makeDescriptor(type));
                }
                index++;
                ca.aastore();
            }
        }
        // Generate the call to an invoke* method ot the superclass
        String invoke = "invoke";
        String ret = "Ljava/lang/Object;";
        if (returnType.isPrimitive() && returnType != Void.TYPE) {
            String typeName = returnType.getName();
            invoke += (Character.toUpperCase(typeName.charAt(0))
                    + typeName.substring(1));
            ret = DescriptorUtils.makeDescriptor(returnType);
        }
        ca.invokevirtual(superclass.getName(), invoke, "(Ljava/lang/String;Lorg/wildfly/iiop/openjdk/rmi/marshal/strategy/StubStrategy;[Ljava/lang/Object;)" + ret);
        if (!returnType.isPrimitive() && returnType != Object.class) {
            ca.checkcast(returnType);
        }
        ca.returnInstruction();

        // Generate a static method that initializes the method's strategy field
        final CodeAttribute init = asm.addMethod(Modifier.PRIVATE + Modifier.STATIC,initMethod, "V").getCodeAttribute();
            int i;
            int len;

            // Push first argument for StubStrategy constructor:
            // array with abbreviated names of the param marshallers
            len = paramTypes.length;
            init.iconst(len);
            init.anewarray(String.class.getName());
            for (i = 0; i < len; i++) {
                init.dup();
                init.iconst(i);
                init.ldc(CDRStream.abbrevFor(paramTypes[i]));
                init.aastore();
            }

            // Push second argument for StubStrategy constructor:
            // array with exception repository ids
            len = exceptions.length;
            int n = 0;
            for (i = 0; i < len; i++) {
                if (!RemoteException.class.isAssignableFrom(exceptions[i])) {
                    n++;
                }
            }
            init.iconst(n);
            init.anewarray(String.class.getName());
            try {
                int j = 0;
                for (i = 0; i < len; i++) {
                    if (!RemoteException.class.isAssignableFrom(exceptions[i])) {
                        init.dup();
                        init.iconst(j);
                        init.ldc(
                                ExceptionAnalysis.getExceptionAnalysis(exceptions[i])
                                        .getExceptionRepositoryId());
                        init.aastore();
                        j++;
                    }
                }
            } catch (RMIIIOPViolationException e) {
                throw EjbLogger.ROOT_LOGGER.exceptionRepositoryNotFound(exceptions[i].getName(), e.getLocalizedMessage());
            }

            // Push third argument for StubStrategy constructor:
            // array with exception class names
            init.iconst(n);
            init.anewarray(String.class.getName());
            int j = 0;
            for (i = 0; i < len; i++) {
                if (!RemoteException.class.isAssignableFrom(exceptions[i])) {
                    init.dup();
                    init.iconst(j);
                    init.ldc(exceptions[i].getName());
                    init.aastore();
                    j++;
                }
            }

            // Push fourth argument for StubStrategy constructor:
            // abbreviated name of the return value marshaller
            init.ldc(CDRStream.abbrevFor(returnType));

            // Push fifth argument for StubStrategy constructor:
            // null (no ClassLoader specified)
            init.aconstNull();

            // Constructs the StubStrategy
            init.invokestatic(StubStrategy.class.getName(), "forMethod", "([Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)Lorg/wildfly/iiop/openjdk/rmi/marshal/strategy/StubStrategy;");

            // Set the strategy field of this stub class
            init.putstatic(asm.getName(), strategyField, StubStrategy.class);

            init.returnInstruction();
    }

    /**
     * Generates the bytecodes of a stub class for a given interface.
     *
     * @param interfaceAnalysis an <code>InterfaceAnalysis</code> instance
     *                        describing the RMI/IIOP interface to be
     *                        implemented by the stub class
     * @param superclass      the superclass of the stub class
     * @param stubClassName   the name of the stub class
     * @return a byte array with the generated bytecodes.
     */
    private static ClassFile generateCode(InterfaceAnalysis interfaceAnalysis,
                                       Class<?> superclass, String stubClassName) {
        final ClassFile asm =
                new ClassFile(stubClassName, superclass.getName(), null, ModuleClassFactory.INSTANCE, interfaceAnalysis.getCls().getName());

        int methodIndex = 0;

        AttributeAnalysis[] attrs = interfaceAnalysis.getAttributes();
        for (int i = 0; i < attrs.length; i++) {
            OperationAnalysis op = attrs[i].getAccessorAnalysis();
            generateMethodCode(asm, superclass, op.getMethod(), op.getIDLName(),
                    strategy(methodIndex), init(methodIndex));
            methodIndex++;
            op = attrs[i].getMutatorAnalysis();
            if (op != null) {
                generateMethodCode(asm, superclass,
                        op.getMethod(), op.getIDLName(),
                        strategy(methodIndex), init(methodIndex));
                methodIndex++;
            }
        }

        final OperationAnalysis[] ops = interfaceAnalysis.getOperations();
        for (int i = 0; i < ops.length; i++) {
            generateMethodCode(asm, superclass,
                    ops[i].getMethod(), ops[i].getIDLName(),
                    strategy(methodIndex), init(methodIndex));
            methodIndex++;
        }

        // Generate the constructor
        final ClassMethod ctor = asm.addMethod(Modifier.PUBLIC, "<init>", "V");
        ctor.getCodeAttribute().aload(0);
        ctor.getCodeAttribute().invokespecial(superclass.getName(), "<init>", "()V");
        ctor.getCodeAttribute().returnInstruction();

        // Generate the method _ids(), declared as abstract in ObjectImpl
        final String[] ids = interfaceAnalysis.getAllTypeIds();
        asm.addField(Modifier.PRIVATE + Modifier.STATIC, ID_FIELD_NAME, String[].class);
        final CodeAttribute idMethod = asm.addMethod(Modifier.PUBLIC + Modifier.FINAL, "_ids", "[Ljava/lang/String;").getCodeAttribute();
        idMethod.getstatic(stubClassName, ID_FIELD_NAME, "[Ljava/lang/String;");
        idMethod.returnInstruction();

        // Generate the static initializer
        final CodeAttribute clinit = asm.addMethod(Modifier.STATIC, "<clinit>", "V").getCodeAttribute();
        clinit.iconst(ids.length);
        clinit.anewarray(String.class.getName());
        for (int i = 0; i < ids.length; i++) {
            clinit.dup();
            clinit.iconst(i);
            clinit.ldc(ids[i]);
            clinit.aastore();
        }
        clinit.putstatic(stubClassName, ID_FIELD_NAME, "[Ljava/lang/String;");

        int n = methodIndex; // last methodIndex + 1
        for (methodIndex = 0; methodIndex < n; methodIndex++) {
            clinit.invokestatic(stubClassName, init(methodIndex), "()V");
        }
        clinit.returnInstruction();

        return asm;
    }

    /**
     * Generates the bytecodes of a stub class for a given interface.
     *
     * @param interfaceAnalysis an <code>InterfaceAnalysis</code> instance
     *                          describing the RMI/IIOP interface to be
     *                          implemented by the stub class
     * @param superclass        the superclass of the stub class
     * @param stubClassName     the name of the stub class
     * @return a byte array with the generated bytecodes.
     */
    private static ClassFile makeCode(InterfaceAnalysis interfaceAnalysis,
                                   Class<?> superclass, String stubClassName) {

        ClassFile code = generateCode(interfaceAnalysis, superclass, stubClassName);
        //try {
        //   String fname = stubClassName;
        //   fname = fname.substring(1 + fname.lastIndexOf('.')) + ".class";
        //   fname = "/tmp/" + fname;
        //   java.io.OutputStream cf = new java.io.FileOutputStream(fname);
        //   cf.write(code);
        //   cf.close();
        //   System.err.println("wrote " + fname);
        //}
        //catch(java.io.IOException ee) {
        //}
        return code;
    }

    // Public method ----------------------------------------------------------

    /**
     * Generates the bytecodes of a stub class for a given interface.
     *
     * @param intf          RMI/IIOP interface to be implemented by the
     *                      stub class
     * @param stubClassName the name of the stub class
     * @return a byte array with the generated bytecodes;
     */
    public static ClassFile compile(Class<?> intf, String stubClassName) {
        InterfaceAnalysis interfaceAnalysis = null;

        try {
            interfaceAnalysis = InterfaceAnalysis.getInterfaceAnalysis(intf);
        } catch (RMIIIOPViolationException e) {
            throw EjbLogger.ROOT_LOGGER.rmiIiopVoliation(e.getLocalizedMessage());
        }
        return makeCode(interfaceAnalysis, DynamicIIOPStub.class, stubClassName);
    }


    public Class<?> compileToClass(Class<?> intf, String stubClassName) {
        return compile(intf, stubClassName).define(intf.getClassLoader(), intf.getProtectionDomain());
    }

}
