package s3.services.transactions;

import ovm.core.domain.Blueprint;
import ovm.core.domain.ConstantResolvedInstanceFieldref;
import ovm.core.domain.ConstantResolvedInstanceMethodref;
import ovm.core.domain.ConstantResolvedInterfaceMethodref;
import ovm.core.domain.ConstantResolvedMethodref;
import ovm.core.domain.ConstantResolvedStaticFieldref;
import ovm.core.domain.ConstantResolvedStaticMethodref;
import ovm.core.domain.DomainDirectory;
import ovm.core.domain.LinkageException;
import ovm.core.domain.MemberResolver;
import ovm.core.domain.Method;
import ovm.core.domain.ResolutionInfo;
import ovm.core.execution.Native;
import ovm.core.repository.ConstantMethodref;
import ovm.core.repository.JavaNames;
import ovm.core.repository.RepositoryUtils;
import ovm.core.repository.Selector;
import ovm.core.repository.UTF8Store;
import ovm.core.repository.UnboundSelector;
import ovm.core.repository.TypeName.Scalar;
import ovm.services.bytecode.Instruction;
import ovm.services.bytecode.InstructionBuffer;
import ovm.services.bytecode.editor.CodeFragmentEditor;
import ovm.services.bytecode.editor.Cursor;
import ovm.services.bytecode.editor.CodeFragmentEditor.ExceptionHandlerList;
import ovm.util.BitSet;
import ovm.util.UnicodeBuffer;
import s3.core.domain.S3Blueprint;
import s3.services.bootimage.IRewriter;
import s3.util.PragmaException;
import s3.util.PragmaTransformCallsiteIR;

/**
 * This visitor insert calls to transaction logging methods. Before every memory write
 * the original value of the location will be saved. Method calls are transformed to
 * calls of the corresponding logging methods.
 */
public class PARifyingRewriteVisitor extends IRewriter.RewriteVisitor {
    
     
    public PARifyingRewriteVisitor(S3Transaction t, IRewriter ir, S3Blueprint bp,   CodeFragmentEditor cfe_) {
	ir.super(bp, false, false);  cfe = cfe_; transact = t;
    }

    BitSet specPC = new BitSet();
    boolean parLogged = false;
    S3Transaction transact;
    
    int READ_COUNT;
    int WRITE_COUNT;
    boolean METHOD_IGNORED = true;
    
    private boolean parSpecializeInstruction() { return specPC.get(getPC()); }
    
    public void beginEditing(InstructionBuffer buf, CodeFragmentEditor cfe) {
	super.beginEditing(buf, cfe);
//System.out.println("<< " + buf.getSelector() +" >>");	
	parLogged = buf.getSelector().getName().endsWith(transact.getTRANS_SUFFIX());
	specPC.set(0, buf.limit(), parLogged);
	for (ExceptionHandlerList curr = cfe.getExceptionHandlers(); curr != null; curr = curr.next()) {
	    if (curr.getExceptionType() == null || 
		    !curr.getExceptionType().equals(Transaction.ABORTED_EXCEPTION)) continue;
	    
	    if (parLogged) S3Transaction.NESTED_ATOMIC_BLOCK_COUNT++;
	    else S3Transaction.ATOMIC_BLOCK_COUNT++;
	    METHOD_IGNORED = false;
	    
	    if (parLogged) { curr.delete(); continue; }	                
	    int start = curr.getStart().getCursor().getPC();
	    int finish = curr.getEnd().getCursor().getPC();
	    specPC.set(start, finish - 1, true);		    		
	    for (ExceptionHandlerList inner = cfe.getExceptionHandlers(); inner != null; inner = inner.next()) {
		if (inner == curr || (inner.getExceptionType() != Transaction.ABORTED_EXCEPTION)) continue;
		int innerStart = inner.getStart().getCursor().getPC();
		if ((innerStart >= start) && (innerStart < finish)) S3Transaction.NESTED_ATOMIC_BLOCK_COUNT++;			    
		if ((innerStart >= start) && (innerStart < finish)) inner.delete();			    

	    }
	}      		    
	while (buf.hasRemaining()) buf.get();
	int lastPC = buf.getPC();
	buf.rewind();	   	    
    }
    
    private void parifyCall(ConstantResolvedMethodref imi, Instruction i) throws LinkageException {
	if (parSpecializeInstruction() && !isExecutive_) {
	    int idx = parSpecializeCall(imi);
	    if (imi instanceof ConstantResolvedInstanceMethodref) {
		imi = getConstants().resolveInstanceMethod(idx);		   
	    } else if (imi instanceof ConstantResolvedStaticMethodref) {
		imi = getConstants().resolveStaticMethod(idx);				    
	    } else if (imi instanceof ConstantResolvedInterfaceMethodref) {
		imi = getConstants().resolveInterfaceMethod(idx);		    
	    }
	    
	    if (i instanceof Instruction.INVOKE_NATIVE) {
		System.out.println("Invoking " + imi + " from atomic");
	    } else if (i instanceof Instruction.INVOKE_SYSTEM) {
		System.out.println("Invoking " + imi + " from atomic");
	    } else if (i instanceof Instruction.INVOKEINTERFACE) {
		cfe.replaceInstruction().addINVOKEINTERFACE(imi);
	    } else if (i instanceof Instruction.INVOKEVIRTUAL) {		
		cfe.replaceInstruction().addINVOKEVIRTUAL(imi);
	    } else if (i instanceof Instruction.INVOKESTATIC) {
		cfe.replaceInstruction().addINVOKESTATIC(imi);
	    } else if (i instanceof Instruction.INVOKESPECIAL) {
		cfe.replaceInstruction().addINVOKESPECIAL(imi);
	    }
	}
    }
    
    // Tranform a non-logged method selector into a logged method selector.  
    // The logged method selector is added to the constant pool and its index is returned.
    private int parSpecializeCall(ConstantMethodref ref) {
	Selector.Method sel = ref.asSelector();
	if (sel.getName().endsWith(transact.getTRANS_SUFFIX())) {		
	    if (ref instanceof ConstantResolvedInterfaceMethodref)  // return existing CP entry
		return cfe.getConstantsEditor().addInterfaceMethodref(ref);
	    else return cfe.getConstantsEditor().addMethodref(ref);
	}	
	String newName = sel.getName() + transact.getTRANS_SUFFIX();
	UnicodeBuffer newNameBuf = UnicodeBuffer.factory().wrap(newName);
	int idx = UTF8Store._.findUtf8(newNameBuf);
	if (idx == -1) throw new Error("can't find " + newName + " for " + sel + " in " + curBp_);
	Selector.Method newSel = Selector.Method.make(UnboundSelector.Method.make(idx, sel.getDescriptor()), sel.getDefiningClass());
	if (ref instanceof ConstantResolvedInterfaceMethodref)
	    return cfe.getConstantsEditor().addInterfaceMethodref(newSel);
	else return cfe.getConstantsEditor().addMethodref(newSel);
    }
    
    public void visit(Instruction.PUTFIELD i) {
	if (!this.parSpecializeInstruction()) return;		
	WRITE_COUNT++;
	try {    		
	    ConstantResolvedInstanceFieldref field =  cp.resolveInstanceField(i.getCPIndex(buf));
	    Cursor c = cfe.getCursorBeforeMarker(getPC());
	    boolean wide = field.getField().getType().isWidePrimitive();
	    if (!wide) { // tgt val  
		c.addSimpleInstruction(DUP2);  // tgt val ==> tgt val tgt val
		c.addSimpleInstruction(POP);   // tgt val tgt val ==> tgt val tgt
	    } else {  // tgt  valval
		c.addSimpleInstruction(DUP2_X1);  // valval tgt valval
		c.addSimpleInstruction(POP2);   // valval tgt
		c.addSimpleInstruction(DUP_X2);  // tgt valval tgt
		// tgt valval tgt 
	    }
	    addLogCall(c,field.getOffset(),wide);
	} catch (LinkageException e) {IRewriter.warn(i, e.getMessage());}
    }

    public void visit(Instruction.ArrayLoad i) {doRead();}
    public void visit(Instruction.GETFIELD i)  {doRead();}
    public void visit(Instruction.GETSTATIC i) {doRead();}
    private void doRead() {
	if(!Transaction.the().gatherStatistics())return;
	if (!parSpecializeInstruction()) return;
	READ_COUNT++;
	Cursor c = cfe.getCursorBeforeMarker(getPC());
	c.addResolvedRefLoadConstant(rtExportsOop()); 
	MemberResolver resolver = DomainDirectory.getUserDomain(1).getMemberResolver();
	UnboundSelector.Method meth = JavaNames.PAR_READ;
	ResolutionInfo ri = resolver.resolvePublicVirtualMethod(rtExportsBpt(), meth);
	Method m = resolver.resolveInstanceMethod(rtExportsBpt().getType().asCompound(), meth, null);
	ConstantResolvedInstanceMethodref imi = ConstantResolvedInstanceMethodref.make(m, ri.getOffset(), rtExportsBpt());
	c.addINVOKEVIRTUAL(imi);
  
    }
    public void visit(Instruction.PUTSTATIC i) {
	if (!parSpecializeInstruction() || isExecutive_) return;	// FIXME: why isExec??
	WRITE_COUNT++;
	try {    		
	    ConstantResolvedStaticFieldref field = getConstants().resolveStaticField(i.getCPIndex(buf));	         
	    boolean wide = field.getField().getType().isWidePrimitive();
	    Cursor c = cfe.getCursorBeforeMarker(getPC());
	    c.addLOAD_SHST_FIELD(i.getCPIndex(buf));  //  v? ==>  v? tgt
	    addLogCall(c,field.getOffset(),wide);
	} catch (LinkageException e) {IRewriter.warn(i, e.getMessage());}
    }
    private void addLogCall(Cursor c, int offset, boolean wide) {
	c.addResolvedRefLoadConstant(rtExportsOop());  // ==> -- tgt RTE
	c.addSimpleInstruction(SWAP); // -- RTE tgt
	c.addLoadConstant(offset); // -- RTE tgt off
	MemberResolver resolver = DomainDirectory.getUserDomain(1).getMemberResolver();
	UnboundSelector.Method meth = (!wide)? JavaNames.PAR_LOG : JavaNames.PAR_LOGW;
	ResolutionInfo ri = resolver.resolvePublicVirtualMethod(rtExportsBpt(), meth);
	Method m = resolver.resolveInstanceMethod(rtExportsBpt().getType().asCompound(), meth, null);
	ConstantResolvedInstanceMethodref imi = ConstantResolvedInstanceMethodref.make(m, ri.getOffset(), rtExportsBpt());
	c.addINVOKEVIRTUAL(imi);
    }
    
    public void visit(Instruction.ArrayStore i) {
	if (!this.parSpecializeInstruction()) return;
	
	WRITE_COUNT++;
	
	Cursor c = cfe.getCursorBeforeMarker(getPC());
	UnboundSelector.Method meth;
	if (!( i instanceof Instruction.DASTORE) &&
		!(i instanceof Instruction.LASTORE)) {   // t i v
	    c.addSimpleInstruction(DUP_X2); // v t i v
	    c.addSimpleInstruction(POP);    // v t i
	    c.addSimpleInstruction(DUP2_X1); // t i v t i
	    meth = JavaNames.PAR_LOG_ARR;
	} else { // t i vv
	    c.addSimpleInstruction(DUP2_X2); // vv t i vv
	    c.addSimpleInstruction(POP2);    // vv t i
	    c.addSimpleInstruction(DUP2_X2); // t i vv t i
	    meth = JavaNames.PAR_LOG_ARRW;
	}
	c.addResolvedRefLoadConstant(rtExportsOop()); // t i v? t i r
	c.addSimpleInstruction(DUP_X2); // t i v? r t i r
	c.addSimpleInstruction(POP); // t i v? r t i
	MemberResolver resolver = DomainDirectory.getUserDomain(1).getMemberResolver();	    
	ResolutionInfo ri = resolver.resolvePublicVirtualMethod(rtExportsBpt(), meth);
	Method m = resolver.resolveInstanceMethod(rtExportsBpt().getType().asCompound(), meth, null);
	ConstantResolvedInstanceMethodref imi = ConstantResolvedInstanceMethodref.make(m, ri.getOffset(), rtExportsBpt());
	c.addINVOKEVIRTUAL(imi);
    }
    public void visit(Instruction.INVOKEVIRTUAL i) {
	if (!this.parSpecializeInstruction()) return;
	try { ConstantResolvedInstanceMethodref vmi =  cp.resolveInstanceMethod(i.getCPIndex(buf));		

	if (doSystemRewriting_ &&
		handledInlineSubstituteBytecode(vmi.asSelector(), (S3Blueprint) vmi.getStaticDefinerBlueprint())) 
	    return;
	if (verboten(vmi))return;
//if (this.parSpecializeInstruction())System.out.println("IV# " + vmi);	
	parifyCall(vmi,i);
	} catch (LinkageException e) {IRewriter.warn(i, e.getMessage()); }
    }	
    public void visit(Instruction.INVOKESTATIC i) {
	if (!this.parSpecializeInstruction()) return;
	try { ConstantResolvedStaticMethodref smi = getConstants().resolveStaticMethod(i.getCPIndex(buf));
	if (doSystemRewriting_ && isKernelCall(smi.asSelector(),i)) return;
	if (verboten(smi))return;
//if (this.parSpecializeInstruction())System.out.println("IT# " + smi);

	
	parifyCall(smi,i);
	} catch (LinkageException e) { IRewriter.warn(i, e.getMessage()); }
    }	
    public void visit(Instruction.INVOKESPECIAL i) {
	if (!this.parSpecializeInstruction()) return;
	try { ConstantResolvedInstanceMethodref smi = getConstants().resolveInstanceMethod(i.getCPIndex(buf));
	if (verboten(smi))return;
//if (this.parSpecializeInstruction())System.out.println("IS# " + smi);

parifyCall(smi,i); 
	} catch (LinkageException e) { IRewriter.warn(i, e.getMessage());  }
    }	
    
  
    
    private boolean verboten(ConstantResolvedInstanceMethodref smi) {
	if (smi.asSelector().getDefiningClass().equals(JavaNames.java_lang_Throwable)||
		smi.asSelector().getDefiningClass().equals(JavaNames.java_lang_String)||
		smi.asSelector().getDefiningClass().equals(JavaNames.java_lang_Class)	||
		smi.asSelector().getDefiningClass().equals(javax_realtime_MemoryArea)	||
		smi.asSelector().getDefiningClass().equals(JavaNames.java_lang_StringBuffer)	)
	return true;
	else return false;
    }
    private boolean verboten(ConstantResolvedStaticMethodref smi) {
	if (smi.asSelector().getDefiningClass().equals(JavaNames.java_lang_Throwable)||
		smi.asSelector().getDefiningClass().equals(JavaNames.java_lang_String)||
		smi.asSelector().getDefiningClass().equals(JavaNames.java_lang_Class)	||
		smi.asSelector().getDefiningClass().equals(javax_realtime_MemoryArea)	||
		smi.asSelector().getDefiningClass().equals(JavaNames.java_lang_StringBuffer)	)
	return true;
	else return false;
    }
    private boolean verboten(ConstantResolvedInterfaceMethodref smi) {
	if (smi.asSelector().getDefiningClass().equals(JavaNames.java_lang_Throwable)||
		smi.asSelector().getDefiningClass().equals(JavaNames.java_lang_String)||
		smi.asSelector().getDefiningClass().equals(JavaNames.java_lang_Class)	||
		smi.asSelector().getDefiningClass().equals(javax_realtime_MemoryArea)	||
		smi.asSelector().getDefiningClass().equals(JavaNames.java_lang_StringBuffer)	)
	return true;
	else return false;
    }
    static final String JR = "javax/realtime";
    static final int java_realtime = RepositoryUtils.asUTF(JR);       
    public static final Scalar javax_realtime_MemoryArea = Scalar.make(java_realtime, RepositoryUtils.asUTF("MemoryArea"));
 
    public void visit(Instruction.INVOKEINTERFACE i) {
	if (!this.parSpecializeInstruction()) return; 
	try { ConstantResolvedInterfaceMethodref imi = getConstants().resolveInterfaceMethod(i.getCPIndex(buf));
	if (verboten(imi))return;
//if (this.parSpecializeInstruction())System.out.println("IFC# " + imi);
	parifyCall(imi,i); 
	} catch (LinkageException e) { IRewriter.warn(i, e.getMessage()); }
    }
    

    private boolean isKernelCall(Selector.Method sel, Instruction.Invocation i) {
	return sel.getDefiningClass().getShortNameIndex()  == RepositoryUtils.asUTF("LibraryImports");
    }	
    private boolean handledInlineSubstituteBytecode(Selector.Method sel, Blueprint bp) {	    
	try { return  PragmaTransformCallsiteIR.descendantDeclaredBy(sel, bp) != null;
	} catch (PragmaException.UnregisteredPragmaException upe) { return false; }
    }	
} 
