package org.jerlang;

import java.util.Map;

import org.jerlang.erts.emulator.Instruction;
import org.jerlang.erts.erlang.Error;
import org.jerlang.stdlib.beam_lib.BeamData;
import org.jerlang.stdlib.beam_lib.CodeChunk;
import org.jerlang.type.Atom;
import org.jerlang.type.List;
import org.jerlang.type.Term;

/**
 * Executes opcodes
 */
public class Interpreter {

    private static final Atom LINE = Atom.of("line");

    public static Term dispatch(List params) {
        // A call to a function defined in a BEAM file
        // has always the signature as first parameter.

        Process process = ProcessRegistry.self();
        FunctionSignature s = process.signature();

        params_to_register(params, process.registers());

        Module m = ModuleRegistry.get(s.module());
        int start = find_start(s, m.beamData());

        return continueExecution(s, start);
    }

    public static Term continueExecution(FunctionSignature signature, int start) {
        Process process = ProcessRegistry.self();

        Module m = ModuleRegistry.get(signature.module());

        if (m.isInternal()) {
            process.setState(ProcessState.EXITING);
            // We need to fetch params from registers
            List args = List.nil;
            for (int index = signature.fun_arity().toInt(); index > 0; index--) {
                args = new List(process.getX(index - 1), args);
            }
            Term res = m.apply(signature, args);
            process.setX(0, res);
            return res;
        }

        CodeChunk code = m.beamData().codeChunk();
        java.util.List<Instruction> instructions = code.instructions();
        Map<Integer, Integer> labels = code.labels();
        int maxInstructions = instructions.size();

        if (start == -1) {
            start = find_start(process.signature(), m.beamData());
        }

        Term result = List.nil;
        Term params = List.nil; // TODO: is this correct?

        for (int index = start; index < maxInstructions; index++) {
            Instruction i = instructions.get(index);

            // process.printStack();
            // System.out.println(i);

            if (LINE.equals(i.element(1))) {
                continue;
            }

            try {
                // System.out.println("" + process + ": " + String.format("%2d", index) + ": " + i);

                if (i.opcode().methodHandle() != null) {
                    Term r = (Term) i.opcode().methodHandle().invoke(process, m, i, params);
                    if (r != null) {
                        if (r instanceof org.jerlang.type.Integer) {
                            // CP
                            index = r.toInteger().toInt();
                            if (index == 0) {
                                // Return
                            }
                        } else {
                            // this must be a label jump
                            int lbl = r.toTuple().element(2).toInteger().toInt();
                            index = labels.get(lbl);
                        }
                    }
                } else {
                    System.err.println("Unsupported opcode: " + i.opcode());
                    break;
                }
            } catch (Error error) {
                if (process.exceptionHandler() != null) {
                    Term label = process.exceptionHandler().label();
                    int lbl = label.toRegisterIndex().toInt();
                    index = labels.get(lbl);
                    process.setExceptionHandler(null);
                    process.setX(0, Atom.of("error"));
                    process.setX(1, error.reason());
                } else {
                    error.printStackTrace();
                    System.exit(1);
                    break;
                }
            } catch (Throwable e) {
                e.printStackTrace();
                System.exit(1);
                break;
            }

            if (process.state() == ProcessState.WAITING) {
                // Set process was set to waiting mode.
                process.setCP(index);
                break;
            }

            if (i.opcode() == Opcode.call_last
                || i.opcode() == Opcode.call_ext_last
                || i.opcode() == Opcode.call_ext_only
                || (i.opcode() == Opcode._return && index <= 0)) {
                // The result of the call or return
                // is stored in the x0 register
                result = process.registers()[0];
                process.setState(ProcessState.EXITING);
                break;
            }
        }
        return result;
    }

    /**
     * Store the params, if any, in the X registers of the process.
     */
    private static void params_to_register(List params, Term[] registers) {
        int index = 0;
        while (params.length() > 0) {
            registers[index++] = params.head();
            params = params.tail();
        }
    }

    /**
     * Looks for the exported functions.
     * On match, returns the index of the label instruction
     * that is assigned to the function.
     */
    private static int find_start(FunctionSignature s, BeamData beamData) {
        for (FunctionSignature fs : beamData.exportTableChunk().exports()) {
            if (fs.equals(s)) {
                int lbl = fs.label().toInt();
                int idx = beamData.codeChunk().labels().get(lbl);
                return idx;
            }
        }
        throw new Error("No label found for " + s);
    }
}

@@ -1114,60 +1114,8 @@
 ) %7B%0A
-            process.setState(ProcessState.EXITING);%0A
     
@@ -1437,24 +1437,302 @@
 tX(0, res);%0A
+            // Setting the EXITING state must be the last%0A            // operation before return. Otherwise another%0A            // thread waiting for this process might get%0A            // wrong result as X0 is not filled yet.%0A            process.setState(ProcessState.EXITING);%0A
             

Fix race condition