/*
 * 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.bcel;

import static org.junit.jupiter.api.Assertions.fail;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.junit.jupiter.api.Test;

public final class PerformanceTest {

    private static final boolean REPORT = Boolean.parseBoolean(System.getProperty("PerformanceTest.report", "true"));

    private static byte[] read(final InputStream is) throws IOException {
        if (is == null) {
            throw new IOException("Class not found");
        }
        byte[] b = new byte[is.available()];
        int len = 0;
        while (true) {
            final int n = is.read(b, len, b.length - len);
            if (n == -1) {
                if (len < b.length) {
                    b = Arrays.copyOf(b, len);
                }
                return b;
            }
            len += n;
            if (len == b.length) {
                final byte[] c = new byte[b.length + 1000];
                System.arraycopy(b, 0, c, 0, len);
                b = c;
            }
        }
    }

    private static void test(final File lib) throws IOException {
        final NanoTimer total = new NanoTimer();
        final NanoTimer parseTime = new NanoTimer();
        final NanoTimer cgenTime = new NanoTimer();
        final NanoTimer mgenTime = new NanoTimer();
        final NanoTimer mserTime = new NanoTimer();
        final NanoTimer serTime = new NanoTimer();

        System.out.println("Parsing " + lib);

        total.start();
        try (JarFile jar = new JarFile(lib)) {
            final Enumeration<?> en = jar.entries();

            while (en.hasMoreElements()) {
                final JarEntry e = (JarEntry) en.nextElement();
                if (e.getName().endsWith(JavaClass.EXTENSION)) {
                    byte[] bytes;
                    try (InputStream in = jar.getInputStream(e)) {
                        bytes = read(in);
                    }

                    parseTime.start();
                    final JavaClass clazz = new ClassParser(new ByteArrayInputStream(bytes), e.getName()).parse();
                    parseTime.stop();

                    cgenTime.start();
                    final ClassGen cg = new ClassGen(clazz);
                    cgenTime.stop();

                    final Method[] methods = cg.getMethods();
                    for (final Method m : methods) {
                        mgenTime.start();
                        final MethodGen mg = new MethodGen(m, cg.getClassName(), cg.getConstantPool());
                        final InstructionList il = mg.getInstructionList();
                        mgenTime.stop();

                        mserTime.start();
                        if (il != null) {
                            mg.getInstructionList().setPositions();
                            mg.setMaxLocals();
                            mg.setMaxStack();
                        }
                        cg.replaceMethod(m, mg.getMethod());
                        mserTime.stop();
                    }

                    serTime.start();
                    cg.getJavaClass().getBytes();
                    serTime.stop();
                }
            }
        }
        total.stop();
        if (REPORT) {
            System.out.println("ClassParser.parse: " + parseTime);
            System.out.println("ClassGen.init: " + cgenTime);
            System.out.println("MethodGen.init: " + mgenTime);
            System.out.println("MethodGen.getMethod: " + mserTime);
            System.out.println("ClassGen.getJavaClass.getBytes: " + serTime);
            System.out.println("Total: " + total);
            System.out.println();
        }
    }

    @Test
    public void testPerformance() {
        final File javaLib = new File(System.getProperty("java.home"), "lib");
        javaLib.listFiles(file -> {
            if (file.getName().endsWith(".jar")) {
                try {
                    test(file);
                } catch (final IOException e) {
                    fail(e.getMessage());
                }
            }
            return false;
        });
    }

}
