/*
 * 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
 *
 *   https://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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.util.stream.Stream;

import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.LineNumber;
import org.apache.bcel.classfile.LineNumberTable;
import org.apache.bcel.classfile.LocalVariable;
import org.apache.bcel.classfile.LocalVariableTable;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.Type;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class PLSETest extends AbstractTest {

    /**
     * BCEL-208: A couple of methods in MethodGen.java need to test for an empty instruction list.
     */
    @Test
    void testBCEL208() throws ClassNotFoundException {
        final JavaClass clazz = getTestJavaClass(PACKAGE_BASE_NAME + ".data.PLSETestClass");
        final ClassGen gen = new ClassGen(clazz);
        final ConstantPoolGen pool = gen.getConstantPool();
        final Method m = gen.getMethodAt(1);
        final MethodGen mg = new MethodGen(m, gen.getClassName(), pool);
        mg.setInstructionList(null);
        mg.addLocalVariable("local2", Type.INT, null, null);
        // currently, this will cause null pointer exception
        mg.getLocalVariableTable(pool);
    }

    /**
     * BCEL-262:
     */
    @Test
    void testBCEL262() throws ClassNotFoundException {
        final JavaClass clazz = getTestJavaClass(PACKAGE_BASE_NAME + ".data.PLSETestEnum");
        final ClassGen gen = new ClassGen(clazz);
        final ConstantPoolGen pool = gen.getConstantPool();
        // get the values() method
        final Method m = gen.getMethodAt(0);
        final MethodGen mg = new MethodGen(m, gen.getClassName(), pool);
        final InstructionList il = mg.getInstructionList();
        // get the invokevirtual instruction
        final InstructionHandle ih = il.findHandle(3);
        final InvokeInstruction ii = (InvokeInstruction) ih.getInstruction();
        // without fix, the getClassName() will throw:
        // java.lang.IllegalArgumentException: Cannot be used on an array type
        final String cn = ii.getClassName(pool);
        assertEquals("[Lorg.apache.bcel.data.PLSETestEnum;", cn);
    }

    /**
     * BCEL-295:
     */
    @Test
    void testBCEL295() throws Exception {
        final JavaClass clazz = getTestJavaClass(PACKAGE_BASE_NAME + ".data.PLSETestClass2");
        final ClassGen cg = new ClassGen(clazz);
        final ConstantPoolGen pool = cg.getConstantPool();
        final Method m = cg.getMethodAt(1); // 'main'
        final LocalVariableTable lvt = m.getLocalVariableTable();
        final LocalVariable lv = lvt.getLocalVariable(2, 4); // 'i'
        // System.out.println(lv);
        final MethodGen mg = new MethodGen(m, cg.getClassName(), pool);
        final LocalVariableTable newLvt = mg.getLocalVariableTable(mg.getConstantPool());
        final LocalVariable newLv = newLvt.getLocalVariable(2, 4); // 'i'
        // System.out.println(new_lv);
        assertEquals(lv.getLength(), newLv.getLength(), "live range length");
    }

    /**
     * BCEL-361: LineNumber.toString() treats code offset as signed
     */
    @Test
    void testBCEL361() throws Exception {
        final JavaClass clazz = getTestJavaClass(PACKAGE_BASE_NAME + ".data.LargeMethod");
        final Method[] methods = clazz.getMethods();
        final Method m = methods[0];
        // System.out.println(m.getName());
        final Code code = m.getCode();
        final LineNumberTable lnt = code.getLineNumberTable();
        final LineNumber[] lineNumbers = lnt.getLineNumberTable();
        final String data = lineNumbers[lineNumbers.length - 1].toString();
        // System.out.println(data);
        // System.out.println(data.contains("-"));
        assertFalse(data.contains("-"), "code offsets must be positive");
        Stream.of(lineNumbers).forEach(ln -> assertFalse(ln.getLineNumber() < 0));
        Stream.of(lineNumbers).forEach(ln -> assertFalse(ln.getStartPC() < 0));
    }

    /**
     * BCEL-79:
     */
    @Test
    void testBCEL79() throws ClassNotFoundException {
        final JavaClass clazz = getTestJavaClass(PACKAGE_BASE_NAME + ".data.PLSETestClass");
        final ClassGen gen = new ClassGen(clazz);
        final ConstantPoolGen pool = gen.getConstantPool();
        final Method m = gen.getMethodAt(2);
        final LocalVariableTable lvt = m.getLocalVariableTable();
        // System.out.println(lvt);
        // System.out.println(lvt.getTableLength());
        final MethodGen mg = new MethodGen(m, gen.getClassName(), pool);
        final LocalVariableTable newLvt = mg.getLocalVariableTable(mg.getConstantPool());
        // System.out.println(new_lvt);
        assertEquals(lvt.getTableLength(), newLvt.getTableLength(), "number of locals");
    }

    /**
     * Test to improve BCEL tests code coverage for classfile/Utility.java.
     */
    @ParameterizedTest
    @ValueSource(booleans = { true, false })
    void testCoverage(final boolean compress) throws ClassNotFoundException, java.io.IOException {
        // load a class with a wide variety of byte codes - including tableswitch and lookupswitch
        final JavaClass clazz = getTestJavaClass(PACKAGE_BASE_NAME + ".data.ConstantPoolX");
        for (final Method m : clazz.getMethods()) {
            final String signature = m.getSignature();
            Utility.methodTypeToSignature(Utility.methodSignatureReturnType(signature), Utility.methodSignatureArgumentTypes(signature)); // discard result
            final Code code = m.getCode();
            if (code != null) {
                // TODO: need for real assertions here
                final String encoded = Utility.encode(code.getCode(), compress);
                assertNotNull(encoded);
                // following statement will throw exeception without classfile/Utility.encode fix
                assertNotNull(Utility.decode(encoded, compress));
                assertNotNull(code.toString());
            }
        }
    }
}
