package s3.core.domain;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import ovm.core.domain.Code;
import ovm.core.domain.Type;
import ovm.core.execution.CoreServicesAccess;
import ovm.core.repository.Attribute;
import ovm.core.repository.Constants;
import ovm.core.repository.ExceptionHandler;
import ovm.core.repository.RepositoryClass;
import ovm.util.ByteBuffer;
import ovm.util.HTString2int;
import ovm.core.services.memory.VM_Address;
import s3.util.PragmaTransformCallsiteIR.BCbootTime;

public class S3NativeCode extends S3Code {

    public static Code.Kind KIND = new Code.Kind() {};

    private final Linker     linker;
    private  int             baseAddress;
    private  Constants       constants_;
    private  boolean         synchronized_;
    private  byte[]          bytes;
    private  int             maxLocals_;
    private  int             maxStack_;
    private  int             argLen_;
    private final byte[]     sourceFileName;
    private final int[]      bytecodePC2NativePC;

    private final Attribute.LineNumberTable lnt;
    private final VM_Address                codeEntry; // A pointer to the first element in the byte array <i>bytes</i>
    private final CoreServicesAccess        myCSA_;
    private  ExceptionHandler[]             exceptions_;   
    private  byte[]                         methodname_;// For JIT debugging.
    /** The argument length (including the receiver) in bytes. Used by
     * for SimpleJIT to do INVOKESYSTEM.INVOKE. */
    private int argumentLength_;

    public S3NativeCode( S3Method method, byte[] nativeCode, Linker linker,
        ExceptionHandler[] nativeExceptionHandlers,  Constants constants,
        int maxLocals,  int maxStack,  int argLen, int[] bytecodePC2NativePC,
        Attribute.LineNumberTable lnt,  int byteArrayHeaderSize)  throws BCbootTime {

        super(method);
       
        try {// Store the method name as zero terminated bytes for debugging 
            ByteArrayOutputStream str = new ByteArrayOutputStream();
            method.getSelector().write(str);
            str.write(0);
            methodname_ = str.toByteArray();
        } catch (IOException e) {  throw new RuntimeException(e);  }
        this.bytes = nativeCode;
        this.codeEntry = VM_Address.fromInt(VM_Address.fromObject(nativeCode).asInt() + byteArrayHeaderSize);
        this.linker = linker;
        this.synchronized_ = method.getMode().isSynchronized();
        this.constants_ = constants;
        this.myCSA_ = method.getDeclaringType().getDomain().getCoreServicesAccess();
        this.exceptions_ = nativeExceptionHandlers;
        this.maxLocals_ = maxLocals;
        this.maxStack_ = maxStack;
        this.argLen_ = argLen;
        this.lnt = lnt;
        this.bytecodePC2NativePC = bytecodePC2NativePC;

        // Process the source file name
        Type.Compound declType = method.getDeclaringType();
        String sourceFile = "[no source file]";
        if ( declType instanceof Type.Scalar) {
            RepositoryClass rc = ((Type.Scalar) declType).getRepositoryClass();
            Attribute[] attrs = rc.getAttributes();
            for ( int i = 0; i < attrs.length; i++)
                if (attrs[i] instanceof Attribute.SourceFile) {
                    sourceFile = ((Attribute.SourceFile) attrs[i]).getSourceFileName();
                    break;
                }
        }
        byte[] raw_bytes = sourceFile.getBytes();
        byte[] zero_terminated_bytes = new byte[raw_bytes.length + 1];
        for (int i = 0; i < raw_bytes.length; i++) 
            zero_terminated_bytes[i] = raw_bytes[i];
        zero_terminated_bytes[zero_terminated_bytes.length - 1] = 0;
        this.sourceFileName = zero_terminated_bytes;
        int receiver_sz = 4;
        this.argumentLength_ = receiver_sz + method.getArgumentLength();
    }
    
    public Code.Kind getKind() { return KIND; }

    public void bang(Code c) { bang( (S3NativeCode)c); }

    public void bang(S3NativeCode c) {
	if ( (getMethod() != c.getMethod()) ||
	     (linker != c.linker) )
	    throw new Error("Assertion failed");
	this.bytes = c.bytes;
	this.maxStack_ = c.maxStack_;
	this.maxLocals_ = c.maxLocals_;
	this.exceptions_ = c.exceptions_;
	this.baseAddress = c.baseAddress;
	this.constants_ = c.constants_;
	this.synchronized_ = c.synchronized_;	
	this.argLen_ = c.argLen_;
	this.methodname_ = c.methodname_;
    }

    public VM_Address getCodeEntry() { return codeEntry; }
    public byte[] getBytes() { return bytes;  }
    public int getLineNumber(int pc) {
	return lnt == null ? pc : lnt.getLineNumber(pc);
    }
    public String[] getExternFunctions() { return linker.getExternFunctions(); }  
    public int getBaseAddress() { return baseAddress; }
    public void relocate(int baseAddr, HTString2int extfunctions) {
	this.baseAddress = baseAddr;
	linker.linkRelativeJumps(bytes);
	linker.linkAbsoluteRefs2Bytecode(bytes, baseAddr);
	linker.linkAbsoluteRefs(bytes, baseAddr);
	linker.linkCalls(bytes, baseAddr, extfunctions);
    }
    public ExceptionHandler[] getExceptionHandlers() { return exceptions_;  } 
    public Attribute[] getAttributes() {
	if (lnt != null)
	    return new Attribute[] { lnt };
	else
	    return new Attribute[0];
    }
    public static abstract class Linker {
        public abstract void linkRelativeJumps(byte[] code);
        public abstract void linkAbsoluteRefs2Bytecode(byte[] code, int baseAddress);
        public abstract void linkAbsoluteRefs(byte[] code, int baseAddress);
        public abstract void linkCalls(byte[] code, int baseAddress, HTString2int functionPointers);
        public abstract void linkRelativeJumps(ByteBuffer code);
        public abstract void linkAbsoluteRefs2Bytecode(ByteBuffer code, int baseAddress);
        public abstract void linkAbsoluteRefs(ByteBuffer code, int baseAddress);
        public abstract void linkCalls(ByteBuffer code, int baseAddress, HTString2int functionPointers);
        public abstract String[] getExternFunctions();
    }

}
