package org.jruby.ir.interpreter;

import java.util.EnumSet;
import java.util.List;
import java.util.Stack;
import java.util.function.Supplier;

import org.jruby.RubySymbol;
import org.jruby.ir.IRFlags;
import org.jruby.ir.IRMetaClassBody;
import org.jruby.ir.IRScope;
import org.jruby.ir.instructions.ExceptionRegionEndMarkerInstr;
import org.jruby.ir.instructions.ExceptionRegionStartMarkerInstr;
import org.jruby.ir.instructions.Instr;
import org.jruby.ir.instructions.LabelInstr;
import org.jruby.ir.representations.CFG;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.ThreadContext;

public class InterpreterContext {

    private final static Instr[] NO_INSTRUCTIONS = new Instr[0];

    private final static InterpreterEngine DEFAULT_INTERPRETER = new InterpreterEngine();
    private final static InterpreterEngine STARTUP_INTERPRETER = new StartupInterpreterEngine();

    protected int temporaryVariableCount;

    // startup interp will mark this at construction and not change but full interpreter will write it
    // much later after running compiler passes.  JIT will not use this field at all.
    protected Instr[] instructions;

    // Contains pairs of values.  The first value is number of instrs in this range + number of instrs before
    // this range.  The second number is the rescuePC.  getRescuePC(ipc) will walk this list and first odd value
    // less than this value will be the rpc.
    protected int[] rescueIPCs = null;

    // Cached computed fields
    protected boolean hasExplicitCallProtocol; // Only can be true in Full+
    protected boolean dynamicScopeEliminated; // Only can be true in Full+
    private boolean reuseParentDynScope; // Only can be true in Full+
    private boolean metaClassBodyScope;

    private InterpreterEngine engine;
    public final Supplier<List<Instr>> instructionsCallback;
    private EnumSet<IRFlags> flags;

    private final IRScope scope;

    public InterpreterContext(IRScope scope, List<Instr> instructions, int temporaryVariableCount, EnumSet<IRFlags> flags) {
        this.scope = scope;

        // FIXME: Hack null instructions means coming from FullInterpreterContext but this should be way cleaner
        // For impl testing - engine = determineInterpreterEngine(scope);
        this.engine = instructions == null ? DEFAULT_INTERPRETER : STARTUP_INTERPRETER;

        this.metaClassBodyScope = scope instanceof IRMetaClassBody;
        setInstructions(instructions);
        this.instructionsCallback = null; // engine != null
        this.temporaryVariableCount = temporaryVariableCount;
        this.flags = flags;
    }

    public InterpreterContext(IRScope scope, Supplier<List<Instr>> instructions, int temporaryVariableCount, EnumSet<IRFlags> flags) {
        this.scope = scope;

        this.metaClassBodyScope = scope instanceof IRMetaClassBody;
        this.instructionsCallback = instructions;
        this.temporaryVariableCount = temporaryVariableCount;
        this.flags = flags;
    }

    protected void initialize() {
        if (instructions == null) getEngine();
    }

    public InterpreterEngine getEngine() {
        if (engine == null) {
            setInstructions(instructionsCallback.get());

            // FIXME: Hack null instructions means coming from FullInterpreterContext but this should be way cleaner
            // For impl testing - engine = determineInterpreterEngine(scope);
            this.engine = instructions == null ? DEFAULT_INTERPRETER : STARTUP_INTERPRETER;
        }
        return engine;
    }

    public Instr[] getInstructions() {
        initialize();

        return instructions == null ? NO_INSTRUCTIONS : instructions;
    }

    private void setInstructions(final List<Instr> instructions) {
        this.instructions = instructions != null ? prepareBuildInstructions(instructions) : null;
    }

    private Instr[] prepareBuildInstructions(List<Instr> instructions) {
        int length = instructions.size();
        Instr[] linearizedInstrArray = instructions.toArray(new Instr[length]);

        for (int ipc = 0; ipc < length; ipc++) {
            Instr i = linearizedInstrArray[ipc];

            if (i instanceof LabelInstr) ((LabelInstr) i).getLabel().setTargetPC(ipc + 1);
        }

        Stack<Integer> markers = new Stack();
        rescueIPCs = new int[length];
        int rpc = -1;

        for (int ipc = 0; ipc < length; ipc++) {
            Instr i = linearizedInstrArray[ipc];

            if (i instanceof ExceptionRegionStartMarkerInstr) {
                rpc = ((ExceptionRegionStartMarkerInstr) i).getFirstRescueBlockLabel().getTargetPC();
                markers.push(rpc);
            } else if (i instanceof ExceptionRegionEndMarkerInstr) {
                markers.pop();
                rpc = markers.isEmpty() ? -1 : markers.peek().intValue();
            }

            rescueIPCs[ipc] = rpc;
        }

        return linearizedInstrArray;
    }

    public int[] getRescueIPCs() {
        return rescueIPCs;
    }

    public int getRequiredArgsCount() {
        return getStaticScope().getSignature().required();
    }

    public IRScope getScope() {
        return scope;
    }

    public CFG getCFG() {
        return null;
    }

    public int getTemporaryVariableCount() {
        return temporaryVariableCount;
    }

    public Object[] allocateTemporaryVariables() {
        return temporaryVariableCount > 0 ? new Object[temporaryVariableCount] : null;
    }

    public boolean[] allocateTemporaryBooleanVariables() {
        return null;
    }

    public long[] allocateTemporaryFixnumVariables() {
        return null;
    }

    public double[] allocateTemporaryFloatVariables() {
        return null;
    }

    public StaticScope getStaticScope() {
        return scope.getStaticScope();
    }

    public String getFileName() {
        return scope.getFile();
    }

    public RubySymbol getName() {
        return scope.getManager().getRuntime().newSymbol(scope.getId());
    }

    public void computeScopeFlagsFromInstructions() {
        for (Instr instr : getInstructions()) {
            instr.computeScopeFlags(scope, getFlags());
        }
    }

    
Get a new dynamic scope. Note: This only works for method scopes (ClosureIC will throw).
/** * Get a new dynamic scope. Note: This only works for method scopes (ClosureIC will throw). */
public DynamicScope newDynamicScope(ThreadContext context) { // Add a parent-link to current dynscope to support non-local returns cheaply. This doesn't // affect variable scoping since local variables will all have the right scope depth. if (metaClassBodyScope) return DynamicScope.newDynamicScope(getStaticScope(), context.getCurrentScope()); return DynamicScope.newDynamicScope(getStaticScope()); } public boolean hasExplicitCallProtocol() { initialize(); return hasExplicitCallProtocol; } public void setExplicitCallProtocol(boolean callProtocol) { this.hasExplicitCallProtocol = callProtocol; } public boolean isDynamicScopeEliminated() { return dynamicScopeEliminated; } public void setDynamicScopeEliminated(boolean dynamicScopeEliminated) { this.dynamicScopeEliminated = dynamicScopeEliminated; } public boolean pushNewDynScope() { initialize(); return !dynamicScopeEliminated && !reuseParentDynScope; } public boolean reuseParentDynScope() { initialize(); return reuseParentDynScope; } public void setReuseParentDynScope(boolean reuseParentDynScope) { this.reuseParentDynScope = reuseParentDynScope; } public boolean popDynScope() { initialize(); return pushNewDynScope() || this.reuseParentDynScope(); } public boolean receivesKeywordArguments() { return scope.receivesKeywordArgs(); } @Override public String toString() { StringBuilder buf = new StringBuilder(); buf.append(getFileName()).append(':').append(scope.getLine()); if (getName() != null) buf.append(' ').append(getName()).append("\n"); if (getInstructions() == null) { buf.append(" No Instructions. Full Build before linearizeInstr?"); } else { buf.append(toStringInstrs()).append("\n"); } return buf.toString(); } public String toStringInstrs() { StringBuilder b = new StringBuilder(); int length = instructions.length; for (int i = 0; i < length; i++) { if (i > 0) b.append("\n"); b.append(" ").append(i).append('\t').append(instructions[i]); } /* ENEBO: I this this is too much output espectially for ic and not fic Collection<IRClosure> nestedClosures = scope.getClosures(); if (nestedClosures != null && !nestedClosures.isEmpty()) { b.append("\n\n------ Closures encountered in this scope ------\n"); for (IRClosure c: nestedClosures) b.append(c.toStringBody()); b.append("------------------------------------------------\n"); }*/ return b.toString(); } public EnumSet<IRFlags> getFlags() { return flags; } }