/*
 * Copyright 2016 Sam Sun <me@samczsun.com>
 *
 *    Licensed 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 com.samczsun.helios.transformers.disassemblers;

import com.android.dx.command.dexer.Main;
import com.android.dx.dex.DexFormat;
import com.android.dx.dex.DexOptions;
import org.apache.commons.io.output.StringBuilderWriter;
import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.baksmali.baksmaliOptions;
import org.jf.dexlib2.Opcodes;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.dexlib2.iface.DexFile;
import org.jf.util.IndentingWriter;
import org.objectweb.asm.tree.ClassNode;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;

public class BaksmaliDisassembler extends Disassembler {
    private static final MethodHandle OUTPUT_DEX;
    private static final MethodHandle ARGS;
    private static final MethodHandle PROCESS_FILE_BYTES;
    private static final MethodHandle WRITE_DEX;

    static {
        Class<?> dexer = com.android.dx.command.dexer.Main.class;
        try {
            Constructor<MethodHandles.Lookup> lookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
            lookupConstructor.setAccessible(true);
            MethodHandles.Lookup lookup = lookupConstructor.newInstance(Object.class, -1);
            OUTPUT_DEX = lookup.findStaticSetter(dexer, "outputDex", com.android.dx.dex.file.DexFile.class);
            ARGS = lookup.findStaticSetter(dexer, "args", Main.Arguments.class);
            PROCESS_FILE_BYTES = lookup.findStatic(dexer, "processFileBytes", MethodType.methodType(boolean.class, String.class, long.class, byte[].class));
            WRITE_DEX = lookup.findStatic(dexer, "writeDex", MethodType.methodType(byte[].class));
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    public BaksmaliDisassembler() {
        super("baksmali-disassembler", "Baksmali Disassembler");
    }

    @Override
    public boolean isApplicable(String className) {
        return className.endsWith(".class");
    }

    public boolean disassembleClassNode(ClassNode cn, byte[] b, StringBuilder output) {
        try {
            DexOptions dexOptions = new DexOptions();
            dexOptions.targetApiLevel = DexFormat.API_NO_EXTENDED_OPCODES;
            OUTPUT_DEX.invoke(new com.android.dx.dex.file.DexFile(dexOptions));
            Main.Arguments args = new Main.Arguments();
            args.parse(new String[] {"--no-strict", "Helios.class"});
            ARGS.invoke(args);

            PROCESS_FILE_BYTES.invoke("Helios.class", System.currentTimeMillis(), b);
            byte[] bytes = (byte[]) WRITE_DEX.invoke();

            OUTPUT_DEX.invoke((Object) null);
            ARGS.invoke((Object) null);

            baksmaliOptions options = new baksmaliOptions();
            options.deodex = true;
            DexFile file = new DexBackedDexFile(Opcodes.forApi(options.apiLevel), bytes);
            ClassDefinition classDefinition = new ClassDefinition(options, file.getClasses().iterator().next());
            classDefinition.writeTo(new IndentingWriter(new StringBuilderWriter(output)));
            return true;
        } catch (final Throwable e) {
            output.setLength(0);
            output.append(parseException(e));
            return false;
        }
    }
}

@@ -796,32 +796,91 @@
 dex.DexOptions;%0A
+import org.apache.commons.io.output.ByteArrayOutputStream;%0A
 import org.apach
@@ -912,32 +912,32 @@
 gBuilderWriter;%0A
-
 import org.jf.ba
@@ -1082,32 +1082,70 @@
 xBackedDexFile;%0A
+import org.jf.dexlib2.iface.ClassDef;%0A
 import org.jf.de
@@ -1243,16 +1243,73 @@
 sNode;%0A%0A
+import java.io.OutputStream;%0Aimport java.io.PrintStream;%0A
 import j
@@ -1450,16 +1450,43 @@
 tructor;
+%0Aimport java.util.Iterator;
 %0A%0Apublic
@@ -2991,32 +2991,214 @@
 %7B%0A        try %7B%0A
+            PrintStream oldErr = System.err;%0A            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();%0A            System.setErr(new PrintStream(outputStream));%0A%0A
             DexO
@@ -3481,17 +3481,16 @@
 String%5B%5D
- 
 %7B%22--no-s
@@ -3959,24 +3959,173 @@
 l), bytes);%0A
+            Iterator%3C? extends ClassDef%3E it = file.getClasses().iterator();%0A%0A            System.setErr(oldErr);%0A%0A            if (it.hasNext()) %7B%0A    
             
@@ -4191,36 +4191,10 @@
 ns, 
-file.getClasses().iterator()
+it
 .nex
@@ -4195,24 +4195,28 @@
 it.next());%0A
+    
             
@@ -4302,24 +4302,28 @@
             
+  
+  
 return true;
@@ -4319,24 +4319,202 @@
 eturn true;%0A
+            %7D else %7B%0A                throw new IllegalStateException(%22Baksmali failed to read the class file%5Cn%22 + new String(outputStream.toByteArray(), %22UTF-8%22));%0A            %7D%0A
         %7D ca

Update baksmali error