/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package jogamp.graph.font.typecast.ot;

/**
 * @author <a href="mailto:david.schweinsberg@gmail.com">David Schweinsberg</a>
 */
public class Disassembler {

    /**
     * Advance the instruction pointer to the next executable opcode.
     * This will be the next byte, unless the current opcode is a push
     * instruction, in which case it will be the byte immediately beyond
     * the last data byte.
     * @param ip The current instruction pointer
     * @return The new instruction pointer
     */
    private static int advanceIP(final short[] instructions, int ip) {

        // The high word specifies font, cvt, or glyph program
        int i = ip & 0xffff;
        int dataCount;
        ip++;
        if (Mnemonic.NPUSHB == instructions[i]) {
            // Next byte is the data byte count
            dataCount = instructions[++i];
            ip += dataCount + 1;
        } else if (Mnemonic.NPUSHW == instructions[i]) {
            // Next byte is the data word count
            dataCount = instructions[++i];
            ip += dataCount*2 + 1;
        } else if (Mnemonic.PUSHB == (instructions[i] & 0xf8)) {
            dataCount = (short)((instructions[i] & 0x07) + 1);
            ip += dataCount;
        } else if (Mnemonic.PUSHW == (instructions[i] & 0xf8)) {
            dataCount = (short)((instructions[i] & 0x07) + 1);
            ip += dataCount*2;
        }
        return ip;
    }

    private static short getPushCount(final short[] instructions, final int ip) {
        final short instr = instructions[ip & 0xffff];
        if ((Mnemonic.NPUSHB == instr) || (Mnemonic.NPUSHW == instr)) {
            return instructions[(ip & 0xffff) + 1];
        } else if ((Mnemonic.PUSHB == (instr & 0xf8)) || (Mnemonic.PUSHW == (instr & 0xf8))) {
            return (short)((instr & 0x07) + 1);
        }
        return 0;
    }

    private static int[] getPushData(final short[] instructions, final int ip) {
        final int count = getPushCount(instructions, ip);
        final int[] data = new int[count];
        final int i = ip & 0xffff;
        final short instr = instructions[i];
        if (Mnemonic.NPUSHB == instr) {
            for (int j = 0; j < count; j++) {
                data[j] = instructions[i + j + 2];
            }
        } else if (Mnemonic.PUSHB == (instr & 0xf8)) {
            for (int j = 0; j < count; j++) {
                data[j] = instructions[i + j + 1];
            }
        } else if (Mnemonic.NPUSHW == instr) {
            for (int j = 0; j < count; j++) {
                data[j] = (instructions[i + j*2 + 2] << 8) | instructions[i + j*2 + 3];
            }
        } else if (Mnemonic.PUSHW == (instr & 0xf8)) {
            for (int j = 0; j < count; j++) {
                data[j] = (instructions[i + j*2 + 1] << 8) | instructions[i + j*2 + 2];
            }
        }
        return data;
    }

     public static String disassemble(final short[] instructions, final int leadingSpaces) {
        final StringBuilder sb = new StringBuilder();
        int ip = 0;
        while (ip < instructions.length) {
            for (int i = 0; i < leadingSpaces; i++) {
                sb.append(" ");
            }
            sb.append(Fmt.pad(3, ip)).append(": ");
            sb.append(Mnemonic.getMnemonic(instructions[ip]));
            if (getPushCount(instructions, ip) > 0) {
                final int[] data = getPushData(instructions, ip);
                for (final int datum : data) {
                    if ((instructions[ip] == Mnemonic.PUSHW) ||
                            (instructions[ip] == Mnemonic.NPUSHW)) {
                        sb.append(" ").append((short) datum);
                    } else {
                        sb.append(" ").append(datum);
                    }
                }
            }
            sb.append("\n");
            ip = advanceIP(instructions, ip);
        }
        return sb.toString();
    }
}
