package edu.ucdavis.rj;

import java.util.*;

/**
 * Represents a possibly quantified arm on a co.
 *
 * It has many constructors to allow for optional parts
 * and quantified operations in different forms.
 */
public class CoArm {

    Quantifiers quants;
    OpProxy op;
    Invocation inv;
    ArmCode code;

    OpInni replyOp; // the op to which reply will be sent (generated here)
    // Co and replyOp on same VM, so can use OpInni.

    // either oa or op will be null
    OpArray oa;

    CoKind coKind;

    /**
     * Represents the kind of concurrent invocation.
     */
    public enum CoKind {
	/** synchronous invocation */
	COCALL,
	/** asynchronous invocation */
	COSEND,
    }


    // general, quantified case:
    // only used internally.
    //////////// explain more in comment........ about quant value or null.
    CoArm(Quantifiers quant, OpProxy op, Invocation inv, ArmCode code, CoKind coKind) {
	this.quants = quant;
	this.op = op;

	/////////////// should clone inv...................

	this.inv = inv;
	this.code = code;

	////////////// rewrite so can pass this in
	//////////// so that arrays use just one op.
	this.replyOp = OpInni.newOpInni();

	this.coKind = coKind;
    }

    // single op:  general, unquantified case:
    public CoArm(  OpProxy op, Invocation inv, ArmCode code, CoKind coKind) {
	this(null, op,         inv,            code,         coKind);
    }

    // single op:  general, unquantified case:
    public CoArm(  OpProxy op, Invocation inv, ArmCode code                ) {
	this(      op,         inv,            code,          CoKind.COCALL);
    }

    // single op:  general, unquantified case:
    public CoArm(  OpProxy op, Invocation inv,                CoKind coKind) {
	this(null, op,         inv,            null,          coKind);
    }

    // single op:  general, unquantified case:
    public CoArm(  OpProxy op, Invocation inv                              ) {
	this(      op,         inv,            null,          CoKind.COCALL);
    }

    // single op:  general, unquantified case:
    public CoArm(OpImpl op, Invocation inv, ArmCode code, CoKind coKind) {
	this(    new OpProxy(op),
                            inv,            code,         coKind);
    }

    // single op:  general, unquantified case:
    public CoArm(OpImpl op, Invocation inv, ArmCode code               ) {
	this(new OpProxy(op),
                            inv,            code,         CoKind.COCALL);
    }

    // single op:  general, unquantified case:
    public CoArm(OpImpl op, Invocation inv,               CoKind coKind) {
	this(    new OpProxy(op),
                            inv,            null,         coKind);
    }
    // single op:  general, unquantified case:
    public CoArm(OpImpl op, Invocation inv                             ) {
	this(    new OpProxy(op),
                            inv,            null,         CoKind.COCALL);
    }

    // array of ops:  general, quantified case:
    public CoArm(OpArray oa, ArmCode code, CoKind coKind) {
	this.oa = oa;
	this.code = code;
	this.coKind = coKind;
	commonArrayCheck();
    }

    // array of ops:  general, quantified case:
    public CoArm(OpArray oa, ArmCode code               ) {
	this(    oa,         code,         CoKind.COCALL);
    }

    // array of ops:  general, quantified case:
    public CoArm(OpArray oa,               CoKind coKind) {
	this(    oa,         null,         coKind       );
    }

    // array of ops:  general, quantified case:
    public CoArm(OpArray oa                             ) {
	this(    oa,         null,         CoKind.COCALL);
    }

    //// for VM creation
    //// wrappers that convert to CoArms that use ops.

    // single "new VM":  general, unquantified case:
    public CoArm(ArmCode code, CoKind coKind) {
	/////////	this(VM.currentVM().toString(),            code,         coKind);
	this(rjvm.thisVM.host,            code,         coKind);
    }
    // single "new VM":  general, unquantified case:
    public CoArm(String host, ArmCode code, CoKind coKind) {
	///////////Invocation inv = new Invocation(host);
	/////////////OpProxy op = Create.newVM;
	this(null, Create.newVM,         new Invocation(host),            code,         coKind);
    }

    // "new VM":  general, quantified case:
    // but default "on"
    public CoArm(Quantifiers [] quantsArray,
		 ArmCode code, CoKind coKind) {
	// convert to CoArm w/ invocation of newVM.
	this(new CoArm.OpArray(quantsArray,
			       makeArrayTheNewVM(quantsArray.length, Create.newVM),
			       makeVMInvocations(quantsArray.length,
						 rjvm.thisVM.host)),
	     code,         coKind);
    }
    // "new VM":  general, quantified case:
    // (for now, only provide Array version; later can add ArrayList verison)
    // (later: could also make these more consistent w/ how did op Arrays.)
    // (later: and check that hsots.length == quantsArray.length)
    public CoArm(Quantifiers [] quantsArray,
		 String [] hosts, ArmCode code, CoKind coKind) {
	// convert to CoArm w/ invocation of newVM.
	this(new CoArm.OpArray(quantsArray,
			       makeArrayTheNewVM(quantsArray.length, Create.newVM),
			       makeVMInvocations(hosts)),
	     code,         coKind);
    }
    private static OpProxy [] makeArrayTheNewVM(int len, OpProxy t) {
	OpProxy [] ret = new OpProxy[ len ];
	for (int k = 0; k < len; k++) {
	    ret[k] = t;
	}
	return ret;
    }
    private static Invocation [] makeVMInvocations(int len, String host) {
	Invocation [] ret = new Invocation[ len ];
	for (int k = 0; k < len; k++) {
	    ret[k] = new Invocation(host);
	}
	return ret;
    }
    private static Invocation [] makeVMInvocations(String [] hosts) {
	Invocation [] ret = new Invocation[ hosts.length ];
	for (int k = 0; k < hosts.length; k++) {
	    ret[k] = new Invocation(hosts[k]);
	}
	return ret;
    }


    // single "new remote":  general, unquantified case:
    public CoArm(ArmCode code, String cls, Invocation inv, CoKind coKind) {
	this(VM.currentVM(), code, cls, inv,         coKind);
    }
    // single "new remote":  general, unquantified case:
    public CoArm(VM v, ArmCode code, String cls, Invocation inv, CoKind coKind) {
	this(null,  Create.newRO, new Invocation(v, cls, inv), code,         coKind);
    }
    // single "new remote":  general, quantified case:
    // but no "on"
    public CoArm(Quantifiers [] quantsArray,
		 ArmCode code,
		 String cls, Invocation [] invs, CoKind coKind) {
	this(new CoArm.OpArray(quantsArray,
			       makeArrayTheNewVM(quantsArray.length, Create.newRO),
			       makeRemoteInvocations(cls, invs)),
	     code,         coKind);
    }
    // single "new remote":  general, quantified case:
    public CoArm(Quantifiers [] quantsArray,
		 VM [] vA, ArmCode code,
		 String cls, Invocation [] invs, CoKind coKind) {
	this(new CoArm.OpArray(quantsArray,
			       makeArrayTheNewVM(quantsArray.length, Create.newRO),
			       makeRemoteInvocations(vA, cls, invs)),
	     code,         coKind);
    }
    private static Invocation [] makeRemoteInvocations(
						       String cls,
						       Invocation [] invs) {
	Invocation [] ret = new Invocation[ invs.length ];
	for (int k = 0; k < invs.length; k++) {
	    ret[k] = new Invocation(VM.currentVM(), cls, invs[k]);
	}
	return ret;
    }
    private static Invocation [] makeRemoteInvocations(
						       VM [] vA,
						       String cls,
						       Invocation [] invs) {
	Invocation [] ret = new Invocation[ vA.length ];
	for (int k = 0; k < vA.length; k++) {
	    ret[k] = new Invocation(vA[k], cls, invs[k]);
	}
	return ret;
    }


    private void commonArrayCheck() {
	// OpArray constructer already checked that sizes equal.
	oa.checkQuantifiers();
	oa.checkOps();
	for (Invocation inv: oa.invsList) {
	    if (inv == null) {
		throw new NullPointerException("Invocation given to CoArm is null");
	    }
	}
    }

    OpProxy getOp() {
	return op;
    }

    Invocation getInvocation() {
	return inv;
    }

    ArmCode getCode() {
	return code;
    }

    CoKind getCoKind() {
	return coKind;
    }

    /**
     * An array of operations for this CoArm
     * and (typically) the associated quantifier values.
     */
    public static class OpArray extends InniArm.OpArray {

	ArrayList<Invocation> invsList;

	{
	    classNameForErrors = "CoArm";
	}
	
	public OpArray(Quantifiers [] quantsArray,
		       OpProxy [] ops,
		       Invocation [] invs) {
	    super(quantsArray, ops);
	    this.invsList = new ArrayList<Invocation>(Arrays.asList(invs));
	    commonCheck();
	}
	public OpArray(ArrayList<Quantifiers> quantsList,
		       ArrayList<OpProxy> opsList,
		       ArrayList<Invocation> invs) {
	    super(quantsList, opsList);
	    this.invsList = (ArrayList<Invocation>)(invsList.clone());
	    commonCheck();
	}

	// second group is above repeated but for OpImpl
	public OpArray(Quantifiers [] quantsArray,
		       OpImpl [] ops,
		       Invocation [] invs) {
	    super(quantsArray, ops);
	    this.invsList = new ArrayList<Invocation>(Arrays.asList(invs));
	    commonCheck();
	}
	public OpArray(ArrayList<Quantifiers> quantsList,
		       ArrayList<OpImpl> opsList,
		       ArrayList<Invocation> invsList,
		       Dummy ... dummy) {
	    super(quantsList, opsList);
	    this.invsList = (ArrayList<Invocation>)(invsList.clone());
	    commonCheck();
	}
	// no quantifier: "implied" quantifier of entire array
	public OpArray(OpProxy [] ops, Invocation [] invs) {
	    this(null, ops, invs);
	}

	// no quantifier: "implied" quantifier of entire array
	public OpArray(ArrayList<OpProxy> opsList, 
		       ArrayList<Invocation> invs) {
	    this(null, opsList, invs);
	}

	// no quantifier: "implied" quantifier of entire array
	public OpArray(OpImpl [] ops, Invocation [] invs) {
	    this(null, ops, invs);
	}

	// no quantifier: "implied" quantifier of entire array
	public OpArray(ArrayList<OpImpl> opsList, 
		       ArrayList<Invocation> invs,
		       Dummy ... dummy) {
	    this(null, opsList, invs);
	}

	// used to workaround Java's "same type erasure"
	// for constructors for OpImpl and similars one for OpProxy.
	// protected so that user can't actually use it.
	// (so constructor invocation has 0 Dummy, as we want.)
	// Java 1.6 allowed:
	//  protected class Dummy {}
	// Java 1.7 requires:
	public class Dummy {}

	/////
	// checks needed by all constructors:
	//   same number of elements in opsList as in invsList
	//
	// further semantic checks (elements not null)
	// left for later (CoArm)
	private void commonCheck() {
	    if (opsList.size() != invsList.size()) {
		String msg = "in constructing OpArray, "
		    +"size of op list ("
		    +opsList.size() +") differs from size of invocation list ("
		    +invsList.size()+")";
		throw new rjRuntimeError(msg);
	    }
	}

	ArrayList<Invocation> getInvocationsList() {
	    return invsList;
	}
    }

}
