/*
 * BCJ filter for x86 instructions
 *
 * Authors: Lasse Collin <lasse.collin@tukaani.org>
 *          Igor Pavlov <http://7-zip.org/>
 *
 * This file has been put into the public domain.
 * You can do whatever you want with this file.
 */

package org.tukaani.xz.simple;

public final class X86 implements SimpleFilter {
    private static final boolean[] MASK_TO_ALLOWED_STATUS
            = {true, true, true, false, true, false, false, false};

    private static final int[] MASK_TO_BIT_NUMBER = {0, 1, 2, 2, 3, 3, 3, 3};

    private final boolean isEncoder;
    private int pos;
    private int prevMask = 0;

    private static boolean test86MSByte(byte b) {
        int i = b & 0xFF;
        return i == 0x00 || i == 0xFF;
    }

    public X86(boolean isEncoder, int startPos) {
        this.isEncoder = isEncoder;
        pos = startPos + 5;
    }

    public int code(byte[] buf, int off, int len) {
        int prevPos = off - 1;
        int end = off + len - 5;
        int i;

        for (i = off; i <= end; ++i) {
            if ((buf[i] & 0xFE) != 0xE8)
                continue;

            prevPos = i - prevPos;
            if ((prevPos & ~3) != 0) { // (unsigned)prevPos > 3
                prevMask = 0;
            } else {
                prevMask = (prevMask << (prevPos - 1)) & 7;
                if (prevMask != 0) {
                    if (!MASK_TO_ALLOWED_STATUS[prevMask] || test86MSByte(
                            buf[i + 4 - MASK_TO_BIT_NUMBER[prevMask]])) {
                        prevPos = i;
                        prevMask = (prevMask << 1) | 1;
                        continue;
                    }
                }
            }

            prevPos = i;

            if (test86MSByte(buf[i + 4])) {
                int src = (buf[i + 1] & 0xFF)
                          | ((buf[i + 2] & 0xFF) << 8)
                          | ((buf[i + 3] & 0xFF) << 16)
                          | ((buf[i + 4] & 0xFF) << 24);
                int dest;
                while (true) {
                    if (isEncoder)
                        dest = src + (pos + i - off);
                    else
                        dest = src - (pos + i - off);

                    if (prevMask == 0)
                        break;

                    int index = MASK_TO_BIT_NUMBER[prevMask] * 8;
                    if (!test86MSByte((byte)(dest >>> (24 - index))))
                        break;

                    src = dest ^ ((1 << (32 - index)) - 1);
                }

                buf[i + 1] = (byte)dest;
                buf[i + 2] = (byte)(dest >>> 8);
                buf[i + 3] = (byte)(dest >>> 16);
                buf[i + 4] = (byte)(~(((dest >>> 24) & 1) - 1));
                i += 4;
            } else {
                prevMask = (prevMask << 1) | 1;
            }
        }

        prevPos = i - prevPos;
        prevMask = ((prevPos & ~3) != 0) ? 0 : prevMask << (prevPos - 1);

        i -= off;
        pos += i;
        return i;
    }
}
