// Path: instrumenter/src/main/java/com/offbynull/coroutines/instrumenter/asm/SearchUtils.java
// public static LocalVariableNode findLocalVariableNodeForInstruction(List<LocalVariableNode> lvnList, InsnList insnList,
//         final AbstractInsnNode insnNode, int idx) {
//     Validate.notNull(insnList);
//     Validate.notNull(insnNode);
//     Validate.isTrue(idx >= 0);
//         
//     int insnIdx = insnList.indexOf(insnNode);
//     Validate.isTrue(insnIdx != -1);
//         
//     lvnList = lvnList.stream()
//             .filter(lvn -> lvn.index == idx) // filter to lvns at the index we want
//             .filter(lvn -> {                 // filter to lvns that's scope starts before the instruction we want
//                 AbstractInsnNode currentInsnNode = insnNode.getPrevious();
//                 while (currentInsnNode != null) {
//                     if (currentInsnNode == lvn.start) {
//                         return true;
//                     }
//                     currentInsnNode = currentInsnNode.getPrevious();
//                 }
//                 return false;
//             })
//             .filter(lvn -> {                 // filter to lvns that's scope stops after the instruction we want
//                 AbstractInsnNode currentInsnNode = insnNode.getNext();
//                 while (currentInsnNode != null) {
//                     if (currentInsnNode == lvn.end) {
//                         return true;
//                     }
//                     currentInsnNode = currentInsnNode.getNext();
//                 }
//                 return false;
//             })
//             .collect(Collectors.toList());
//         
//         
//     // If we don't have any LVNs at this point, return null
//     if (lvnList.isEmpty()) {
//         return null;
//     }
// 
//         
//     // Should this be a list or should it always be a single entry? The problem is that there's nothing stopping multiple LVN's coming
//     // back for some instruction+lvt_index combination.
//     //
//     // The one thing we can be sure of at this point is that IF WE GET BACK MULTIPLE LVNs, THEY MUST OVERLAP AT SOME POINT.
//         
//     // The assumption at this point is...
//     //   1. LVNs are scoped such that the index of start label is BEFORE the index of the end label
//     //   2. LVNs must fully overlap, meaning that they can't go past each other's boundaries
//     //   3. LVNs can end at the same label, but they can't start at the same label
//     //        e.g. not allowed
//     //             x-----------x
//     //                   x--------x
//     //        e.g. allowed
//     //             x-----------x
//     //               x--------x
//     //        e.g. allowed
//     //              x--------x
//     //             x-----------x
//     //        e.g. not allowed
//     //             x--------x
//     //             x-----------x
//     //
//     // Error out if you spot this -- someone will eventually report it and it'll get fixed
//         
//         // the following blocks of code are far from efficient, but they're easily readable/understandable
//     for (LocalVariableNode lvn : lvnList) { // test condition 1
//         int start = insnList.indexOf(lvn.start);
//         int end = insnList.indexOf(lvn.end);
//         Validate.validState(end > start);
//     }
// 
//     for (LocalVariableNode lvnTester : lvnList) { // test condition 2 and 3
//         int startTester = insnList.indexOf(lvnTester.start);
//         int endTester = insnList.indexOf(lvnTester.end);
//         Range rangeTester = Range.between(startTester, endTester);
//             
//         for (LocalVariableNode lvnTestee : lvnList) {
//             if (lvnTester == lvnTestee) {
//                 continue;
//             }
//                 
//             int startTestee = insnList.indexOf(lvnTestee.start);
//             int endTestee = insnList.indexOf(lvnTestee.end);
//             Range rangeTestee = Range.between(startTestee, endTestee);
//                 
//             Range intersectRange = rangeTester.intersectionWith(rangeTestee); 
//             Validate.validState(intersectRange.equals(rangeTester) || intersectRange.equals(rangeTestee)); // test condition 2
//                 
//             Validate.validState(rangeTester.getMinimum() != rangeTestee.getMinimum()); // test condition 3
//         }
//     }
//         
//         
//     // Given that all the above assumptions are correct, the LVN with the smallest range will be the correct one. It's the one that's
//     // most tightly scoped around the instruction.
//     //   e.g.
//     //    x------------i----x
//     //        x--------i-x
//     //             x---i-x
//         
//     return Collections.min(lvnList, (o1, o2) -> {
//         int o1Len = insnList.indexOf(o1.end) - insnList.indexOf(o1.start);
//         int o2Len = insnList.indexOf(o2.end) - insnList.indexOf(o2.start);
//         return Integer.compare(o1Len, o2Len);
//     });
// }


import static com.offbynull.coroutines.instrumenter.asm.SearchUtils.findLocalVariableNodeForInstruction;
import java.util.Locale;
import org.apache.commons.collections4.list.UnmodifiableList;
import org.apache.commons.lang3.Validate;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.BasicValue;
            // container[4] has local variables that are Objects
            // container[5] has operands that are bytes/shorts/ints
            // container[6] has operands that are floats
            // container[7] has operands that are longs
            // container[8] has operands that are doubles
            // container[9] has operands that are Objects
            detailLocals(cp, methodNode, output);
            detailOperands(cp, output);

            output.append('\n');
        }
        
        output.append('\n');
    }

    private void detailLocals(ContinuationPoint cp, MethodNode methodNode, StringBuilder output) {
        int intIdx = 0;
        int floatIdx = 0;
        int doubleIdx = 0;
        int longIdx = 0;
        int objectIdx = 0;

        for (int j = 0; j < cp.getFrame().getLocals(); j++) {
            BasicValue local = cp.getFrame().getLocal(j);

            if (local.getType() == null) {
                // unused in frame, so skip over it
                continue;
            }

// Path: instrumenter/src/main/java/com/offbynull/coroutines/instrumenter/asm/SearchUtils.java
// public static LocalVariableNode findLocalVariableNodeForInstruction(List<LocalVariableNode> lvnList, InsnList insnList,
//         final AbstractInsnNode insnNode, int idx) {
//     Validate.notNull(insnList);
//     Validate.notNull(insnNode);
//     Validate.isTrue(idx >= 0);
//         
//     int insnIdx = insnList.indexOf(insnNode);
//     Validate.isTrue(insnIdx != -1);
//         
//     lvnList = lvnList.stream()
//             .filter(lvn -> lvn.index == idx) // filter to lvns at the index we want
//             .filter(lvn -> {                 // filter to lvns that's scope starts before the instruction we want
//                 AbstractInsnNode currentInsnNode = insnNode.getPrevious();
//                 while (currentInsnNode != null) {
//                     if (currentInsnNode == lvn.start) {
//                         return true;
//                     }
//                     currentInsnNode = currentInsnNode.getPrevious();
//                 }
//                 return false;
//             })
//             .filter(lvn -> {                 // filter to lvns that's scope stops after the instruction we want
//                 AbstractInsnNode currentInsnNode = insnNode.getNext();
//                 while (currentInsnNode != null) {
//                     if (currentInsnNode == lvn.end) {
//                         return true;
//                     }
//                     currentInsnNode = currentInsnNode.getNext();
//                 }
//                 return false;
//             })
//             .collect(Collectors.toList());
//         
//         
//     // If we don't have any LVNs at this point, return null
//     if (lvnList.isEmpty()) {
//         return null;
//     }
// 
//         
//     // Should this be a list or should it always be a single entry? The problem is that there's nothing stopping multiple LVN's coming
//     // back for some instruction+lvt_index combination.
//     //
//     // The one thing we can be sure of at this point is that IF WE GET BACK MULTIPLE LVNs, THEY MUST OVERLAP AT SOME POINT.
//         
//     // The assumption at this point is...
//     //   1. LVNs are scoped such that the index of start label is BEFORE the index of the end label
//     //   2. LVNs must fully overlap, meaning that they can't go past each other's boundaries
//     //   3. LVNs can end at the same label, but they can't start at the same label
//     //        e.g. not allowed
//     //             x-----------x
//     //                   x--------x
//     //        e.g. allowed
//     //             x-----------x
//     //               x--------x
//     //        e.g. allowed
//     //              x--------x
//     //             x-----------x
//     //        e.g. not allowed
//     //             x--------x
//     //             x-----------x
//     //
//     // Error out if you spot this -- someone will eventually report it and it'll get fixed
//         
//         // the following blocks of code are far from efficient, but they're easily readable/understandable
//     for (LocalVariableNode lvn : lvnList) { // test condition 1
//         int start = insnList.indexOf(lvn.start);
//         int end = insnList.indexOf(lvn.end);
//         Validate.validState(end > start);
//     }
// 
//     for (LocalVariableNode lvnTester : lvnList) { // test condition 2 and 3
//         int startTester = insnList.indexOf(lvnTester.start);
//         int endTester = insnList.indexOf(lvnTester.end);
//         Range rangeTester = Range.between(startTester, endTester);
//             
//         for (LocalVariableNode lvnTestee : lvnList) {
//             if (lvnTester == lvnTestee) {
//                 continue;
//             }
//                 
//             int startTestee = insnList.indexOf(lvnTestee.start);
//             int endTestee = insnList.indexOf(lvnTestee.end);
//             Range rangeTestee = Range.between(startTestee, endTestee);
//                 
//             Range intersectRange = rangeTester.intersectionWith(rangeTestee); 
//             Validate.validState(intersectRange.equals(rangeTester) || intersectRange.equals(rangeTestee)); // test condition 2
//                 
//             Validate.validState(rangeTester.getMinimum() != rangeTestee.getMinimum()); // test condition 3
//         }
//     }
//         
//         
//     // Given that all the above assumptions are correct, the LVN with the smallest range will be the correct one. It's the one that's
//     // most tightly scoped around the instruction.
//     //   e.g.
//     //    x------------i----x
//     //        x--------i-x
//     //             x---i-x
//         
//     return Collections.min(lvnList, (o1, o2) -> {
//         int o1Len = insnList.indexOf(o1.end) - insnList.indexOf(o1.start);
//         int o2Len = insnList.indexOf(o2.end) - insnList.indexOf(o2.start);
//         return Integer.compare(o1Len, o2Len);
//     });
// }


// Path: instrumenter/src/main/java/com/offbynull/coroutines/instrumenter/SerializationDetailer.java
import static com.offbynull.coroutines.instrumenter.asm.SearchUtils.findLocalVariableNodeForInstruction;
import java.util.Locale;
import org.apache.commons.collections4.list.UnmodifiableList;
import org.apache.commons.lang3.Validate;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.BasicValue;

            // container[4] has local variables that are Objects
            // container[5] has operands that are bytes/shorts/ints
            // container[6] has operands that are floats
            // container[7] has operands that are longs
            // container[8] has operands that are doubles
            // container[9] has operands that are Objects
            detailLocals(cp, methodNode, output);
            detailOperands(cp, output);

            output.append('\n');
        }
        
        output.append('\n');
    }

    private void detailLocals(ContinuationPoint cp, MethodNode methodNode, StringBuilder output) {
        int intIdx = 0;
        int floatIdx = 0;
        int doubleIdx = 0;
        int longIdx = 0;
        int objectIdx = 0;

        for (int j = 0; j < cp.getFrame().getLocals(); j++) {
            BasicValue local = cp.getFrame().getLocal(j);

            if (local.getType() == null) {
                // unused in frame, so skip over it
                continue;
            }

            LocalVariableNode lvn = findLocalVariableNodeForInstruction(