package org.skywalking.apm.agent.core.plugin;

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.TypeResolutionStrategy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.pool.TypePool;
import org.skywalking.apm.logging.ILog;
import org.skywalking.apm.logging.LogManager;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;

/**
 * A test entrance for enhancing class.
 * This should be used only in bytecode-manipulate test.
 * And make sure, all classes which need to be enhanced, must not be loaded.
 *
 * @author wusheng
 */
public class TracingBootstrap {
    private static final ILog logger = LogManager.getLogger(TracingBootstrap.class);

    private TracingBootstrap() {
    }

    /**
     * Main entrance for testing.
     *
     * @param args includes target classname ( which exists "public static void main(String[] args)" ) and arguments
     *             list.
     * @throws PluginException
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static void main(String[] args)
        throws PluginException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException,
        IllegalAccessException {
        if (args.length == 0) {
            throw new RuntimeException("bootstrap failure. need args[0] to be main class.");
        }

        List<AbstractClassEnhancePluginDefine> plugins = null;

        try {
            PluginBootstrap bootstrap = new PluginBootstrap();
            plugins = bootstrap.loadPlugins();
        } catch (Throwable t) {
            logger.error("PluginBootstrap start failure.", t);
        }

        for (AbstractClassEnhancePluginDefine plugin : plugins) {
            String enhanceClassName = plugin.enhanceClassName();
            TypePool.Resolution resolution = TypePool.Default.ofClassPath().describe(enhanceClassName);
            if (!resolution.isResolved()) {
                logger.error("Failed to resolve the class " + enhanceClassName, null);
                continue;
            }
            DynamicType.Builder<?> newClassBuilder =
                new ByteBuddy().rebase(resolution.resolve(), ClassFileLocator.ForClassLoader.ofClassPath());
            newClassBuilder = ((AbstractClassEnhancePluginDefine) plugin).define(enhanceClassName, newClassBuilder);
            newClassBuilder.make(new TypeResolutionStrategy.Active()).load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION)
                .getLoaded();
        }

        String[] newArgs = Arrays.copyOfRange(args, 1, args.length);

        Class.forName(args[0]).getMethod("main", String[].class).invoke(null, new Object[] {newArgs});
    }
}
