/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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.thesett.aima.logic.fol.wam.machine;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.Iterator;
import java.util.Set;

import com.thesett.aima.logic.fol.FunctorName;
import com.thesett.aima.logic.fol.Variable;
import com.thesett.aima.logic.fol.wam.compiler.WAMCallPoint;
import com.thesett.aima.logic.fol.wam.compiler.WAMInstruction;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.ALLOCATE;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.ALLOCATE_N;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.CALL;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.CALL_INTERNAL;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.CON;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.CONTINUE;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.CUT;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.DEALLOCATE;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.EXECUTE;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.GET_CONST;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.GET_LEVEL;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.GET_LIST;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.GET_STRUC;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.GET_VAL;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.GET_VAR;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.LIS;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.NECK_CUT;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.NO_OP;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.PROCEED;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.PUT_CONST;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.PUT_LIST;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.PUT_STRUC;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.PUT_UNSAFE_VAL;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.PUT_VAL;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.PUT_VAR;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.REF;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.RETRY;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.RETRY_ME_ELSE;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.SET_CONST;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.SET_LOCAL_VAL;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.SET_VAL;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.SET_VAR;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.SET_VOID;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.STACK_ADDR;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.STR;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.SUSPEND;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.SWITCH_ON_CONST;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.SWITCH_ON_STRUC;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.SWITCH_ON_TERM;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.TRUST;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.TRUST_ME;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.TRY;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.TRY_ME_ELSE;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.UNIFY_CONST;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.UNIFY_LOCAL_VAL;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.UNIFY_VAL;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.UNIFY_VAR;
import static com.thesett.aima.logic.fol.wam.compiler.WAMInstruction.UNIFY_VOID;
import com.thesett.common.util.SequenceIterator;
import com.thesett.common.util.doublemaps.SymbolTable;

/**
 * WAMResolvingJavaMachine is a byte code interpreter for WAM written in java. This is a direct implementation of the
 * instruction interpretations given in "Warren's Abstract Machine: A Tutorial Reconstruction". The pseudo algorithm
 * presented there can be read in the comments interspersed with the code. There are a couple of challenges to be solved
 * that are not presented in the book:
 *
 *

 *

 *
The book describes a STORE[addr] operation that loads or stores a heap, register or stack address. In the L1 and
 * L0 machines, only heap and register addresses had to be catered for. This made things easier because the registers
 * could be held at the top of the heap and a single common address range used for both. With increasing numbers of data
 * areas in the machine, and Java unable to use direct pointers into memory, a choice between having separate arrays for
 * each data area, or building all data areas within a single array has to be made. The single array approach was
 * chosen, because otherwise the addressing mode would need to be passed down into the 'deref' and 'unify' operations,
 * which would be complicated by having to choose amongst which of several arrays to operate on. An addressing mode has
 * had to be added to the instruction set, so that instructions loading data from registers or stack, can specify which.
 * Once addresses are resolved relative to the register or stack basis, the plain addresses offset to the base of the
 * whole data area are used, and it is these addresses that are passed to the 'deref' and 'unify' operations.
 *
The memory layout for the WAM is described in Appendix B.3. of the book. The same layout is usd for this machine
 * with the exception that the code area is held in a separate array. This follows the x86 machine convention of
 * separating code and data segments in memory, and also caters well for the sharing of the code area with the JVM as a
 * byte buffer.
 *
The deref operation is presented in the book as a recursive function. It was turned into an equivalent iterative
 * looping function instead. The deref operation returns multiple parameters, but as Java only supports single return
 * types, a choice had to be made between creating a simple class to hold the return types, or storing the return values
 * in member variables, and reading them from there. The member variables solution was chosen.

 *

 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Execute compiled WAM programs and queries.
 *
 Provide access to the heap.
 *

 *
 * @author Rupert Smith
 * @todo   Think about unloading of byte code as well as insertion of of byte code. For example, would it be possible to
 *         unload a program, and replace it with a different one? This would require re-linking of any references to the
 *         original. So maybe want to add an index to reverse references. Call instructions should have their jumps
 *         pre-calculated for speed, but perhaps should also put the bare f/n into them, for the case where they may
 *         need to be updated. Also, add a semaphore to all call instructions, or at the entry point of all programs,
 *         this would be used to synchronize live updates to programs in a running machine, as well as to add debugging
 *         break points.
 * @todo   Think about ability to grow (and shrink?) the heap. Might be best to do this at the same time as the first
 *         garbage collector.
 */
public class WAMResolvingJavaMachine extends WAMResolvingMachine
{
    /** Used for debugging. */
    /* private static final Logger log = Logger.getLogger(WAMResolvingJavaMachine.class.getName()); */

    /** Used for tracing instruction executions. */
    /* private static final Logger trace = Logger.getLogger("TRACE.WAMResolvingJavaMachine"); */

    /** The id of the internal call/1 function. */
    public static final int CALL_1_ID = 1;

    /** The id of the internal call/1 function execute variant. */
    public static final int EXECUTE_1_ID = 2;

    /** The mask to extract an address from a tagged heap cell. */
    public static final int AMASK = 0x3FFFFFFF;

    /**
     * The mask to extract a constant from a tagged heap cell. Arity of atomic constants is always zero, so just the
     * functor name needs to be stored and loaded to the heap cell.
     */
    public static final int CMASK = 0xFFFFFFF;

    /** The shift to position the tag within a tagged heap cell. */
    public static final int TSHIFT = 30;

    /** Defines the register capacity for the virtual machine. */
    private static final int REG_SIZE = 256;

    /** Defines the heap size to use for the virtual machine. */
    private static final int HEAP_SIZE = 10000000;

    /** Defines the offset of the base of the heap in the data area. */
    private static final int HEAP_BASE = REG_SIZE;

    /** Defines the stack size to use for the virtual machine. */
    private static final int STACK_SIZE = 1000000;

    /** Defines the offset of the base of the stack in the data area. */
    private static final int STACK_BASE = REG_SIZE + HEAP_SIZE;

    /** Defines the trail size to use for the virtual machine. */
    private static final int TRAIL_SIZE = 10000;

    /** Defines the offset of the base of the trail in the data area. */
    private static final int TRAIL_BASE = REG_SIZE + HEAP_SIZE + STACK_SIZE;

    /** Defines the max unification stack depth for the virtual machine. */
    private static final int PDL_SIZE = 10000;

    /** Defines the highest address in the data area of the virtual machine. */
    private static final int TOP = REG_SIZE + HEAP_SIZE + STACK_SIZE + TRAIL_SIZE + PDL_SIZE;

    /** Defines the initial code area size for the virtual machine. */
    private static final int CODE_SIZE = 1000000;

    /** Holds the current instruction pointer into the code. */
    private int ip;

    /** Holds the current continuation pointer into the code. */
    private int cp;

    /** Holds the entire data segment of the machine. All registers, heaps and stacks are held in here. */
    private IntBuffer data;

    /** Holds the heap pointer. */
    private int hp;

    /** Holds the top of heap at the latest choice point. */
    private int hbp;

    /** Holds the secondary heap pointer, used for the heap address of the next term to match. */
    private int sp;

    /** Holds the unification stack pointer. */
    private int up;

    /** Holds the environment base pointer. */
    private int ep;

    /** Holds the choice point base pointer. */
    private int bp;

    /** Holds the last call choice point pointer. */
    private int b0;

    /** Holds the trail pointer. */
    private int trp;

    /** Used to record whether the machine is in structure read or write mode. */
    private boolean writeMode;

    /** Holds the heap cell tag from the most recent dereference. */
    private byte derefTag;

    /** Holds the heap call value from the most recent dereference. */
    private int derefVal;

    /** Indicates that the machine has been suspended, upon finding a solution. */
    private boolean suspended;

    /**
     * Creates a unifying virtual machine for WAM with default heap sizes.
     *
     * @param symbolTable The symbol table for the machine.
     */
    public WAMResolvingJavaMachine(SymbolTable<Integer, String, Object> symbolTable)
    {
        super(symbolTable);

        // Reset the machine to its initial state.
        reset();
    }

    /**
     * Resets the machine, to its initial state. This clears any programs from the machine, and clears all of its stacks
     * and heaps.
     */
    public void reset()
    {
        // Create fresh heaps, code areas and stacks.
        data = ByteBuffer.allocateDirect(TOP << 2).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
        codeBuffer = ByteBuffer.allocateDirect(CODE_SIZE);
        codeBuffer.order(ByteOrder.LITTLE_ENDIAN);

        // Registers are on the top of the data area, the heap comes next.
        hp = HEAP_BASE;
        hbp = HEAP_BASE;
        sp = HEAP_BASE;

        // The stack comes after the heap. Pointers are zero initially, since no stack frames exist yet.
        ep = 0;
        bp = 0;
        b0 = 0;

        // The trail comes after the stack.
        trp = TRAIL_BASE;

        // The unification stack (PDL) is a push down stack at the end of the data area.
        up = TOP;

        // Turn off write mode.
        writeMode = false;

        // Reset the instruction pointer to that start of the code area, ready for fresh code to be loaded there.
        ip = 0;

        // Could probably not bother resetting these, but will do it anyway just to be sure.
        derefTag = 0;
        derefVal = 0;

        // The machine is initially not suspended.
        suspended = false;

        // Ensure that the overridden reset method of WAMBaseMachine is run too, to clear the call table.
        super.reset();

        // Put the internal functions in the call table.
        setInternalCodeAddress(internFunctorName("call", 1), CALL_1_ID);
        setInternalCodeAddress(internFunctorName("execute", 1), EXECUTE_1_ID);

        // Notify any debug monitor that the machine has been reset.
        if (monitor != null)
        {
            monitor.onReset(this);
        }
    }

    /**
     * Provides an iterator that generates all solutions on demand as a sequence of variable bindings.
     *
     * @return An iterator that generates all solutions on demand as a sequence of variable bindings.
     */
    public Iterator<Set<Variable>> iterator()
    {
        return new SequenceIterator<Set<Variable>>()
            {
                public Set<Variable> nextInSequence()
                {
                    return resolve();
                }
            };
    }

    /**
     * Sets the maximum number of search steps that a search method may take. If it fails to find a solution before this
     * number of steps has been reached its search method should fail and return null. What exactly constitutes a single
     * step, and the granularity of the step size, is open to different interpretation by different search algorithms.
     * The guideline is that this is the maximum number of states on which the goal test should be performed.
     *
     * @param max The maximum number of states to goal test. If this is zero or less then the maximum number of steps
     *            will not be checked for.
     */
    public void setMaxSteps(int max)
    {
        throw new UnsupportedOperationException("WAMResolvingJavaMachine does not support max steps limit on search.");
    }

    /** {@inheritDoc} */
    public IntBuffer getDataBuffer()
    {
        return data;
    }

    /** {@inheritDoc} */
    public WAMInternalRegisters getInternalRegisters()
    {
        return new WAMInternalRegisters(ip, hp, hbp, sp, up, ep, bp, b0, trp, writeMode);
    }

    /** {@inheritDoc} */
    public WAMMemoryLayout getMemoryLayout()
    {
        return new WAMMemoryLayout(0, REG_SIZE, HEAP_BASE, HEAP_SIZE, STACK_BASE, STACK_SIZE, TRAIL_BASE, TRAIL_SIZE,
            TOP - PDL_SIZE, PDL_SIZE);
    }

    /**
     * {@inheritDoc}
     *
     *
Does nothing.
     */
    protected void codeAdded(ByteBuffer codeBuffer, int codeOffset, int length)
    {
    }

    /** {@inheritDoc} */
    protected int derefStack(int a)
    {
        /*log.fine("Stack deref from " + (a + ep + 3) + ", ep = " + ep);*/

        return deref(a + ep + 3);
    }

    /** {@inheritDoc} */
    protected boolean execute(WAMCallPoint callPoint)
    {
        /*log.fine("protected boolean execute(WAMCallPoint callPoint): called");*/

        boolean failed;

        // Check if the machine is being woken up from being suspended, in which case immediately fail in order to
        // trigger back-tracking to find more solutions.
        if (suspended)
        {
            failed = true;
            suspended = false;
        }
        else
        {
            ip = callPoint.entryPoint;
            uClear();
            failed = false;
        }

        int numOfArgs = 0;

        // Holds the current continuation point.
        cp = codeBuffer.position();

        // Notify any debug monitor that execution is starting.
        if (monitor != null)
        {
            monitor.onExecute(this);
        }

        //while (!failed && (ip < code.length))
        while (true)
        {
            // Attempt to backtrack on failure.
            if (failed)
            {
                failed = backtrack();

                if (failed)
                {
                    break;
                }
            }

            // Grab next instruction and switch on it.
            byte instruction = codeBuffer.get(ip);

            switch (instruction)
            {
            // put_struc Xi, f/n:
            case PUT_STRUC:
            {
                // grab addr, f/n
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);
                int fn = codeBuffer.getInt(ip + 3);

                /*trace.fine(ip + ": PUT_STRUC " + printSlot(xi, mode) + ", " + fn);*/

                // heap[h] <- STR, h + 1
                data.put(hp, fn);

                // Xi <- heap[h]
                data.put(xi, structureAt(hp));

                // h <- h + 2
                hp += 1;

                // P <- instruction_size(P)
                ip += 7;

                break;
            }

            // set_var Xi:
            case SET_VAR:
            {
                // grab addr
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);

                /*trace.fine(ip + ": SET_VAR " + printSlot(xi, mode));*/

                // heap[h] <- REF, h
                data.put(hp, refTo(hp));

                // Xi <- heap[h]
                data.put(xi, data.get(hp));

                // h <- h + 1
                hp++;

                // P <- instruction_size(P)
                ip += 3;

                break;
            }

            // set_val Xi:
            case SET_VAL:
            {
                // grab addr
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);

                /*trace.fine(ip + ": SET_VAL " + printSlot(xi, mode));*/

                // heap[h] <- Xi
                data.put(hp, data.get(xi));

                // h <- h + 1
                hp++;

                // P <- instruction_size(P)
                ip += 3;

                break;
            }

            // get_struc Xi,
            case GET_STRUC:
            {
                // grab addr, f/n
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);
                int fn = codeBuffer.getInt(ip + 3);

                /*trace.fine(ip + ": GET_STRUC " + printSlot(xi, mode) + ", " + fn);*/

                // addr <- deref(Xi);
                int addr = deref(xi);
                byte tag = derefTag;
                int a = derefVal;

                // switch STORE[addr]
                switch (tag)
                {
                // case REF:
                case REF:
                {
                    // heap[h] <- STR, h + 1
                    data.put(hp, structureAt(hp + 1));

                    // heap[h+1] <- f/n
                    data.put(hp + 1, fn);

                    // bind(addr, h)
                    bind(addr, hp);

                    // h <- h + 2
                    hp += 2;

                    // mode <- write
                    writeMode = true;
                    /*trace.fine("-> write mode");*/

                    break;
                }

                // case STR, a:
                case STR:
                {
                    // if heap[a] = f/n
                    if (data.get(a) == fn)
                    {
                        // s <- a + 1
                        sp = a + 1;

                        // mode <- read
                        writeMode = false;
                        /*trace.fine("-> read mode");*/
                    }
                    else
                    {
                        // fail
                        failed = true;
                    }

                    break;
                }

                default:
                {
                    // fail
                    failed = true;
                }
                }

                // P <- instruction_size(P)
                ip += 7;

                break;
            }

            // unify_var Xi:
            case UNIFY_VAR:
            {
                // grab addr
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);

                /*trace.fine(ip + ": UNIFY_VAR " + printSlot(xi, mode));*/

                // switch mode
                if (!writeMode)
                {
                    // case read:
                    // Xi <- heap[s]
                    data.put(xi, data.get(sp));
                }
                else
                {
                    // case write:
                    // heap[h] <- REF, h
                    data.put(hp, refTo(hp));

                    // Xi <- heap[h]
                    data.put(xi, data.get(hp));

                    // h <- h + 1
                    hp++;
                }

                // s <- s + 1
                sp++;

                // P <- P + instruction_size(P)
                ip += 3;

                break;
            }

            // unify_val Xi:
            case UNIFY_VAL:
            {
                // grab addr
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);

                /*trace.fine(ip + ": UNIFY_VAL " + printSlot(xi, mode));*/

                // switch mode
                if (!writeMode)
                {
                    // case read:
                    // unify (Xi, s)
                    failed = !unify(xi, sp);
                }
                else
                {
                    // case write:
                    // heap[h] <- Xi
                    data.put(hp, data.get(xi));

                    // h <- h + 1
                    hp++;
                }

                // s <- s + 1
                sp++;

                // P <- P + instruction_size(P)
                ip += 3;

                break;
            }

            // put_var Xn, Ai:
            case PUT_VAR:
            {
                // grab addr, Ai
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);
                byte ai = codeBuffer.get(ip + 3);

                /*trace.fine(ip + ": PUT_VAR " + printSlot(xi, mode) + ", A" + ai);*/

                if (mode == WAMInstruction.REG_ADDR)
                {
                    // heap[h] <- REF, H
                    data.put(hp, refTo(hp));

                    // Xn <- heap[h]
                    data.put(xi, data.get(hp));

                    // Ai <- heap[h]
                    data.put(ai, data.get(hp));
                }
                else
                {
                    // STACK[addr] <- REF, addr
                    data.put(xi, refTo(xi));

                    // Ai <- STACK[addr]
                    data.put(ai, data.get(xi));
                }

                // h <- h + 1
                hp++;

                // P <- P + instruction_size(P)
                ip += 4;

                break;
            }

            // put_val Xn, Ai:
            case PUT_VAL:
            {
                // grab addr, Ai
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);
                byte ai = codeBuffer.get(ip + 3);

                /*trace.fine(ip + ": PUT_VAL " + printSlot(xi, mode) + ", A" + ai);*/

                // Ai <- Xn
                data.put(ai, data.get(xi));

                // P <- P + instruction_size(P)
                ip += 4;

                break;
            }

            // get var Xn, Ai:
            case GET_VAR:
            {
                // grab addr, Ai
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);
                byte ai = codeBuffer.get(ip + 3);

                /*trace.fine(ip + ": GET_VAR " + printSlot(xi, mode) + ", A" + ai);*/

                // Xn <- Ai
                data.put(xi, data.get(ai));

                // P <- P + instruction_size(P)
                ip += 4;

                break;
            }

            // get_val Xn, Ai:
            case GET_VAL:
            {
                // grab addr, Ai
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);
                byte ai = codeBuffer.get(ip + 3);

                /*trace.fine(ip + ": GET_VAL " + printSlot(xi, mode) + ", A" + ai);*/

                // unify (Xn, Ai)
                failed = !unify(xi, ai);

                // P <- P + instruction_size(P)
                ip += 4;

                break;
            }

            case PUT_CONST:
            {
                // grab addr, f/n
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);
                int fn = codeBuffer.getInt(ip + 3);

                /*trace.fine(ip + ": PUT_CONST " + printSlot(xi, mode) + ", " + fn);*/

                // Xi <- heap[h]
                data.put(xi, constantCell(fn));

                // P <- instruction_size(P)
                ip += 7;

                break;
            }

            case GET_CONST:
            {
                // grab addr, Ai
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);
                int fn = codeBuffer.getInt(ip + 3);

                /*trace.fine(ip + ": GET_CONST " + printSlot(xi, mode) + ", " + fn);*/

                // addr <- deref(Xi)
                int addr = deref(xi);
                int tag = derefTag;
                int val = derefVal;

                failed = !unifyConst(fn, xi);

                // P <- P + instruction_size(P)
                ip += 7;

                break;
            }

            case SET_CONST:
            {
                int fn = codeBuffer.getInt(ip + 1);

                /*trace.fine(ip + ": SET_CONST " + fn);*/

                // heap[h] <- <CON, c>
                data.put(hp, constantCell(fn));

                // h <- h + 1
                hp++;

                // P <- instruction_size(P)
                ip += 5;

                break;
            }

            case UNIFY_CONST:
            {
                int fn = codeBuffer.getInt(ip + 1);

                /*trace.fine(ip + ": UNIFY_CONST " + fn);*/

                // switch mode
                if (!writeMode)
                {
                    // case read:
                    // addr <- deref(S)

                    // unifyConst(fn, addr)
                    failed = !unifyConst(fn, sp);
                }
                else
                {
                    // case write:
                    // heap[h] <- <CON, c>
                    data.put(hp, constantCell(fn));

                    // h <- h + 1
                    hp++;
                }

                // s <- s + 1
                sp++;

                // P <- P + instruction_size(P)
                ip += 5;

                break;
            }

            case PUT_LIST:
            {
                // grab addr
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);

                /*trace.fine(ip + ": PUT_LIST " + printSlot(xi, mode));*/

                // Xi <- <LIS, H>
                data.put(xi, listCell(hp));

                // P <- P + instruction_size(P)
                ip += 3;

                break;
            }

            case GET_LIST:
            {
                // grab addr
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);

                /*trace.fine(ip + ": GET_LIST " + printSlot(xi, mode));*/

                int addr = deref(xi);
                int tag = derefTag;
                int val = derefVal;

                // case STORE[addr] of
                switch (tag)
                {
                case REF:
                {
                    // <REF, _> :
                    // HEAP[H] <- <LIS, H+1>
                    data.put(hp, listCell(hp + 1));

                    // bind(addr, H)
                    bind(addr, hp);

                    // H <- H + 1
                    hp += 1;

                    // mode <- write
                    writeMode = true;
                    /*trace.fine("-> write mode");*/

                    break;
                }

                case LIS:
                {
                    // <LIS, a> :
                    // S <- a
                    sp = val;

                    // mode <- read
                    writeMode = false;
                    /*trace.fine("-> read mode");*/

                    break;
                }

                default:
                {
                    // other: fail <- true;
                    failed = true;
                }
                }

                // P <- P + instruction_size(P)
                ip += 3;

                break;
            }

            case SET_VOID:
            {
                // grab N
                int n = (int) codeBuffer.get(ip + 1);

                /*trace.fine(ip + ": SET_VOID " + n);*/

                // for i <- H to H + n - 1 do
                //  HEAP[i] <- <REF, i>
                for (int addr = hp; addr < (hp + n); addr++)
                {
                    data.put(addr, refTo(addr));
                }

                // H <- H + n
                hp += n;

                // P <- P + instruction_size(P)
                ip += 2;

                break;
            }

            case UNIFY_VOID:
            {
                // grab N
                int n = (int) codeBuffer.get(ip + 1);

                /*trace.fine(ip + ": UNIFY_VOID " + n);*/

                // case mode of
                if (!writeMode)
                {
                    //  read: S <- S + n
                    sp += n;
                }
                else
                {
                    //  write:
                    //   for i <- H to H + n -1 do
                    //    HEAP[i] <- <REF, i>
                    for (int addr = hp; addr < (hp + n); addr++)
                    {
                        data.put(addr, refTo(addr));
                    }

                    //   H <- H + n
                    hp += n;
                }

                // P <- P + instruction_size(P)
                ip += 2;

                break;
            }

            // put_unsafe_val Yn, Ai:
            case PUT_UNSAFE_VAL:
            {
                // grab addr, Ai
                byte mode = codeBuffer.get(ip + 1);
                int yi = (int) codeBuffer.get(ip + 2) + (ep + 3);
                byte ai = codeBuffer.get(ip + 3);

                /*trace.fine(ip + ": PUT_UNSAFE_VAL " + printSlot(yi, WAMInstruction.STACK_ADDR) + ", A" + ai);*/

                int addr = deref(yi);

                if (addr < ep)
                {
                    // Ai <- Xn
                    data.put(ai, data.get(addr));
                }
                else
                {
                    data.put(hp, refTo(hp));
                    bind(addr, hp);
                    data.put(ai, data.get(hp));
                    hp++;
                }

                // P <- P + instruction_size(P)
                ip += 4;

                break;
            }

            // set_local_val Xi:
            case SET_LOCAL_VAL:
            {
                // grab addr
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);

                /*trace.fine(ip + ": SET_LOCAL_VAL " + printSlot(xi, mode));*/

                int addr = deref(xi);

                if (addr < ep)
                {
                    data.put(hp, data.get(addr));
                }
                else
                {
                    data.put(hp, refTo(hp));
                    bind(addr, hp);
                }

                // h <- h + 1
                hp++;

                // P <- P + instruction_size(P)
                ip += 3;

                break;
            }

            // unify_local_val Xi:
            case UNIFY_LOCAL_VAL:
            {
                // grab addr
                byte mode = codeBuffer.get(ip + 1);
                int xi = getRegisterOrStackSlot(mode);

                /*trace.fine(ip + ": UNIFY_LOCAL_VAL " + printSlot(xi, mode));*/

                // switch mode
                if (!writeMode)
                {
                    // case read:
                    // unify (Xi, s)
                    failed = !unify(xi, sp);
                }
                else
                {
                    // case write:
                    int addr = deref(xi);

                    if (addr < ep)
                    {
                        data.put(hp, data.get(addr));
                    }
                    else
                    {
                        data.put(hp, refTo(hp));
                        bind(addr, hp);
                    }

                    // h <- h + 1
                    hp++;
                }

                // s <- s + 1
                sp++;

                // P <- P + instruction_size(P)
                ip += 3;

                break;
            }

            // call @(p/n), perms:
            case CALL:
            {
                // grab @(p/n), perms
                int pn = codeBuffer.getInt(ip + 1);
                int n = codeBuffer.get(ip + 5);
                int numPerms = (int) codeBuffer.get(ip + 6);

                // num_of_args <- n
                numOfArgs = n;

                // Ensure that the predicate to call is known and linked in, otherwise fail.
                if (pn == -1)
                {
                    failed = true;

                    break;
                }

                // STACK[E + 2] <- numPerms
                data.put(ep + 2, numPerms);

                // CP <- P + instruction_size(P)
                cp = ip + 7;

                /*trace.fine(ip + ": CALL " + pn + "/" + n + ", " + numPerms + " (cp = " + cp + ")]");*/

                // B0 <- B
                b0 = bp;

                // P <- @(p/n)
                ip = pn;

                break;
            }

            // execute @(p/n):
            case EXECUTE:
            {
                // grab @(p/n)
                int pn = codeBuffer.getInt(ip + 1);
                int n = codeBuffer.get(ip + 5);

                // num_of_args <- n
                numOfArgs = n;

                /*trace.fine(ip + ": EXECUTE " + pn + "/" + n + " (cp = " + cp + ")]");*/

                // Ensure that the predicate to call is known and linked in, otherwise fail.
                if (pn == -1)
                {
                    failed = true;

                    break;
                }

                // B0 <- B
                b0 = bp;

                // P <- @(p/n)
                ip = pn;

                break;
            }

            // proceed:
            case PROCEED:
            {
                /*trace.fine(ip + ": PROCEED" + " (cp = " + cp + ")]");*/

                // P <- CP
                ip = cp;

                break;
            }

            // allocate:
            case ALLOCATE:
            {
                // if E > B
                //  then newB <- E + STACK[E + 2] + 3
                // else newB <- B + STACK[B] + 7
                int esp = nextStackFrame();

                // STACK[newE] <- E
                data.put(esp, ep);

                // STACK[E + 1] <- CP
                data.put(esp + 1, cp);

                // STACK[E + 2] <- N
                data.put(esp + 2, 0);

                // E <- newE
                // newE <- E + n + 3
                ep = esp;

                /*trace.fine(ip + ": ALLOCATE");*/
                /*trace.fine("-> env @ " + ep + " " + traceEnvFrame());*/

                // P <- P + instruction_size(P)
                ip += 1;

                break;
            }

            // allocate N:
            case ALLOCATE_N:
            {
                // grab N
                int n = (int) codeBuffer.get(ip + 1);

                // if E > B
                //  then newB <- E + STACK[E + 2] + 3
                // else newB <- B + STACK[B] + 7
                int esp = nextStackFrame();

                // STACK[newE] <- E
                data.put(esp, ep);

                // STACK[E + 1] <- CP
                data.put(esp + 1, cp);

                // STACK[E + 2] <- N
                data.put(esp + 2, n);

                // E <- newE
                // newE <- E + n + 3
                ep = esp;

                /*trace.fine(ip + ": ALLOCATE_N " + n);*/
                /*trace.fine("-> env @ " + ep + " " + traceEnvFrame());*/

                // P <- P + instruction_size(P)
                ip += 2;

                break;
            }

            // deallocate:
            case DEALLOCATE:
            {
                int newip = data.get(ep + 1);

                // E <- STACK[E]
                ep = data.get(ep);

                /*trace.fine(ip + ": DEALLOCATE");*/
                /*trace.fine("<- env @ " + ep + " " + traceEnvFrame());*/

                // CP <- STACK[E + 1]
                cp = newip;

                // P <- P + instruction_size(P)
                ip += 1;

                break;
            }

            // try me else L:
            case TRY_ME_ELSE:
            {
                // grab L
                int l = codeBuffer.getInt(ip + 1);

                // if E > B
                //  then newB <- E + STACK[E + 2] + 3
                // else newB <- B + STACK[B] + 7
                int esp = nextStackFrame();

                // STACK[newB] <- num_of_args
                // n <- STACK[newB]
                int n = numOfArgs;
                data.put(esp, n);

                // for i <- 1 to n do STACK[newB + i] <- Ai
                for (int i = 0; i < n; i++)
                {
                    data.put(esp + i + 1, data.get(i));
                }

                // STACK[newB + n + 1] <- E
                data.put(esp + n + 1, ep);

                // STACK[newB + n + 2] <- CP
                data.put(esp + n + 2, cp);

                // STACK[newB + n + 3] <- B
                data.put(esp + n + 3, bp);

                // STACK[newB + n + 4] <- L
                data.put(esp + n + 4, l);

                // STACK[newB + n + 5] <- TR
                data.put(esp + n + 5, trp);

                // STACK[newB + n + 6] <- H
                data.put(esp + n + 6, hp);

                // STACK[newB + n + 7] <- B0
                data.put(esp + n + 7, b0);

                // B <- new B
                bp = esp;

                // HB <- H
                hbp = hp;

                /*trace.fine(ip + ": TRY_ME_ELSE");*/
                /*trace.fine("-> chp @ " + bp + " " + traceChoiceFrame());*/

                // P <- P + instruction_size(P)
                ip += 5;

                break;
            }

            // retry me else L:
            case RETRY_ME_ELSE:
            {
                // grab L
                int l = codeBuffer.getInt(ip + 1);

                // n <- STACK[B]
                int n = data.get(bp);

                // for i <- 1 to n do Ai <- STACK[B + i]
                for (int i = 0; i < n; i++)
                {
                    data.put(i, data.get(bp + i + 1));
                }

                // E <- STACK[B + n + 1]
                ep = data.get(bp + n + 1);

                // CP <- STACK[B + n + 2]
                cp = data.get(bp + n + 2);

                // STACK[B + n + 4] <- L
                data.put(bp + n + 4, l);

                // unwind_trail(STACK[B + n + 5], TR)
                unwindTrail(data.get(bp + n + 5), trp);

                // TR <- STACK[B + n + 5]
                trp = data.get(bp + n + 5);

                // H <- STACK[B + n + 6]
                hp = data.get(bp + n + 6);

                // HB <- H
                hbp = hp;

                /*trace.fine(ip + ": RETRY_ME_ELSE");*/
                /*trace.fine("-- chp @ " + bp + " " + traceChoiceFrame());*/

                // P <- P + instruction_size(P)
                ip += 5;

                break;
            }

            // trust me (else fail):
            case TRUST_ME:
            {
                // n <- STACK[B]
                int n = data.get(bp);

                // for i <- 1 to n do Ai <- STACK[B + i]
                for (int i = 0; i < n; i++)
                {
                    data.put(i, data.get(bp + i + 1));
                }

                // E <- STACK[B + n + 1]
                ep = data.get(bp + n + 1);

                // CP <- STACK[B + n + 2]
                cp = data.get(bp + n + 2);

                // unwind_trail(STACK[B + n + 5], TR)
                unwindTrail(data.get(bp + n + 5), trp);

                // TR <- STACK[B + n + 5]
                trp = data.get(bp + n + 5);

                // H <- STACK[B + n + 6]
                hp = data.get(bp + n + 6);

                // HB <- STACK[B + n + 6]
                hbp = hp;

                // B <- STACK[B + n + 3]
                bp = data.get(bp + n + 3);

                /*trace.fine(ip + ": TRUST_ME");*/
                /*trace.fine("<- chp @ " + bp + " " + traceChoiceFrame());*/

                // P <- P + instruction_size(P)
                ip += 1;

                break;
            }

            case SWITCH_ON_TERM:
            {
                // grab labels
                int v = codeBuffer.getInt(ip + 1);
                int c = codeBuffer.getInt(ip + 5);
                int l = codeBuffer.getInt(ip + 9);
                int s = codeBuffer.getInt(ip + 13);

                int addr = deref(1);
                int tag = derefTag;

                // case STORE[deref(A1)] of
                switch (tag)
                {
                case REF:

                    // <REF, _> : P <- V
                    ip = v;
                    break;

                case CON:

                    // <CON, _> : P <- C
                    ip = c;
                    break;

                case LIS:

                    // <LIS, _> : P <- L
                    ip = l;
                    break;

                case STR:

                    // <STR, _> : P <- S
                    ip = s;
                    break;
                }

                break;
            }

            case SWITCH_ON_CONST:
            {
                // grab labels
                int t = codeBuffer.getInt(ip + 1);
                int n = codeBuffer.getInt(ip + 5);

                // <tag, val> <- STORE[deref(A1)]
                deref(1);

                int val = derefVal;

                // <found, inst> <- get_hash(val, T, N)
                int inst = getHash(val, t, n);

                // if found
                if (inst > 0)
                {
                    // then P <- inst
                    ip = inst;
                }
                else
                {
                    // else backtrack
                    failed = true;
                }

                break;
            }

            case SWITCH_ON_STRUC:
            {
                // grab labels
                int t = codeBuffer.getInt(ip + 1);
                int n = codeBuffer.getInt(ip + 5);

                // <tag, val> <- STORE[deref(A1)]
                deref(1);

                int val = derefVal;

                // <found, inst> <- get_hash(val, T, N)
                int inst = getHash(val, t, n);

                // if found
                if (inst > 0)
                {
                    // then P <- inst
                    ip = inst;
                }
                else
                {
                    // else backtrack
                    failed = true;
                }

                break;
            }

            case TRY:
            {
                // grab L
                int l = codeBuffer.getInt(ip + 1);

                // if E > B
                //  then newB <- E + STACK[E + 2] + 3
                // else newB <- B + STACK[B] + 7
                int esp = nextStackFrame();

                // STACK[newB] <- num_of_args
                // n <- STACK[newB]
                int n = numOfArgs;
                data.put(esp, n);

                // for i <- 1 to n do STACK[newB + i] <- Ai
                for (int i = 0; i < n; i++)
                {
                    data.put(esp + i + 1, data.get(i));
                }

                // STACK[newB + n + 1] <- E
                data.put(esp + n + 1, ep);

                // STACK[newB + n + 2] <- CP
                data.put(esp + n + 2, cp);

                // STACK[newB + n + 3] <- B
                data.put(esp + n + 3, bp);

                // STACK[newB + n + 4] <- L
                data.put(esp + n + 4, ip + 5);

                // STACK[newB + n + 5] <- TR
                data.put(esp + n + 5, trp);

                // STACK[newB + n + 6] <- H
                data.put(esp + n + 6, hp);

                // STACK[newB + n + 7] <- B0
                data.put(esp + n + 7, b0);

                // B <- new B
                bp = esp;

                // HB <- H
                hbp = hp;

                /*trace.fine(ip + ": TRY");*/
                /*trace.fine("-> chp @ " + bp + " " + traceChoiceFrame());*/

                // P <- L
                ip = l;

                break;
            }

            case RETRY:
            {
                // grab L
                int l = codeBuffer.getInt(ip + 1);

                // n <- STACK[B]
                int n = data.get(bp);

                // for i <- 1 to n do Ai <- STACK[B + i]
                for (int i = 0; i < n; i++)
                {
                    data.put(i, data.get(bp + i + 1));
                }

                // E <- STACK[B + n + 1]
                ep = data.get(bp + n + 1);

                // CP <- STACK[B + n + 2]
                cp = data.get(bp + n + 2);

                // STACK[B + n + 4] <- L
                data.put(bp + n + 4, ip + 5);

                // unwind_trail(STACK[B + n + 5], TR)
                unwindTrail(data.get(bp + n + 5), trp);

                // TR <- STACK[B + n + 5]
                trp = data.get(bp + n + 5);

                // H <- STACK[B + n + 6]
                hp = data.get(bp + n + 6);

                // HB <- H
                hbp = hp;

                /*trace.fine(ip + ": RETRY");*/
                /*trace.fine("-- chp @ " + bp + " " + traceChoiceFrame());*/

                // P <- L
                ip = l;

                break;
            }

            case TRUST:
            {
                // grab L
                int l = codeBuffer.getInt(ip + 1);

                // n <- STACK[B]
                int n = data.get(bp);

                // for i <- 1 to n do Ai <- STACK[B + i]
                for (int i = 0; i < n; i++)
                {
                    data.put(i, data.get(bp + i + 1));
                }

                // E <- STACK[B + n + 1]
                ep = data.get(bp + n + 1);

                // CP <- STACK[B + n + 2]
                cp = data.get(bp + n + 2);

                // unwind_trail(STACK[B + n + 5], TR)
                unwindTrail(data.get(bp + n + 5), trp);

                // TR <- STACK[B + n + 5]
                trp = data.get(bp + n + 5);

                // H <- STACK[B + n + 6]
                hp = data.get(bp + n + 6);

                // HB <- STACK[B + n + 6]
                hbp = hp;

                // B <- STACK[B + n + 3]
                bp = data.get(bp + n + 3);

                /*trace.fine(ip + ": TRUST");*/
                /*trace.fine("<- chp @ " + bp + " " + traceChoiceFrame());*/

                // P <- L
                ip = l;

                break;
            }

            case NECK_CUT:
            {
                if (bp > b0)
                {
                    bp = b0;
                    tidyTrail();
                }

                /*trace.fine(ip + ": NECK_CUT");*/
                /*trace.fine("<- chp @ " + bp + " " + traceChoiceFrame());*/

                ip += 1;

                break;
            }

            case GET_LEVEL:
            {
                int yn = (int) codeBuffer.get(ip + 1) + (ep + 3);

                data.put(yn, b0);

                /*trace.fine(ip + ": GET_LEVEL " + codeBuffer.get(ip + 1));*/

                ip += 2;

                break;
            }

            case CUT:
            {
                int yn = (int) codeBuffer.get(ip + 1) + (ep + 3);

                int cbp = data.get(yn);

                if (bp > cbp)
                {
                    bp = cbp;
                    tidyTrail();
                }

                /*trace.fine(ip + ": CUT " + codeBuffer.get(ip + 1));*/
                /*trace.fine("<- chp @ " + bp + " " + traceChoiceFrame());*/

                ip += 2;

                break;
            }

            case CONTINUE:
            {
                // grab L
                int l = codeBuffer.getInt(ip + 1);

                /*trace.fine(ip + ": CONTINUE " + l);*/

                ip = l;

                break;
            }

            case NO_OP:
            {
                /*trace.fine(ip + ": NO_OP");*/

                ip += 1;

                break;
            }

            // call_internal @(p/n), perms:
            case CALL_INTERNAL:
            {
                // grab @(p/n), perms
                int pn = codeBuffer.getInt(ip + 1);
                int n = codeBuffer.get(ip + 5);
                int numPerms = (int) codeBuffer.get(ip + 6);

                // num_of_args <- n
                numOfArgs = n;

                /*trace.fine(ip + ": CALL_INTERNAL " + pn + "/" + n + ", " + numPerms + " (cp = " + cp + ")]");*/

                boolean callOk = callInternal(pn, n, numPerms);

                failed = !callOk;

                break;
            }

            // suspend on success:
            case SUSPEND:
            {
                /*trace.fine(ip + ": SUSPEND");*/
                ip += 1;
                suspended = true;

                return true;
            }
            }

            // Notify any debug monitor that the machine has been stepped.
            if (monitor != null)
            {
                monitor.onStep(this);
            }
        }

        return !failed;
    }

    /**
     * Pretty prints the current environment frame, for debugging purposes.
     *
     * @return The current environment frame, pretty printed.
     */
    protected String traceEnvFrame()
    {
        return "env: [ ep = " + data.get(ep) + ", cp = " + data.get(ep + 1) + ", n = " + data.get(ep + 2) + "]";
    }

    /**
     * Pretty prints the current choice point frame, for debugging purposes.
     *
     * @return The current choice point frame, pretty printed.
     */
    protected String traceChoiceFrame()
    {
        if (bp == 0)
        {
            return "";
        }

        int n = data.get(bp);

        return "choice: [ n = " + data.get(bp) + ", ep = " + data.get(bp + n + 1) + ", cp = " + data.get(bp + n + 2) +
            ", bp = " + data.get(bp + n + 3) + ", l = " + data.get(bp + n + 4) + ", trp = " + data.get(bp + n + 5) +
            ", hp = " + data.get(bp + n + 6) + ", b0 = " + data.get(bp + n + 7);
    }

    /** {@inheritDoc} */
    protected int deref(int a)
    {
        // tag, value <- STORE[a]
        int addr = a;
        int tmp = data.get(a);
        derefTag = (byte) (tmp >>> TSHIFT);
        derefVal = tmp & AMASK;

        // while tag = REF and value != a
        while ((derefTag == WAMInstruction.REF))
        {
            // tag, value <- STORE[a]
            addr = derefVal;
            tmp = data.get(derefVal);
            derefTag = (byte) (tmp >>> TSHIFT);
            tmp = tmp & AMASK;

            // Break on free var.
            if (derefVal == tmp)
            {
                break;
            }

            derefVal = tmp;
        }

        return addr;
    }

    /**
     * Gets the heap cell tag for the most recent dereference operation.
     *
     * @return The heap cell tag for the most recent dereference operation.
     */
    protected byte getDerefTag()
    {
        return derefTag;
    }

    /**
     * Gets the heap cell value for the most recent dereference operation.
     *
     * @return The heap cell value for the most recent dereference operation.
     */
    protected int getDerefVal()
    {
        return derefVal;
    }

    /**
     * Gets the value of the heap cell at the specified location.
     *
     * @param  addr The address to fetch from the heap.
     *
     * @return The heap cell at the specified location.
     */
    protected int getHeap(int addr)
    {
        return data.get(addr);
    }

    /**
     * Invokes an internal function.
     *
     * @param  function The id of the internal function to call.
     * @param  arity    The arity of the function to call.
     * @param  numPerms The number of permanent variables remaining in the environment.
     *
     * @return <tt>true</tt> if the call succeeded, and <tt>false</tt> if it failed.
     */
    private boolean callInternal(int function, int arity, int numPerms)
    {
        switch (function)
        {
        case CALL_1_ID:
            return internalCall_1(numPerms);

        case EXECUTE_1_ID:
            return internalExecute_1();

        default:
            throw new IllegalStateException("Unknown internal function id: " + function);
        }
    }

    /**
     * Implements the 'call/1' predicate.
     *
     * @param  numPerms The number of permanent variables remaining in the environment.
     *
     * @return <tt>true</tt> if the call succeeded, and <tt>false</tt> if it failed.
     */
    private boolean internalCall_1(int numPerms)
    {
        int pn = setupCall_1();

        if (pn == -1)
        {
            return false;
        }

        // Make the call.
        // STACK[E + 2] <- numPerms
        data.put(ep + 2, numPerms);

        // CP <- P + instruction_size(P)
        cp = ip + 7;

        /*trace.fine(ip + ": (CALL) " + pn + ", " + numPerms + " (cp = " + cp + ")]");*/

        // B0 <- B
        b0 = bp;

        // P <- @(p/n)
        ip = pn;

        return true;
    }

    /**
     * Implements the execute variant of the 'call/1' predicate.
     *
     * @return <tt>true</tt> if the call succeeded, and <tt>false</tt> if it failed.
     */
    private boolean internalExecute_1()
    {
        int pn = setupCall_1();

        if (pn == -1)
        {
            return false;
        }

        // Make the call.
        /*trace.fine(ip + ": (EXECUTE) " + pn + " (cp = " + cp + ")]");*/

        // B0 <- B
        b0 = bp;

        // P <- @(p/n)
        ip = pn;

        return true;
    }

    /**
     * Sets up the registers to make a call, for implementing call/1. The first register should reference a structure to
     * be turned into a predicate call. The arguments of this structure will be set up in the registers, and the entry
     * point of the predicate to call will be returned.
     *
     * @return The entry address of the predicate to call, or <tt>-1</tt> if the call cannot be resolved to a known
     *         predicate.
     */
    private int setupCall_1()
    {
        // Get X0.
        int addr = deref(0);
        byte tag = derefTag;
        int val = derefVal;

        // Check it points to a structure.
        int fn;

        if (tag == STR)
        {
            fn = getHeap(val);
        }
        else if (tag == CON)
        {
            fn = val;
        }
        else
        {
            /*trace.fine("call/1 not invoked against structure.");*/

            return -1;
        }

        // Look up the call point of the matching functor.
        int f = fn & 0x00ffffff;

        WAMCallPoint callPoint = resolveCallPoint(f);

        if (callPoint.entryPoint == -1)
        {
            /*trace.fine("call/1 to unknown call point.");*/

            return -1;
        }

        int pn = callPoint.entryPoint;

        // Set registers X0... to ref to args...
        FunctorName functorName = getDeinternedFunctorName(f);
        int arity = functorName.getArity();

        for (int i = 0; i < arity; i++)
        {
            data.put(i, refTo(val + 1 + i));
        }

        return pn;
    }

    /**
     * Looks up a value (an interned name referring to a constant or structure), in the hash table of size n referred
     * to.
     *
     * @param  val The value to look up.
     * @param  t   The offset of the start of the hash table.
     * @param  n   The size of the hash table in bytes.
     *
     * @return <tt>0</tt> iff no match is found, or a pointer into the code area of the matching branch.
     */
    private int getHash(int val, int t, int n)
    {
        return 0;
    }

    /**
     * Creates a heap cell contents containing a structure tag, and the address of the structure.
     *
     *
Note: This only creates the contents of the cell, it does not write it to the heap.
     *
     * @param  addr The address of the structure.
     *
     * @return The heap cell contents referencing the structure.
     */
    private int structureAt(int addr)
    {
        return (WAMInstruction.STR << TSHIFT) | (addr & AMASK);
    }

    /**
     * Creates a heap cell contents containing a reference.
     *
     *
Note: This only creates the contents of the cell, it does not write it to the heap.
     *
     * @param  addr The references address.
     *
     * @return The heap cell contents containing the reference.
     */
    private int refTo(int addr)
    {
        return (WAMInstruction.REF << TSHIFT) | (addr & AMASK);
    }

    /**
     * Creates a heap cell contents containing a constant.
     *
     *
Note: This only creates the contents of the cell, it does not write it to the heap.
     *
     *
See the comment on {@link #CMASK} about the arity always being zero on a constant.
     *
     * @param  fn The functor name and arity of the constant. Arity should always be zero.
     *
     * @return The heap cell contents containing the constant.
     */
    private int constantCell(int fn)
    {
        return (CON << TSHIFT) | (fn & CMASK);
    }

    /**
     * Creates a heap cell contents containing a list pointer.
     *
     *
Note: This only creates the contents of the cell, it does not write it to the heap.
     *
     * @param  addr The address of the list contents.
     *
     * @return The heap cell contents containing the list pointer.
     */
    private int listCell(int addr)
    {
        return (WAMInstruction.LIS << TSHIFT) | (addr & AMASK);
    }

    /**
     * Loads the contents of a register, or a stack slot, depending on the mode.
     *
     * @param  mode The mode, {@link WAMInstruction#REG_ADDR} for register addressing, {@link WAMInstruction#STACK_ADDR}
     *              for stack addressing.
     *
     * @return The contents of the register or stack slot.
     */
    private int getRegisterOrStackSlot(byte mode)
    {
        return (int) codeBuffer.get(ip + 2) + ((mode == STACK_ADDR) ? (ep + 3) : 0);
    }

    /**
     * Computes the start of the next stack frame. This depends on whether the most recent stack frame is an environment
     * frame or a choice point frame, as these have different sizes. The size of the most recent type of frame is
     * computed and added to the current frame pointer to give the start of the next frame.
     *
     * @return The start of the next stack frame.
     */
    private int nextStackFrame()
    {
        // if E > B
        // then newB <- E + STACK[E + 2] + 3
        // else newB <- B + STACK[B] + 7

        if (ep == bp)
        {
            return STACK_BASE;
        }
        else if (ep > bp)
        {
            return ep + data.get(ep + 2) + 3;
        }
        else
        {
            return bp + data.get(bp) + 8;
        }
    }

    /**
     * Backtracks to the continuation label stored in the current choice point frame, if there is one. Otherwise returns
     * a fail to indicate that there are no more choice points, so no backtracking can be done.
     *
     * @return <tt>true</tt> iff this is the final failure, and there are no more choice points.
     */
    private boolean backtrack()
    {
        // if B = bottom_of_stack
        if (bp == 0)
        {
            //  then fail_and_exit_program
            return true;
        }
        else
        {
            // B0 <- STACK[B + STACK[B} + 7]
            b0 = data.get(bp + data.get(bp) + 7);

            // P <- STACK[B + STACK[B] + 4]
            ip = data.get(bp + data.get(bp) + 4);

            return false;
        }
    }

    /**
     * Creates a binding of one variable onto another. One of the supplied addresses must be an unbound variable. If
     * both are unbound variables, the higher (newer) address is bound to the lower (older) one.
     *
     * @param a1 The address of the first potential unbound variable to bind.
     * @param a2 The address of the second potential unbound variable to bind.
     */
    private void bind(int a1, int a2)
    {
        // <t1, _> <- STORE[a1]
        int t1 = (byte) (data.get(a1) >>> TSHIFT);

        // <t2, _> <- STORE[a2]
        int t2 = (byte) (data.get(a2) >>> TSHIFT);

        // if (t1 = REF) /\ ((t2 != REF) \/ (a2 < a1))
        if ((t1 == WAMInstruction.REF) && ((t2 != WAMInstruction.REF) || (a2 < a1)))
        {
            //  STORE[a1] <- STORE[a2]
            //data.put(a1, refTo(a2));
            data.put(a1, data.get(a2));

            //  trail(a1)
            trail(a1);
        }
        else if (t2 == WAMInstruction.REF)
        {
            //  STORE[a2] <- STORE[a1]
            //data.put(a2, refTo(a1));
            data.put(a2, data.get(a1));

            //  tail(a2)
            trail(a2);
        }
    }

    /**
     * Records the address of a binding onto the 'trail'. The trail pointer is advanced by one as part of this
     * operation.
     *
     * @param addr The binding address to add to the trail.
     */
    private void trail(int addr)
    {
        // if (a < HB) \/ ((H < a) /\ (a < B))
        if ((addr < hbp) || ((hp < addr) && (addr < bp)))
        {
            //  TRAIL[TR] <- a
            data.put(trp, addr);

            //  TR <- TR + 1
            trp++;
        }
    }

    /**
     * Undoes variable bindings that have been recorded on the 'trail'. Addresses recorded on the trail are reset to REF
     * to self.
     *
     * @param a1 The start address within the trail to get the first binding address to clear.
     * @param a2 The end address within the trail, this is one higher than the last address to clear.
     */
    private void unwindTrail(int a1, int a2)
    {
        // for i <- a1 to a2 - 1 do
        for (int addr = a1; addr < a2; addr++)
        {
            //  STORE[TRAIL[i]] <- <REF, TRAIL[i]>
            int tmp = data.get(addr);
            data.put(tmp, refTo(tmp));
        }
    }

    /**
     * Tidies trail when a choice point is being discarded, and a previous choice point it being made the current one.
     *
     *
Copies trail bindings created since the choice point, into the trail as known to the previous choice point.
     * That is bindings on the heap created during the choice point (between HB and H).
     */
    private void tidyTrail()
    {
        int i;

        // Check that there is a current choice point to tidy down to, otherwise tidy down to the root of the trail.
        if (bp == 0)
        {
            i = TRAIL_BASE;
        }
        else
        {
            i = data.get(bp + data.get(bp) + 5);
        }

        while (i < trp)
        {
            int addr = data.get(i);

            if ((addr < hbp) || ((hp < addr) && (addr < bp)))
            {
                i++;
            }
            else
            {
                data.put(i, data.get(trp - 1));
                trp--;
            }
        }
    }

    /**
     * Attempts to unify structures or references on the heap, given two references to them. Structures are matched
     * element by element, free references become bound.
     *
     * @param  a1 The address of the first structure or reference.
     * @param  a2 The address of the second structure or reference.
     *
     * @return <tt>true</tt> if the two structures unify, <tt>false</tt> otherwise.
     */
    private boolean unify(int a1, int a2)
    {
        // pdl.push(a1)
        // pdl.push(a2)
        uPush(a1);
        uPush(a2);

        // fail <- false
        boolean fail = false;

        // while !empty(PDL) and not failed
        while (!uEmpty() && !fail)
        {
            // d1 <- deref(pdl.pop())
            // d2 <- deref(pdl.pop())
            // t1, v1 <- STORE[d1]
            // t2, v2 <- STORE[d2]
            int d1 = deref(uPop());
            int t1 = derefTag;
            int v1 = derefVal;

            int d2 = deref(uPop());
            int t2 = derefTag;
            int v2 = derefVal;

            // if (d1 != d2)
            if (d1 != d2)
            {
                // if (t1 = REF or t2 = REF)
                // bind(d1, d2)
                if ((t1 == WAMInstruction.REF))
                {
                    bind(d1, d2);
                }
                else if (t2 == WAMInstruction.REF)
                {
                    bind(d1, d2);
                }
                else if (t2 == WAMInstruction.STR)
                {
                    // f1/n1 <- STORE[v1]
                    // f2/n2 <- STORE[v2]
                    int fn1 = data.get(v1);
                    int fn2 = data.get(v2);
                    byte n1 = (byte) (fn1 >>> 24);

                    // if f1 = f2 and n1 = n2
                    if (fn1 == fn2)
                    {
                        // for i <- 1 to n1
                        for (int i = 1; i <= n1; i++)
                        {
                            // pdl.push(v1 + i)
                            // pdl.push(v2 + i)
                            uPush(v1 + i);
                            uPush(v2 + i);
                        }
                    }
                    else
                    {
                        // fail <- true
                        fail = true;
                    }
                }
                else if (t2 == WAMInstruction.CON)
                {
                    if ((t1 != WAMInstruction.CON) || (v1 != v2))
                    {
                        fail = true;
                    }
                }
                else if (t2 == WAMInstruction.LIS)
                {
                    if (t1 != WAMInstruction.LIS)
                    {
                        fail = true;
                    }
                    else
                    {
                        uPush(v1);
                        uPush(v2);
                        uPush(v1 + 1);
                        uPush(v2 + 1);
                    }
                }
            }
        }

        return !fail;
    }

    /**
     * A simplified unification algorithm, for unifying against a constant.
     *
     *
Attempts to unify a constant or references on the heap, with a constant. If the address leads to a free
     * variable on dereferencing, the variable is bound to the constant. If the address leads to a constant it is
     * compared with the passed in constant for equality, and unification succeeds when they are equal.
     *
     * @param  fn   The constant to unify with.
     * @param  addr The address of the first constant or reference.
     *
     * @return <tt>true</tt> if the two constant unify, <tt>false</tt> otherwise.
     */
    private boolean unifyConst(int fn, int addr)
    {
        boolean success;

        int deref = deref(addr);
        int tag = derefTag;
        int val = derefVal;

        // case STORE[addr] of
        switch (tag)
        {
        case REF:
        {
            // <REF, _> :
            // STORE[addr] <- <CON, c>
            data.put(deref, constantCell(fn));

            // trail(addr)
            trail(deref);

            success = true;

            break;
        }

        case CON:
        {
            // <CON, c'> :
            // fail <- (c != c');
            success = val == fn;

            break;
        }

        default:
        {
            // other: fail <- true;
            success = false;
        }
        }

        return success;
    }

    /**
     * Pushes a value onto the unification stack.
     *
     * @param val The value to push onto the stack.
     */
    private void uPush(int val)
    {
        data.put(--up, val);
    }

    /**
     * Pops a value from the unification stack.
     *
     * @return The top value from the unification stack.
     */
    private int uPop()
    {
        return data.get(up++);
    }

    /** Clears the unification stack. */
    private void uClear()
    {
        up = TOP;
    }

    /**
     * Checks if the unification stack is empty.
     *
     * @return <tt>true</tt> if the unification stack is empty, <tt>false</tt> otherwise.
     */
    private boolean uEmpty()
    {
        return up >= TOP;
    }

    /**
     * Pretty prints a variable allocation slot for tracing purposes.
     *
     * @param  xi   The allocation slot to print.
     * @param  mode The addressing mode, stack or register.
     *
     * @return The pretty printed slot.
     */
    private String printSlot(int xi, int mode)
    {
        return ((mode == STACK_ADDR) ? "Y" : "X") + ((mode == STACK_ADDR) ? (xi - ep - 3) : xi);
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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.thesett.aima.search.examples.eightpuzzle;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import com.thesett.aima.search.GoalState;
import com.thesett.aima.search.Operator;
import com.thesett.aima.search.TraversableState;
import com.thesett.aima.search.util.OperatorImpl;

/**
 * An implementation of the eight puzzle. This is a puzzle with nine squares arranged in a 3x3 grid. One of the squares
 * is empty and the others contain tiles with the numbers 1 to 8 on them, initially randomly arranged. The object of the
 * game is to slide tiles into the empty square until all the tiles are arranged in order, reading left to right, top to
 * bottom, with the empty square in the last position.
 *
 *
For example (E denotes the empty square):
 *
 *
Random start state:
 *
 *

 *
5E3
 *
617
 *
824
 *

 *
 *
Goal state:
 *
 *

 *
123
 *
456
 *
78E
 *

 *
 *
Instances of this state class represent a single board position. This class also provides for each state, a set of
 * operators corresponding to the legal moves on a state, that result in new legal board states.
 *
 *
For efficient matching of states this state representation provides the {@link #equals} and {@link #hashCode}
 * methods. The hash function is computed by transforming the board position into a string consisting of its tiles in
 * order (using the numeric characters plus 'E' for the empty square) and then taking the string hash of this.
 *
 *
Their are four possible moves; up, down, left and right. These correspond to moving the empty tile up, down, left
 * and right and are represented by the operation strings "U", "D", "L" and "R".
 *
 *
There is a static convenience method that generates a random solvable board position. Not all board positions are
 * solvable. Unsolvable positions can get close to a solution but will end up with two neighbouring tiles that need to
 * be swapped but can't be. Tiles can only swap position with the empty tile. There are effectively two sets of board
 * states for each puzzle, the legal states and the illegal ones. To get from one state set to the other simply swap two
 * adjacent non-empty tiles. This is called an illegal swap. To check for solvability the number of illegal swaps needed
 * to get to the goal position are counted. If this is even then they cancel out so the board position belongs to the
 * set of legal states. If its odd then they don't cancel out so the board belongs to the set of illegal states and is
 * not solvable with respect to the goal position.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Generate a random starting position that is solvable.
 *
 Generate a state corresponding to the goal state.
 *
 Check if a board position is the goal state.
 *
 Supply the valid moves for a board position.
 *
 Calculate the cost of a move (always one).
 *
 Apply a move to generate a new board position.
 *
 Report the goal coordinates for a tile.
 *
 Report the coordinates for a tile.
 *
 Report the position of the empty tile.
 *
 Report the tile at a given position.
 *
 Supply equals and hashCode for efficient hashing on puzzle states.
 *

 *
 * @author Rupert Smith
 */
public class EightPuzzleState extends TraversableState<String> implements GoalState, Cloneable
{
    /**
     * Defines the goal state. Changeing this constant allows different goal states to be defined. Another popular goal
     * state is "1238E4765".
     */
    private static final String GOAL_STRING = "12345678E";

    /** Used to hold the board position. */
    char[][] board = new char[3][3];

    /** Used to hold the X position of the empty tile. */
    int emptyX;

    /** Used to hold the Y position of the empty tile. */
    int emptyY;

    /**
     * Generates a puzzle state from a string.
     *
     * @param  s The board as a string of 9 characters.
     *
     * @return The board as an EightPuzzleState.
     */
    public static EightPuzzleState getStateFromString(String s)
    {
        // Turn the string into a list of characters and then transform this into a proper state object.
        return charListToState(stringToCharList(s));
    }

    /**
     * Generates the goal state.
     *
     * @return The goal state as an EightPuzzleState.
     */
    public static EightPuzzleState getGoalState()
    {
        // Turn the goal string into a list of characters and then transform this into a proper state object.
        return charListToState(stringToCharList(GOAL_STRING));
    }

    /**
     * Generates a random starting position.
     *
     * @return A random board state.
     */
    public static EightPuzzleState getRandomStartState()
    {
        EightPuzzleState newState;

        // Turn the goal string into a list of characters.
        List<Character> charList = stringToCharList(GOAL_STRING);

        // Generate random puzzles until a solvable one is found.
        do
        {
            // Shuffle the list.
            Collections.shuffle(charList);

            // Turn the shuffled list into a proper eight puzzle state object.
            newState = charListToState(charList);

            // Check that the puzzle is solvable and if not then repeat the shuffling process.
        }
        while (!isSolvable(newState));

        return newState;
    }

    /**
     * To check for solvability the empty tile is moved to its goal position and then the number of swaps needed to put
     * the other tiles in position is counted. For an odd number of rows on a square puzzle there must be an even number
     * of swaps, for an even number of rows an odd number of swaps.
     *
     * @param  state A puzzle state to test for solvability.
     *
     * @return True if the puzzle is solvable, false if it is not.
     */
    public static boolean isSolvable(EightPuzzleState state)
    {
        // Take a copy of the puzzle to check. This is done because this puzzle will be updated in-place and the
        // original is to be preserved.
        EightPuzzleState checkState;

        try
        {
            checkState = (EightPuzzleState) state.clone();
        }
        catch (CloneNotSupportedException e)
        {
            throw new IllegalStateException("Puzzle state could not be cloned.", e);
        }

        // Create the goal state to check against when swapping tiles into position.
        EightPuzzleState goalState = getGoalState();

        // Count the number of illegal swaps needed to put the puzzle in order.
        int illegalSwaps = 0;

        // Loop over the whole board, left to right, to to bottom
        for (int j = 0; j < 3; j++)
        {
            for (int i = 0; i < 3; i++)
            {
                // Find out from the goal state what tile should be at this position.
                char t = goalState.getTileAt(i, j);

                // Swap the tile into its goal position keeping count of the total number of illegal swaps.
                illegalSwaps += checkState.swapTileToLocationCountingIllegal(t, i, j);
            }
        }

        // Check if the number of illegal swaps is even in which case the puzzle is solvable, or odd in which case it
        // is not solvable.
        return (illegalSwaps % 2) == 0;
    }

    /**
     * This helper function calculates the goal X position for a tile.
     *
     * @param  t The tile to check.
     *
     * @return The goal X position for the tile.
     */
    public int getGoalXForTile(char t)
    {
        // Find the position of the tile in the goal string and take the remainder after dividing by 3 and
        // this is the X position.
        return GOAL_STRING.indexOf(t) % 3;
    }

    /**
     * This helper function calcualtes the goal Y position for a tile.
     *
     * @param  t The tile to check.
     *
     * @return The goal Y position for the tile.
     */
    public int getGoalYForTile(char t)
    {
        // Find the position of the tile in the goal string and integer divide by 3 to get the Y position.
        return GOAL_STRING.indexOf(t) / 3;
    }

    /**
     * This helper function calculates the X position for a tile.
     *
     * @param  t The tile to check.
     *
     * @return The current X position of the tile.
     */
    public int getXForTile(char t)
    {
        // Find the X position of the tile by finding its position in the string representation and taking the
        // remainder after dividing by 3.
        return toString().indexOf(t) % 3;
    }

    /**
     * This helper function calculates the Y position for a tile.
     *
     * @param  t The tile to check.
     *
     * @return The current X position of the tile.
     */
    public int getYForTile(char t)
    {
        // Find the Y position of the tile by finding its position in the string representation and dividing by 3.
        return toString().indexOf(t) / 3;
    }

    /**
     * Returns the tile at the specified X, Y position.
     *
     * @param  x The X position.
     * @param  y The Y position.
     *
     * @return The tile at X, Y.
     */
    public char getTileAt(int x, int y)
    {
        return board[y][x];
    }

    /**
     * Returns the X position of the empty tile.
     *
     * @return The X position of the empty tile.
     */
    public int getEmptyX()
    {
        return emptyX;
    }

    /**
     * Returns the Y position of the empty tile.
     *
     * @return The Y position of the empty tile.
     */
    public int getEmptyY()
    {
        return emptyY;
    }

    /**
     * Checks if a board position is the goal state.
     *
     * @return True if this board is in the goal position, false otherwise.
     */
    public boolean isGoal()
    {
        return toString().equals(GOAL_STRING);
    }

    /**
     * Applies a move to generate a new board position. This creates a new state object and updates its board position.
     * The board position in this object is not changed.
     *
     * @param  op The move to apply to this board; "U" for up, "D" for down, "L" for left and "R" for right.
     *
     * @return A new instance of EightPuzzleState with the operator applied to it.
     */
    public EightPuzzleState getChildStateForOperator(Operator op)
    {
        // Create a copy of the existing board state
        EightPuzzleState newState;

        try
        {
            newState = (EightPuzzleState) clone();
        }
        catch (CloneNotSupportedException e)
        {
            throw new IllegalStateException("Puzzle state could not be cloned.", e);
        }

        // Update the new board state using the in-place operator application
        newState.updateWithOperator(op);

        return newState;
    }

    /**
     * Calculates the cost of a move (always one).
     *
     * @param  op The move to calculate the cost of.
     *
     * @return All moves always have a cost of 1.0f.
     */
    public float costOf(Operator op)
    {
        return 1.0f;
    }

    /**
     * Supplies the valid moves for a board position.
     *
     * @param  reverse Don't care about reverse orderings. Ignored.
     *
     * @return An iterator over all the moves which are valid for this board position. This can be all 4, 3 or just 2
     *         moves, depending if the empty tile is in the middle, edge or corner positions.
     */
    public Iterator<Operator<String>> validOperators(boolean reverse)
    {
        // Used to hold a list of valid moves
        List<Operator<String>> moves = new ArrayList<Operator<String>>(4);

        // Check if the up move is valid
        if (emptyY != 0)
        {
            moves.add(new OperatorImpl<String>("U"));
        }

        // Check if the down move is valid
        if (emptyY != 2)
        {
            moves.add(new OperatorImpl<String>("D"));
        }

        // Check if the left move is valid
        if (emptyX != 0)
        {
            moves.add(new OperatorImpl<String>("L"));
        }

        // Check if the right move is valid
        if (emptyX != 2)
        {
            moves.add(new OperatorImpl<String>("R"));
        }

        return moves.iterator();
    }

    /**
     * Computes a hash code of the board position for efficient hashing of board states. This uses the string hash
     * function on the board position made by concatenating all the tiles into a string.
     *
     *
Another hashing strategy that could be tried is simply to concatenate all the tiles together into a decimal
     * number modulo the maximum integer.
     *
     * @return A hash code for this board position calculated from the position information.
     */
    public int hashCode()
    {
        return toString().hashCode();
    }

    /**
     * Checks if two board positions are equal. This is computed by using the toString method to concatenate all the
     * tiles togheter into a string and then using string equality.
     *
     *
A faster solution may simply be to iterate over the two boards until a mismatch is found. If there is no
     * mismatch then they are equal.
     *
     * @param  o The object to compare this to.
     *
     * @return True if the comparison object is a board position identical to this one, false otherwise.
     */
    public boolean equals(Object o)
    {
        return toString().equals(o.toString());
    }

    /**
     * Converts the board position into a string representation by walking over the board left-to-right, top-to-bottom
     * using 'E' for the empty tile and the number characters for the numbered tiles.
     *
     * @return A string representation of this board position.
     */
    public String toString()
    {
        char[] result = new char[9];
        int resultCounter = 0;

        for (int j = 0; j < 3; j++)
        {
            for (int i = 0; i < 3; i++)
            {
                result[resultCounter++] = board[j][i];
            }
        }

        return new String(result);
    }

    /**
     * Pretty prints the board as 3 lines of characters with a space for the empty square.
     *
     * @return A pretty printed string representation of this board position.
     */
    public String prettyPrint()
    {
        String result = "";

        for (int j = 0; j < 3; j++)
        {
            result += new String(board[j]) + "\n";
        }

        result = result.replace('E', ' ');

        return result;
    }

    /**
     * Repeatedly swaps a tile with its neighbours until it reaches the specified location. If the tile is swapped with
     * the empty tile then this is a legal move. If the tile is swapped with another non-empty tile then this is an
     * illegal move and the total number of illegal moves is counted.
     *
     *
This method updates the board position array in-place, rather than generating a new state.
     *
     * @param  t the tile to move.
     * @param  x the X position to move the tile to.
     * @param  y the Y position to move the tile to.
     *
     * @return the number of illegal swaps performed.
     */
    protected int swapTileToLocationCountingIllegal(char t, int x, int y)
    {
        // Used to hold the count of illegal swaps
        int illegal = 0;

        // Find out where the tile to move is
        int tileX = getXForTile(t);
        int tileY = getYForTile(t);

        // Shift the tile into the correct column by repeatedly moving it left or right.
        while (tileX != x)
        {
            if ((tileX - x) > 0)
            {
                if (swapTiles(tileX, tileY, tileX - 1, tileY))
                {
                    illegal++;
                }

                tileX--;
            }
            else
            {
                if (swapTiles(tileX, tileY, tileX + 1, tileY))
                {
                    illegal++;
                }

                tileX++;
            }
        }

        // Shift the tile into the correct row by repeatedly moving it up or down.
        while (tileY != y)
        {
            // Commented out because tiles never swap down the board during the solvability test because tiles are
            // swapped into place left to right, top to bottom. The top row is always filled first so tiles cannot be
            // swapped down into it. Then the next row is filled but ones from the row above are never swapped down
            // into it because they are alrady in place and never move again and so on.
            /* if (tileY - y > 0)
             *{*/
            if (swapTiles(tileX, tileY, tileX, tileY - 1))
            {
                illegal++;
            }

            tileY--;
            /*}
             * else { if (swapTiles(tileX, tileY, tileX, tileY + 1)) illegal++; tileY++;}*/
        }

        return illegal;
    }

    /**
     * Applies a move to the board position. This changes the board position stored in this object. This is different
     * from the {@link #getChildStateForOperator} method which updates the board position in a new object.
     *
     * @param op The move to apply to this board; "U" for up, "D" for down, "L" for left and "R" for right.
     */
    protected void updateWithOperator(Operator op)
    {
        // Get the operator as a character by taking the first character of the operator string
        char opc = ((String) op.getOp()).charAt(0);

        // Move the empty tile according to the specified operation
        switch (opc)
        {
        // Swap the empty tile with the one above it.
        case 'U':
        {
            swapTiles(emptyX, emptyY, emptyX, emptyY - 1);
            break;
        }

        // Swap the empty tile with the one below it.
        case 'D':
        {
            swapTiles(emptyX, emptyY, emptyX, emptyY + 1);
            break;
        }

        // Swap the empty tile with the one to the left of it.
        case 'L':
        {
            swapTiles(emptyX, emptyY, emptyX - 1, emptyY);
            break;
        }

        // Swap the empty tile with the one to the right of it.
        case 'R':
        {
            swapTiles(emptyX, emptyY, emptyX + 1, emptyY);
            break;
        }

        default:
        {
            throw new IllegalStateException("Unkown operator: " + opc + ".");
        }
        }
    }

    /**
     * Swaps the two tiles at the specified coordinates. One of the tiles may be the empty tile and the empty tile
     * position will be correctly updated. If neither of the tiles is empty then this is an illegal swap in which case
     * the method returns true.
     *
     * @param  x1 the X position of tile 1
     * @param  y1 the Y position of tile 1
     * @param  x2 the X position of tile 2
     * @param  y2 the Y position of tile 2
     *
     * @return True if it is an illegal swap.
     */
    protected boolean swapTiles(int x1, int y1, int x2, int y2)
    {
        // Used to indicate that one of the swapped tiles was the empty tile
        boolean swappedEmpty = false;

        // Get the tile at the first position
        char tile1 = board[y1][x1];

        // Store the tile from the second position at the first position
        char tile2 = board[y2][x2];

        board[y1][x1] = tile2;

        // Store the first tile in the second position
        board[y2][x2] = tile1;

        // Check if the first tile was the empty tile and update the empty tile coordinates if so
        if (tile1 == 'E')
        {
            emptyX = x2;
            emptyY = y2;
            swappedEmpty = true;
        }

        // Else check if the second tile was the empty tile and update the empty tile coordinates if so
        else if (tile2 == 'E')
        {
            emptyX = x1;
            emptyY = y1;
            swappedEmpty = true;
        }

        return !swappedEmpty;
    }

    /**
     * Creates a deep clone of the puzzle state. That is one where the board position is copied into a new array. The
     * empty tile position is also copied.
     *
     * @return A deep clone of this object.
     *
     * @throws CloneNotSupportedException If cloning fails.
     */
    protected Object clone() throws CloneNotSupportedException
    {
        // Create a new state and copy the existing board position into it
        EightPuzzleState newState = (EightPuzzleState) super.clone();

        newState.board = new char[3][3];

        for (int j = 0; j < 3; j++)
        {
            System.arraycopy(board[j], 0, newState.board[j], 0, 3);
        }

        newState.emptyX = emptyX;
        newState.emptyY = emptyY;

        return newState;
    }

    /**
     * Turns a string representation of the board into a list of characters.
     *
     * @param  boardString The board as a string of nine characters.
     *
     * @return The board as a List of characters.
     */
    private static List<Character> stringToCharList(String boardString)
    {
        // Turn the goal state into a list of characters
        char[] chars = new char[9];

        boardString.getChars(0, 9, chars, 0);

        List<Character> charList = new ArrayList<Character>();

        for (int l = 0; l < 9; l++)
        {
            charList.add(chars[l]);
        }

        return charList;
    }

    /**
     * Turns a list of characters representation of the board into a proper state.
     *
     * @param  charList The board as a list of 9 characters.
     *
     * @return The board as an EightPuzzleState.
     */
    private static EightPuzzleState charListToState(List<Character> charList)
    {
        // Create a new empty puzzle state
        EightPuzzleState newState = new EightPuzzleState();

        // Loop over the board inserting the characters into it from the character list
        Iterator<Character> k = charList.iterator();

        for (int j = 0; j < 3; j++)
        {
            for (int i = 0; i < 3; i++)
            {
                char nextChar = k.next();

                // Check if this is the empty tile and if so then take note of its position
                if (nextChar == 'E')
                {
                    newState.emptyX = i;
                    newState.emptyY = j;
                }

                newState.board[j][i] = nextChar;
            }
        }

        return newState;
    }
}


/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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.thesett.util.log4j;

import java.text.MessageFormat;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;

import org.apache.log4j.Logger;
import org.apache.log4j.Priority;

/**
 * LoggingToLog4JHandler is a java.util.logging handler that redirects all of its output onto Log4J.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Publish a java.util.logging message to Log4J.
 *

 *
 * @author Rupert Smith
 */
public class LoggingToLog4JHandler extends Handler
{
    /**
     * Provides the target Log4J logger for a class, by name.
     *
     * @param  clazz The class to get the logger for.
     *
     * @return The target Log4J logger.
     */
    public static Logger getTargetLogger(Class clazz)
    {
        return getTargetLogger(clazz.getName());
    }

    /**
     * Provides the target Log4J logger.
     *
     * @param  loggerName The name of the logger.
     *
     * @return The target Log4J logger.
     */
    public static Logger getTargetLogger(String loggerName)
    {
        return Logger.getLogger(loggerName);
    }

    /** {@inheritDoc} */
    public void publish(LogRecord record)
    {
        org.apache.log4j.Logger log4j = getTargetLogger(record.getLoggerName());
        Priority priority = toLog4j(record.getLevel());

        if (!priority.equals(org.apache.log4j.Level.OFF))
        {
            log4j.log(priority, toLog4jMessage(record), record.getThrown());
        }
    }

    /** {@inheritDoc} */
    public void flush()
    {
    }

    /** {@inheritDoc} */
    public void close()
    {
    }

    /**
     * Transforms a java.util.logging.LogRecord to a message printable on Log4J.
     *
     * @param  record The log record.
     *
     * @return The log4j message.
     */
    private String toLog4jMessage(LogRecord record)
    {
        String message = record.getMessage();

        // Format message
        Object[] parameters = record.getParameters();

        if ((parameters != null) && (parameters.length != 0))
        {
            // Check for the first few parameters ?
            if ((message.indexOf("{0}") >= 0) || (message.indexOf("{1}") >= 0) || (message.indexOf("{2}") >= 0) ||
                    (message.indexOf("{3}") >= 0))
            {
                message = MessageFormat.format(message, parameters);
            }
        }

        return message;
    }

    /**
     * Converts java.util.logging levels to Log4J logging levels.
     *
     * @param  level The java.util.logging level to convert.
     *
     * @return The corresponding Log4J level.
     */
    private org.apache.log4j.Level toLog4j(Level level)
    {
        if (Level.SEVERE == level)
        {
            return org.apache.log4j.Level.ERROR;
        }
        else if (Level.WARNING == level)
        {
            return org.apache.log4j.Level.WARN;
        }
        else if (Level.INFO == level)
        {
            return org.apache.log4j.Level.INFO;
        }
        else if (Level.FINE == level)
        {
            return org.apache.log4j.Level.DEBUG;
        }
        else if (Level.FINER == level)
        {
            return org.apache.log4j.Level.TRACE;
        }
        else if (Level.OFF == level)
        {
            return org.apache.log4j.Level.OFF;
        }

        return org.apache.log4j.Level.OFF;
    }
}