package s3.services.simplejit.bytecode;

import ovm.core.domain.Blueprint;
import ovm.core.domain.LinkageException;
import ovm.core.domain.Type;
import ovm.core.repository.Descriptor;
import ovm.core.repository.ExceptionHandler;
import ovm.core.repository.TypeName;
import ovm.services.bytecode.Instruction;
import ovm.services.bytecode.InstructionBuffer;
import ovm.services.bytecode.analysis.AbstractValue;
import ovm.services.bytecode.analysis.Frame;
import ovm.services.bytecode.analysis.State;
import ovm.services.bytecode.analysis.WorkList;
import s3.core.domain.S3ByteCode;
import s3.core.domain.S3Method;
import s3.services.bytecode.analysis.S3Frame;
import s3.services.bytecode.analysis.S3Heap;
import s3.services.bytecode.verifier.VerificationFixpointIterator;
import s3.services.simplejit.bytecode.SimpleJITValue.SimpleJITArray;
import s3.services.simplejit.bytecode.SimpleJITValue.SimpleJITReference;
import s3.services.simplejit.ir.SimpleJITBasicBlock;

// FIXME: we may want to directly extend FixpointIterator in the
// future, that may simplify the code a bit (we may just have to override
// some abstract method).  This is due to a refactoring
// of the FI code.  But this should definitely still work just fine.  -- CG
public class SimpleJITFixpointIterator 
    extends VerificationFixpointIterator {

    protected S3Method s3Method;
    protected SimpleJITAbstractInterpreter sjai;

    public SimpleJITFixpointIterator
	(AbstractValue.Factory avf,
	 SimpleJITAbstractInterpreter sjai,
	 S3Method me,
	 InstructionBuffer code) {
	super(avf,
	      sjai,
	      me.getRepositoryMethod(),
	      code);
	s3Method = me;
	this.sjai = sjai;
    }

    private void log(String message) {
	if (SimpleJITAbstractInterpreter.LOG_ENABLED)
	    sjai.addLogByFixpointIterator(message);
    }

    public Instruction next() {
	Frame f = ai.getFrame();
	if (sjai.LOG_ENABLED) {
	    if (f != null) {
		log("PC " + ai.getPC() + " : " 
		    + ((S3Frame)f).toMultiLineString());
		log("worklist : " + worklist);
	    }
	}

        WorkList.Entry curr = null;
        while (worklist.hasNext()) {
            curr = worklist.remove();
	    f = merge(curr.frame, 
		      curr.tryMerge == WorkList.MERGECLONE, 
		      curr.pc);
            if (f == null)
                curr = null; // do not simulate this one again
            else {
                curr.frame = f;
                break;
            }
        }
        if (curr == null)
            return null;
        ai.setFrameAndPC(curr.frame, curr.pc);
	sjai.setCurrentBlockByPC(curr.pc);
	return buf.get(curr.pc);
    }

    public State getState() { return state; }

    public void printStateLog() {
	if (! sjai.LOG_ENABLED)
	    return;
	S3ByteCode bc = (S3ByteCode)s3Method.getCode(S3ByteCode.KIND);
	int bclength = bc.getBytes().length;
	if (sjai.LOG_ENABLED) log("Printing States :");
	for(int pc = 0; pc < bclength; pc++) {
	    State.Iterator it = state.iterator(pc);
	    int cnt = 0;
	    int frameCount = state.getFrameCountAt(pc);
	    if (frameCount > 1)
		if (sjai.LOG_ENABLED) log("Multiple frames at PC " + pc);
	    for(Frame f = it.currentFrame(); 
		f != null; 
		f = it.nextFrame()) {
		if (sjai.LOG_ENABLED) 
		    log("PC " + pc 
			+ " frame# " + cnt + " "
			+ ((S3Frame)f).toMultiLineString());
		cnt++;
	    }
	}
    }

    public Frame makeInitialFrame() {
	S3ByteCode rbcf 
	    = (S3ByteCode)s3Method.getCode(S3ByteCode.KIND);
	Frame frame = new S3Frame.Factory(new S3Heap.Factory())
	    .makeFrame((rbcf.getMaxStack() + 3) * 2, 
		       rbcf.getMaxLocals());
        Descriptor.Method desc = sel.getDescriptor();
	Type.Compound declaringType = s3Method.getDeclaringType();
	int pos = 0, cnt = 0, argCount = desc.getArgumentCount();
	frame.store(pos++,
		    new SimpleJITReference
		    (sjai.blueprintFor(sjai.getTargetDomain(), 
				       declaringType)));
	while (cnt < argCount) {
	    Type t = null;
	    try {
		t = s3Method.getArgumentType(cnt);
		cnt++;
		frame.store
		    (pos, 
		     sjai.type2SimpleJITValue(sjai.getTargetDomain(), t));
		pos++;
		if (t.isWidePrimitive())
		    frame.store(pos++, null);
	    } catch (LinkageException e) { 
		// should have come from the getArgumentType call
		TypeName tn = desc.getArgumentType(cnt++);
		if (tn.isArray())
		    frame.store(pos++, new SimpleJITArray());
		else
		    frame.store(pos++, new SimpleJITReference());
	    }
	}
	return frame;
    }

    protected Frame merge(Frame frame, boolean cloneFrame, int pc) {
	// A flag indicating whether <code>frame</code> was
	// successfully merged with one of the stored frame at the PC
	boolean wasMerged = false;

	// A flag indicating whether <code>frame</code> was
	// successfully included in one of the stored frame at the PC
	boolean wasIncluded = false;

	// An array to temporarily store all the frames stored in the
	// State for this PC
	Frame[] storedFrames = new Frame[state.getFrameCountAt(pc)];

        State.Iterator it = state.iterator(pc);

	// Remove all the frames from the state temporarily
	int i = 0;
        for (Frame f = it.currentFrame(); 
	     f != null; 
	     f = it.removeFrame()) {
	    storedFrames[i++] = f;
	}

	// Test whether <code>frame</code> can be included or merged
	// with each of the frames
        for (i = 0; i < storedFrames.length; i++) {
	    Frame old = storedFrames[i];
	    if (sjai.LOG_ENABLED) 
		log("INCLUSION TESTING PC " +  pc + " FRAMES\n" + old 
		    + " VS \n" + frame);
	    if (old.includes(frame)) {
		if (sjai.LOG_ENABLED) 
		    log("INCLUDED");
		wasIncluded = true;
		continue;
	    }
	    if (sjai.LOG_ENABLED) 
		log("NOT INCLUDED");
	    
	    if (sjai.LOG_ENABLED) 
		log("MERGING PC " + pc + " FRAMES\n" + old 
		    + " AND \n" + frame);
	    Frame merged = old.merge(frame);
	    if (merged == null) {
		if (sjai.LOG_ENABLED) 
		    log("MERGE FAILURE");
	    } else {
		wasMerged = true;
		if (sjai.LOG_ENABLED) 
		    log("MERGE SUCCESS : \n" + merged);
		// replace it
		//storedFrames[i] = merged.cloneFrame();
		storedFrames[i] = null;
		if (sjai.LOG_ENABLED) 
		    log("ADDING THE MERGED FRAME TO WORKLIST");
		addIfNew(pc, 
			 //cloneFrame ? merged.cloneFrame() : merged,
			 merged,
			 WorkList.NOMERGE);
	    }
	}
	// Put all frames back in the State
	for(i = 0; i < storedFrames.length; i++)
	    if (storedFrames[i] != null)
		state.addFrameAt(pc, storedFrames[i]);

	if (wasIncluded || wasMerged)
	    return null;
	else {
	    if (sjai.LOG_ENABLED) 
		log("ADDING TO STATE AT PC " + pc + " (a clone of)\n" 
		    + frame);
	    state.addFrameAt(pc, frame.cloneFrame());
	    return cloneFrame
		? frame.cloneFrame()
		: frame;
	}
    }

    public void addIfNew(int pc, Frame f, int merge) {
	worklist.addIfNew(pc, f, merge);
    }

    protected void addBlock(int pc) {
	SimpleJITBasicBlock b = sjai.getOrCreateBlockAtEntryPC(pc);
	sjai.getCurrentBlock().addEdgeTo(b);
	if (sjai.isInBlock(pc)) {
	    // split the block
	    SimpleJITBasicBlock previous =
		sjai.searchForBlockBackward(pc);
	    previous.split(b);
	}
	sjai.markAsBlockStart(pc);
    }

    protected void addBlockForExceptionHandler(int pc, TypeName.Scalar ex) {
	SimpleJITBasicBlock b = sjai.getOrCreateHandlerBlockAtEntryPC(pc, ex);
	sjai.getCurrentBlock().addEdgeTo(b);
	if (sjai.isInBlock(pc)) {
	    // split the block
	    SimpleJITBasicBlock previous =
		sjai.searchForBlockBackward(pc);
	    previous.split(b);
	}
	sjai.markAsBlockStart(pc);
    }

    protected void linkBlock(int pc) {
	SimpleJITBasicBlock b = sjai.getBlockAtEntryPC(pc);
	if (b != null)
	    sjai.getCurrentBlock().addEdgeTo(b);
	else
	    sjai.markAsInBlock(pc);
    }

    public MergeStrategyVisitor makeStrategyVisitor() {
        return new SimpleJITMergeStrategyVisitor();
    }

    public class SimpleJITMergeStrategyVisitor 
	extends MergeStrategyVisitor {

        public void visit(Instruction i) {
	    int next_pc = ai.getPC() + i.size(ai.getPosition());
            if (i instanceof Instruction.ExceptionThrower)
                visit((Instruction.ExceptionThrower) i, next_pc);
            else {
                add(next_pc,
                    ai.getFrame(),
                    WorkList.NOMERGE);
		linkBlock(next_pc);
	    }
        }

        public void visit(Instruction.FlowEnd i) {
	    sjai.getCurrentBlock().markAsFlowEndBlock();
        }

        public void visit(Instruction.ATHROW i) {
            __addAllExceptionTargets(i);
	    sjai.getCurrentBlock().markAsFlowEndBlock();
        }

        public void visit(Instruction.ExceptionThrower i, int next_pc) {
            add(next_pc,
                ai.getFrame(),
                WorkList.NOMERGE);
            boolean blockEndsHere = __addAllExceptionTargets(i);
	    if (blockEndsHere)
		addBlock(next_pc);
	    else
		linkBlock(next_pc);
        }

        public void visit(Instruction.UnconditionalJump i) {
            add(ai.getPC() + i.getTarget(ai.getPosition()),
                ai.getFrame(),
                WorkList.MERGE);
	    addBlock(ai.getPC() + i.getTarget(ai.getPosition()));
        }

        public void visit(Instruction.ConditionalJump i) {
            add(ai.getPC() + i.getBranchTarget(ai.getPosition()),
                ai.getFrame(),
                WorkList.MERGE);
	    addBlock(ai.getPC() + i.getBranchTarget(ai.getPosition()));
            add(ai.getPC() + i.size(ai.getPosition()),
                ai.getFrame(),
                WorkList.MERGECLONE);
	    addBlock(ai.getPC() + i.size(ai.getPosition()));
        }

        public void visit(Instruction.Switch i) {
            add(ai.getPC() + i.getDefaultTarget(ai.getPosition()),
                ai.getFrame(),
                WorkList.MERGE);
	    addBlock(ai.getPC() + i.getDefaultTarget(ai.getPosition()));
            int[] t = i.getTargets(ai.getPosition());
            for (int j = t.length - 1; j >= 0; j--) {
                add(ai.getPC() + t[j],
                    ai.getFrame(),
                    WorkList.MERGECLONE);
		addBlock(ai.getPC() + t[j]);
	    }
        }

        public void visit(Instruction.RET i) {
            AbstractValue av = 
		ai.getFrame().load(i.getLocalVariableOffset(ai.getPosition()));
            add(av.getJumpTarget().getTarget(),
                ai.getFrame(),
                WorkList.MERGE);
	    addBlock(av.getJumpTarget().getTarget());
        }

        protected boolean __addAllExceptionTargets(Instruction.ExceptionThrower i) {
            Frame f = ai.getFrame();
            TypeName.Scalar[] ex = i.getThrowables();
            S3ByteCode rbcf = 
		(S3ByteCode)s3Method.getCode(S3ByteCode.KIND);
            ExceptionHandler[] targets = rbcf.getExceptionHandlers();
            int pc = ai.getPC();
	    boolean added = false;
            for (int j = 0; j < ex.length; j++) {
		Blueprint throwableBP = 
		    sjai.blueprintFor(sjai.targetDomain, ex[j]);
                for (int k = 0; k < targets.length; k++) {
                    ExceptionHandler rh = targets[k];
		    Blueprint catchBP =
			sjai.blueprintFor(sjai.targetDomain, 
					  rh.getCatchTypeName());
                    if (rh.getStartPC() <= pc
			&& rh.getEndPC() > pc
			&& throwableBP != null
			&& catchBP != null) {
			added = true;
                        f = f.cloneFrame();
                        f.clearStack();
                        f.push(new SimpleJITReference(catchBP));
                        add(rh.getHandlerPC(), f, WorkList.MERGE);
			addBlockForExceptionHandler(rh.getHandlerPC(), 
						    rh.getCatchTypeName());
                    }
                }
            }
	    return added;
        }
    }
    
}
