package s3.services.simplejit;


import ovm.core.repository.ExceptionHandler;
import ovm.util.ByteBuffer;
import ovm.core.execution.NativeConstants;
import ovm.util.HTString2int;
import s3.core.domain.S3ByteCode;
import s3.services.simplejit.Assembler.Branch;

/**
 * @author Hiroshi Yamauchi
 **/
public abstract class CodeGenContext  {

    /**
     * The buffer for the generated machine code
     **/
    private ByteBuffer codeBuffer;
    
    /**
     * A mapping from a bytecode pc to its corresponding native pc
     **/
    private int[] bytecodePC2NativePC;

    /**
     * An array of native exception handlers
     **/
    private ExceptionHandler[] nativeExceptionHandlers;

    /**
     * Relative/absolute/call code linker
     **/
    protected CodeLinker codeLinker;

    private int bytecode_len;

    public CodeGenContext(int bytecode_len) {
	this.bytecode_len = bytecode_len;
	codeBuffer = ByteBuffer.allocate((bytecode_len+20) * 200);
	codeBuffer.order(NativeConstants.BYTE_ORDER);
	bytecodePC2NativePC = new int[bytecode_len];
	codeLinker = makeCodeLinker(bytecodePC2NativePC);
    }

    protected void __assert(String msg, boolean cond) {
	if (! cond)
	    throw new Error("Assertion failure : " + msg);
    }

    public int[] getBytecodePC2NativePC() {
	return bytecodePC2NativePC;
    }

    protected abstract CodeLinker makeCodeLinker(int[] _bytecodePC2NativePC);

    public ExceptionHandler[] 
	getNativeExceptionHandlers(S3ByteCode bytecode) {
	if (nativeExceptionHandlers != null)
	    return nativeExceptionHandlers;

	ExceptionHandler[] bceh = bytecode.getExceptionHandlers();
	nativeExceptionHandlers = new ExceptionHandler[bceh.length];
	for(int i = 0; i < bceh.length; i++) {
	    nativeExceptionHandlers[i] =
		new ExceptionHandler((char)bytecodePC2NativePC[bceh[i].getStartPC()],
				       (char)bytecodePC2NativePC[bceh[i].getEndPC()],
				       (char)bytecodePC2NativePC[bceh[i].getHandlerPC()],
				       bceh[i].getCatchTypeName());
	}
	return nativeExceptionHandlers;
    }

    public Assembler getAssembler() {
	throw new Error();
    }

    /*
    public int getStackTop() {
	return stackHeight[pc];
    }
    */

    public ByteBuffer getCodeBuffer() {
	return codeBuffer;
    }

    public CodeLinker getCodeLinker() {
	return codeLinker;
    }

    /**
     * Note that patching may not have done yet
    public byte[] getCopyOfNativeCode() {
	byte[] retNativeCode = new byte[nativePC];
	System.arraycopy(nativeCode, 0,
			 retNativeCode, 0,
			 nativePC);
	return retNativeCode;
    }	
    **/

    public ByteBuffer getNativeCodeBuffer() {
	return codeBuffer;
    }

    public int getNativePC() {
	return codeBuffer.position();
    }

    /* deprecated */
    byte[] nativeCode;
    int nativePC;

    public byte[] getNativeCode() {
	throw new Error();
	//return nativeCode;
    }

    public byte getNativeCode(int pc) {
	throw new Error();
	//return nativeCode[pc];
    }

    public void incNativePC(int inc) {
	throw new Error();
	/*
	nativePC += inc;
	__assert("Context.incNativePC", 
	       0 <= nativePC && nativePC < nativeCode.length);
	*/
    }

    /* end of deprecated */

    /**
     * @return native pc if the native pc is set yet,  otherwise -1.
     **/
    public int getBytecodePC2NativePC(int bpc) {
	__assert("Context.getBytecodePC2NativePC",
	       0 <= bpc && bpc < bytecodePC2NativePC.length);
	return bytecodePC2NativePC[bpc];
    }
    public void setBytecodePC2NativePC(int bpc, int npc) {
	__assert("Context.getBytecodePC2NativePC, bytecode pc",
		 0 <= bpc && bpc < bytecode_len);
	__assert("Context.getBytecodePC2NativePC, native pc",
		 0 <= npc && npc < codeBuffer.capacity());
	bytecodePC2NativePC[bpc] = npc;
    }

    public void addRelativeJumpPatch(Branch branch, int targetBytecodePC) {
        codeLinker.registerRelativeJump(branch.sourcePC, branch.shift, branch.bits, targetBytecodePC);
    }
    
    /*
    public int getJavaStack2NativeStack(int joffset) {
	//__assert("Context.getJavaStack2NativeStack : " + joffset,
	//       0 <= joffset && joffset < stackLayout.getFrameSize());
	return stackLayout.getNativeOffset(joffset);
    }
    */

    public void addRelativeJumpPatch(int branchPC,
				     int targetBytecodePC) {
	codeLinker.registerRelativeJump(branchPC,
					targetBytecodePC);
    }

    public void addAbsoluteBCRefPatch(int refererPC,
				      int refereeBytecodePC) {
	codeLinker.registerAbsoluteRef2BC(refererPC,
					  refereeBytecodePC);
    }

    public void addAbsoluteNativeRefPatch(int refererPC,
					  int refereePC) {
	codeLinker.registerAbsoluteRef(refererPC,
				       refereePC);
    }

    public void addCallPatch(int callSitePC,
			     String functionName) {
	codeLinker.registerCall(callSitePC,
				functionName);
    }

    public void linkRelativeJumps() {
	codeLinker.linkRelativeJumps(codeBuffer);
    }

    public void linkAbsoluteRefs2Bytecode(int baseAddress) {
	codeLinker.linkAbsoluteRefs2Bytecode(codeBuffer, baseAddress);
    }

    public void linkCalls(int baseAddress,
			  HTString2int functionPointers) {
	codeLinker.linkCalls(codeBuffer, baseAddress, functionPointers);
    }


}
