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

import static org.junit.Assert.*;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.bcel.classfile.JavaClass;
import org.junit.Test;


public class BCELifierTestCase {

    @Test
    public void test() throws Exception {
        final OutputStream os = new ByteArrayOutputStream();
        final JavaClass java_class = BCELifier.getJavaClass("Java8Example");
        assertNotNull(java_class);
        final BCELifier bcelifier = new BCELifier(java_class, os);
        bcelifier.start();
    }

    /*
     * Dump a class using "javap" and compare with the same class recreated
     * using BCELifier, "javac", "java" and dumped with "javap"
     * TODO: detect if JDK present and skip test if not
     */
    @Test
    public void testJavapCompare() throws Exception {
        testClassOnPath("target/test-classes/Java8Example.class");
    }

    private void testClassOnPath(final String javaClass) throws Exception {
        // Get javap of the input class
        final String initial = exec(null, "javap", "-p", "-c", javaClass);

        final File workDir = new File("target");
        final File infile = new File(javaClass);
        final JavaClass java_class = BCELifier.getJavaClass(infile.getName().replace(".class", ""));
        assertNotNull(java_class);
        final File outfile = new File(workDir, infile.getName().replace(".class", "Creator.java"));
        try (FileOutputStream fos = new FileOutputStream(outfile)) {
            final BCELifier bcelifier = new BCELifier(java_class, fos);
            bcelifier.start();
        }
        exec(workDir, "javac", "-cp", "classes", outfile.getName(), "-source", "1.8", "-target", "1.8");
        exec(workDir, "java", "-cp", "." + File.pathSeparator + "classes", outfile.getName().replace(".java", ""));
        final String output = exec(workDir, "javap", "-p", "-c", infile.getName());
        assertEquals(canonHashRef(initial), canonHashRef(output));
    }

    // Canonicalise the javap output so it compares better
    private String canonHashRef(String input) {
        input = input.replaceAll("#\\d+", "#n"); // numbers may vary in length
        input = input.replaceAll(" +", " "); // collapse spaces
        input = input.replaceAll("//.+",""); // comments may vary
        return input;
    }

    private String exec(final File workDir, final String... args) throws Exception {
        // System.err.println(java.util.Arrays.toString(args));
        final ProcessBuilder pb = new ProcessBuilder(args);
        pb.directory(workDir);
        final Process proc = pb.start();
        try (BufferedInputStream is = new BufferedInputStream(proc.getInputStream());
                InputStream es = proc.getErrorStream()) {
            proc.waitFor();
            final byte[] buff = new byte[2048];
            int len;
            while ((len = es.read(buff)) != -1) {
                System.err.print(new String(buff, 0, len));
            }

            final StringBuilder sb = new StringBuilder();
            while ((len = is.read(buff)) != -1) {
                sb.append(new String(buff, 0, len));
            }
            return sb.toString();
        }
    }

}
