package s3.services.simplejit;

import ovm.util.ArrayList;
import ovm.util.ByteBuffer;
import ovm.util.HTString2int;
import s3.core.domain.S3NativeCode;

/**
 * Responsible for patching native code generated by the compiler in connection
 * with relative/absolute branch offsets and function calls.
 * 
 * @author Hiroshi Yamauchi
 */
public abstract class CodeLinker extends S3NativeCode.Linker {

    private ArrayList relativeJumps;

    private ArrayList absoluteRef2BCs;

    private ArrayList absoluteRefs;

    private ArrayList calls;

    private int[] bytecodePC2NativePC;

    public CodeLinker(int[] bytecodePC2NativePC) {
        this.relativeJumps = new ArrayList();
        this.absoluteRef2BCs = new ArrayList();
        this.absoluteRefs = new ArrayList();
        this.calls = new ArrayList();
        this.bytecodePC2NativePC = bytecodePC2NativePC;
    }

    protected abstract RelativeJump makeRelativeJump(int branchPC, int shift,
            int bits, int targetBytecodePC);

    protected abstract AbsoluteRef2BC makeAbsoluteRef2BC(int refererPC,
            int refereeBytecodePC);

    protected abstract AbsoluteRef makeAbsoluteRef(int refererPC, int refereePC);

    protected abstract Call makeCall(int callSitePC, String functionSymbol);

    void registerRelativeJump(int branchPC, int targetBytecodePC) {
        registerRelativeJump(branchPC, 0, 32, targetBytecodePC);
    }

    public void registerRelativeJump(int branchPC,
                              int shift,
                              int bits,
                              int targetBytecodePC) {
        relativeJumps.add(makeRelativeJump(branchPC, shift, bits, targetBytecodePC));
    }
    
    void registerAbsoluteRef2BC(int refererPC, int refereeBytecodePC) {
        absoluteRef2BCs.add(makeAbsoluteRef2BC(refererPC, refereeBytecodePC));
    }

    void registerAbsoluteRef(int refererPC, int refereePC) {
        absoluteRefs.add(makeAbsoluteRef(refererPC, refereePC));
    }

    void registerCall(int callSitePC, String functionSymbol) {
        calls.add(makeCall(callSitePC, functionSymbol));
    }

    public void linkRelativeJumps(byte[] code) {
        Object[] patchers = relativeJumps.toArray();
        for (int i = 0; i < patchers.length; i++) {
            ((RelativeJump) patchers[i]).patch(code, bytecodePC2NativePC);
        }
    }

    public void linkAbsoluteRefs2Bytecode(byte[] code, int baseAddress) {
        Object[] patchers = absoluteRef2BCs.toArray();
        for (int i = 0; i < patchers.length; i++) {
            ((AbsoluteRef2BC) patchers[i]).patch(code, baseAddress,
                    bytecodePC2NativePC);
        }
    }

    public void linkAbsoluteRefs(byte[] code, int baseAddress) {
        Object[] patchers = absoluteRefs.toArray();
        for (int i = 0; i < patchers.length; i++) {
            ((AbsoluteRef) patchers[i]).patch(code, baseAddress);
        }
    }

    public void linkCalls(byte[] code, int baseAddress,
            HTString2int functionPointers) {
        Object[] patchers = calls.toArray();
        for (int i = 0; i < patchers.length; i++) {
            ((Call) patchers[i]).patch(code, baseAddress, functionPointers);
        }
    }

    public void linkRelativeJumps(ByteBuffer code) {
        Object[] patchers = relativeJumps.toArray();
        for (int i = 0; i < patchers.length; i++) {
            ((RelativeJump) patchers[i]).patch(code, bytecodePC2NativePC);
        }
    }

    public void linkAbsoluteRefs2Bytecode(ByteBuffer code, int baseAddress) {
        Object[] patchers = absoluteRef2BCs.toArray();
        for (int i = 0; i < patchers.length; i++) {
            ((AbsoluteRef2BC) patchers[i]).patch(code, baseAddress,
                    bytecodePC2NativePC);
        }
    }

    public void linkAbsoluteRefs(ByteBuffer code, int baseAddress) {
        Object[] patchers = absoluteRefs.toArray();
        for (int i = 0; i < patchers.length; i++) {
            ((AbsoluteRef) patchers[i]).patch(code, baseAddress);
        }
    }

    public void linkCalls(ByteBuffer code, int baseAddress,
            HTString2int functionPointers) {
        Object[] patchers = calls.toArray();
        for (int i = 0; i < patchers.length; i++) {
            ((Call) patchers[i]).patch(code, baseAddress, functionPointers);
        }
    }

    public String[] getExternFunctions() {
        Object[] patchers = calls.toArray();
        String[] functions = new String[patchers.length];
        for (int i = 0; i < patchers.length; i++) {
            functions[i] = ((Call) patchers[i]).getFunctionSymbol();
        }
        return functions;
    }

    protected abstract static class RelativeJump {
        protected int branchPC;
        protected int shift;
        protected int bits;
        protected int targetBytecodePC;

        public RelativeJump(int branchPC, int shift, int bits, int targetBytecodePC) {
            this.branchPC = branchPC;
            this.shift = shift;
            this.bits = bits;
            this.targetBytecodePC = targetBytecodePC;
        }

        public abstract void patch(byte[] code, int[] bytecodePC2NativePC);

        public abstract void patch(ByteBuffer code, int[] bytecodePC2NativePC);
    }

    protected abstract static class AbsoluteRef2BC {
        protected int refererPC;

        protected int refereeBytecodePC;

        public AbsoluteRef2BC(int refererPC, int refereeBytecodePC) {
            this.refererPC = refererPC;
            this.refereeBytecodePC = refereeBytecodePC;
        }

        public abstract void patch(byte[] code, int baseAddress,
                int[] bytecodePC2NativePC);

        public abstract void patch(ByteBuffer code, int baseAddress,
                int[] bytecodePC2NativePC);
    }

    protected abstract static class AbsoluteRef {
        protected int refererPC;

        protected int refereePC;

        public AbsoluteRef(int refererPC, int refereePC) {
            this.refererPC = refererPC;
            this.refereePC = refereePC;
        }

        public abstract void patch(byte[] code, int baseAddress);

        public abstract void patch(ByteBuffer code, int baseAddress);
    }

    protected abstract static class Call {
        protected int callSitePC;

        protected String functionSymbol;

        public Call(int callSitePC, String functionSymbol) {
            this.callSitePC = callSitePC;
            this.functionSymbol = functionSymbol;
        }

        public String getFunctionSymbol() {
            return functionSymbol;
        }

        public abstract void patch(byte[] code, int baseAddress,
                HTString2int functionPointers);

        public abstract void patch(ByteBuffer code, int baseAddress,
                HTString2int functionPointers);
    }
}