import "logging.eol";
import "common.eol";
import "helper.eol";
import "debug.eol";
import "reusable.eol";

//TODO: specialise for mesasge and procedure
operation TDL!Interaction executeSource() : TTCN!FunctionStatementList {
    var ssl = new TTCN!FunctionStatementList();
    
    var sfs = new TTCN!FunctionStatement();
    sfs.communication = new TTCN!CommunicationStatements();
    sfs.communication.send = new TTCN!SendStatement();
    //sfs.communication.send.port = self.sourceGate.gate.equivalent();
    sfs.communication.send.port = self.sourceGate.equivalent().port;
    sfs.communication.send.send = new TTCN!PortSendOp();
    sfs.communication.send.send.template = new TTCN!InLineTemplate();
    self.argument.~rule = self.~rule;
    sfs.communication.send.send.template.template = self.argument.getTemplateBody();
    
    //TODO: a bit of an escape hack
    sfs.communication.send.send.template.type = sfs.communication.send.send.template.template.~type;
    
    ssl.statements.add(sfs);
    ssl.sc.add(";");
    return ssl;
}

operation TDL!Interaction executeTarget(t : TDL!Target) : TTCN!FunctionStatementList {
    var tsl = new TTCN!FunctionStatementList();
    
    var tfs = new TTCN!FunctionStatement();
    tfs.communication = new TTCN!CommunicationStatements();
    tfs.communication.receive = new TTCN!ReceiveStatement();
    tfs.communication.receive.any = new TTCN!PortOrAny();
    //tfs.communication.receive.any.ref = t.targetGate.gate.equivalent();
    tfs.communication.receive.any.ref = t.targetGate.equivalent().port;
    tfs.communication.receive.receive = new TTCN!PortReceiveOp();
    tfs.communication.receive.receive.template = new TTCN!InLineTemplate();
    tfs.communication.receive.receive.template.template = self.argument.getTemplateBody();

    //TODO: a bit of an escape hack
    tfs.communication.receive.receive.template.type = tfs.communication.receive.receive.template.template.~type;
    
    if (not t.valueAssignment.isEmpty()) {
        var pr = new TTCN!PortRedirect();
        tfs.communication.receive.receive.redirect = pr;
        pr.value = new TTCN!ValueSpec();
        pr.value.variable = new TTCN!VariableRef();
        pr.value.variable.ref = new TTCN!ReferencedValue();
        pr.value.variable.ref.head = new TTCN!Head();
        pr.value.variable.ref.head.target = t.valueAssignment.get(0).variable.equivalent();
    }
    
    tsl.statements.add(tfs);
    tsl.sc.add(";");
    return tsl;
}

operation TDL!Interaction execute() {
    //TODO: needs adapting to context
    //      add a statement block stack for each component during transformation?

    //TODO: simplify
    
    //source behaviour
    if ((simulateSUT or self.sourceGate.component.role = TDL!ComponentInstanceRole#Tester)
        and (currentScope.isUndefined() or currentScope = self.sourceGate.component)
    ) {
        var sc = self.getMainContext(self.sourceGate.component);
        var sb = stack.get(sc).last();
        var ssl = self.executeSource();
        sb.stat.add(ssl);
    }

    //TODO: handle multicast?
    //target behaviour
    if ((simulateSUT or self.target.first().targetGate.component.role = TDL!ComponentInstanceRole#Tester)
        and (currentScope.isUndefined() or currentScope = self.target.first().targetGate.component)
    ) {
        var tc = self.getMainContext(self.target.first().targetGate.component);
        var tb = stack.get(tc).last();
        var t = self.target.first();
        var tsl = self.executeTarget(t);
        tb.stat.add(tsl);
    }
    //inlined above
    //self.execute(sb, tb);
}

//TODO: remove? (inlined above)
operation TDL!Interaction execute(sourceContext : TTCN!StatementBlock, targetContext : TTCN!StatementBlock) {
    //source behaviour
    var ssl = self.executeSource();
    sourceContext.stat.add(ssl);
    //TODO: handle multicast?
    //target behaviour
    var t = self.target.first();
    var tsl = self.executeTarget(t);
    targetContext.stat.add(tsl);
}

operation TDL!AlternativeBehaviour execute() {
    if (not transformAlt) return;
    var td = self.getTestDescription();
    //TODO: naive simplified assumption
    var fb = self.block.first().behaviour.first();
    if (fb.isKindOf(TDL!Interaction)) {
        if (simulateSUT or fb.sourceGate.component.role = TDL!ComponentInstanceRole#Tester) {
            var sc = self.getMainContext(fb.sourceGate.component);
            var sb = stack.get(sc).last();
            self.executeSource(sb);
        }

        if (simulateSUT or fb.target.first().targetGate.component.role = TDL!ComponentInstanceRole#Tester) {
            var tc = self.getMainContext(fb.target.first().targetGate.component);
            var tb = stack.get(tc).last();
            self.executeTarget(tb);
        }
    }
    
}

operation TDL!AlternativeBehaviour executeTarget(targetContext : TTCN!StatementBlock) {
    var tsl = self.executeTarget();
    targetContext.stat.add(tsl);
}

operation TDL!AlternativeBehaviour executeSource(sourceContext : TTCN!StatementBlock) {
    //TODO: simple assumption - trigger first
    //TODO: needs to follow path, right now triggers first interactions in other branches as well
    var fb = self.block.first().behaviour.first();
    if (fb.isKindOf(TDL!Interaction)) {
        var ssl = fb.executeSource();
        sourceContext.stat.add(ssl);
    }
}

operation TDL!AlternativeBehaviour checkPath() : Boolean {
    var first = true;
    if (self.eContainer.isDefined()) {
        if (self.eContainer.isKindOf(TDL!Block)) {
            if (self.eContainer.eContainer.isKindOf(TDL!AlternativeBehaviour)) {
                if (self.eContainer.eContainer.block.indexOf(self.eContainer) <> 0) {
                    first = false;
                } else {
                    first = self.eContainer.eContainer.checkPath();
                }
            }
        }
    } 
    
    return first;
}


operation TDL!AlternativeBehaviour executeTarget() : TTCN!FunctionStatementList {
    if (not transformAlt) return;
    //TODO
    
    //target only so far..
    //where should this be placed? in the receiving side?
    //how about source
    //TODO: add explicit scoping
    
    var tsl = new TTCN!FunctionStatementList();
    
    var fs = new TTCN!FunctionStatement();
    fs.behavior = new TTCN!BehaviourStatements();
    fs.behavior.alt = new TTCN!AltConstruct();
    fs.behavior.alt.agList = new TTCN!AltGuardList();
    
    for (block in self.block) {
        var gs = block.executeAltBlock();
        fs.behavior.alt.agList.guardList.add(gs);
    }
    
    tsl.statements.add(fs);
    //tsl.sc.add(";");
    return tsl;
}


operation TDL!Block executeAltBlock() : TTCN!GuardStatement {
    var gs = new TTCN!GuardStatement();
    gs.`guard` = new TTCN!AltGuardChar();
    //TODO: expression

    gs.block = new TTCN!StatementBlock();
    
    //naive assumption
    if (self.behaviour.first().isKindOf(TDL!Interaction)) {
	    var cf = self.behaviour.first().getMainContext(self.behaviour.first().target.first().targetGate.component);
	    stack.get(cf).add(gs.block);
	    
	    var i = 0;
	    for (b in self.behaviour) {
	        if (i==0) {
	            //this should be redundant now
	            if (b.isKindOf(TDL!Interaction)) {
	                gs.op = b.getGuardOp();
	            } else {
	                //TODO: handle other behaviours?    
	            }
	        } else {
	            b.execute();
	        }
	        i = i+1;
	    }
	
	    stack.get(cf).remove(gs.block);
    } else if (self.behaviour.first().isKindOf(TDL!TimeOut)) {
        var cf = self.behaviour.first().getMainContext(self.behaviour.first().componentInstance);
        stack.get(cf).add(gs.block);
        
        var i = 0;
        for (b in self.behaviour) {
            if (i==0) {
                //this should be redundant now
                if (b.isKindOf(TDL!TimeOut)) {
                    gs.op = b.getGuardOp();
                } else {
                    //TODO: handle other behaviours?    
                }
            } else {
                b.execute();
            }
            i = i+1;
        }
    
        stack.get(cf).remove(gs.block);
    
    } else {
        //TODO
    }
    
    //TODO: statement block
    return gs;
}


operation TDL!BoundedLoopBehaviour execute() {
    //TODO: no component?
    for (le in self.numIteration) {
        currentScope = le.componentInstance;
        var sc = self.getMainContext(le.componentInstance);
        var sb = stack.get(sc).last();
    
	    var tsl = new TTCN!FunctionStatementList();
	    sb.stat.add(tsl);
	    
	    var fs = new TTCN!FunctionStatement();
	    fs.basic = new TTCN!BasicStatements();
	    fs.basic.loop = new TTCN!LoopConstruct();
	    fs.basic.loop.forStm = new TTCN!ForStatement();
	
	    tsl.statements.add(fs);
	
	    var init = new TTCN!Initial();
	    fs.basic.loop.forStm.init = init;
	
	    var cv = new TTCN!SingleVarInstance();
	
	    var exp = new TTCN!RelExpression();
	    fs.basic.loop.forStm.expression = exp;
	    
	    //init
	    if (le.expression.isKindOf(TDL!DataInstanceUse)) {
	    
	        cv.name = "cv";
	        cv.ac = ":=";
	        cv.expr = new TTCN!Value();
	        cv.expr.predef = new TTCN!PredefinedValue();
	        cv.expr.predef.integer = "0";
	        
		    init.variable = new TTCN!VarInstance();
	        //TODO: add more comprehensive resolution
		    init.variable.listType = le.expression.dataInstance.dataType.getTTCNType();
		    init.variable.list = new TTCN!VarList();
		    init.variable.list.variables.add(cv);
	    
	        //TODO: the TTCN3 grammar needs to save the operators 
	    
	        //control
	        exp.left = new TTCN!Value();
	        exp.left.ref = new TTCN!ReferencedValue();
	        exp.left.ref.head = new TTCN!Head();
	        exp.left.ref.head.target = cv;
	        
	        exp.right = new TTCN!Value();
	        exp.right.ref = new TTCN!ReferencedValue();
	        exp.right.ref.head = new TTCN!Head();
	        if (le.expression.dataInstance.isValue()) {
	            exp.right.ref.head.target = le.expression.dataInstance.equivalent().defs.list.first();
	        } else {
	            //can it even be template?
		        exp.right.ref.head.target = le.expression.dataInstance.equivalent().base;
	        }
	
	    } else {
	        //TODO:
	    }
	
	    //increment    
	    var assign = new TTCN!Assignment();
	    assign.ref = new TTCN!VariableRef();
	    assign.ref.ref = new TTCN!ReferencedValue();
	    assign.ref.ref.head = new TTCN!Head();
	    assign.ref.ref.head.target = cv;
	    
	    assign.expression = new TTCN!AddExpression();
	    assign.expression.left = new TTCN!Value();
	    assign.expression.left.ref = new TTCN!ReferencedValue();
	    assign.expression.left.ref.head = new TTCN!Head();
	    assign.expression.left.ref.head.target = cv;
	
	    assign.expression.right = new TTCN!Value();
	    assign.expression.right.predef = new TTCN!PredefinedValue();
	    assign.expression.right.predef.integer = "1";
	    
	    fs.basic.loop.forStm.assign = assign;
	    
	    var block = new TTCN!StatementBlock();
	        
	    fs.basic.loop.forStm.statement = block;
	        
	    var cf = self.getMainContext(le.componentInstance);
	    stack.get(cf).add(block);
	    
	    for (b in self.block.behaviour) {
	        b.execute();
	    }
	    
	    stack.get(cf).remove(block);
	    currentScope = null;
    }
    
}

operation TDL!UnboundedLoopBehaviour execute() {
    //TODO
    for (b in self.block.behaviour) {
        b.execute();
    }
}


operation TDL!ConditionalBehaviour execute() {
    var guards = self.block.get(0).`guard`;

    for (le in guards) {
        currentScope = le.componentInstance;
        var sc = self.getMainContext(le.componentInstance);
        var sb = stack.get(sc).last();
    
        var tsl = new TTCN!FunctionStatementList();
        sb.stat.add(tsl);
        
        var sfs = new TTCN!FunctionStatement();
        sfs.basic = new TTCN!BasicStatements();
        sfs.basic.conditional = new TTCN!ConditionalConstruct();
        
        //condition
        sfs.basic.conditional.expression = le.expression.getDataUseValue();
    
        tsl.statements.add(sfs);
    
        //main block    
        var block = new TTCN!StatementBlock();
        sfs.basic.conditional.statement = block;
            
        var cf = self.getMainContext(le.componentInstance);
        stack.get(cf).add(block);
        
        for (b in self.block.get(0).behaviour) {
            b.execute();
        }
        
        stack.get(cf).remove(block);

        //TODO: else ifs
        //else block
        if (self.block.size() > 1) {
	        sfs.basic.conditional.`else` = new TTCN!ElseClause();
	        
	        var eBlock = new TTCN!StatementBlock();
	        sfs.basic.conditional.`else`.statement = eBlock;
	            
	        stack.get(cf).add(eBlock);
            for (b in self.block.last().behaviour) {
                b.execute();
            }
            stack.get(cf).remove(eBlock);
        }

        currentScope = null;
    }
}


operation TDL!tdl::Assignment execute() {
    var ci = self.variable.componentInstance;
    if (simulateSUT or ci.role = TDL!ComponentInstanceRole#Tester) {
        var sc = self.getMainContext(ci);
        var sb = stack.get(sc).last();

        var ssl = new TTCN!FunctionStatementList();
        
        var sfs = new TTCN!FunctionStatement();
        
        sfs.basic = new TTCN!BasicStatements();
        sfs.basic.assign = new TTCN!Assignment();
        sfs.basic.assign.ref = new TTCN!VariableRef();
        sfs.basic.assign.ref.ref = new TTCN!ReferencedValue();
        sfs.basic.assign.ref.ref.head = new TTCN!Head();
        sfs.basic.assign.ref.ref.head.target = self.variable.variable.equivalent();
        
        sfs.basic.assign.expression = self.expression.getDataUseValue();
        
        ssl.statements.add(sfs);
        ssl.sc.add(";");

        sb.stat.add(ssl);
    }
}

operation TDL!Assertion execute() {
    var ci = self.componentInstance;
    if (simulateSUT or ci.role = TDL!ComponentInstanceRole#Tester) {
        var sc = self.getMainContext(ci);
        var sb = stack.get(sc).last();
        var ssl = new TTCN!FunctionStatementList();
        
        var sfs = new TTCN!FunctionStatement();
        sfs.basic = new TTCN!BasicStatements();
        sfs.basic.conditional = new TTCN!ConditionalConstruct();
        
        //condition
        sfs.basic.conditional.expression = self.condition.getDataUseValue();

        //statements - pass
        sfs.basic.conditional.statement = new TTCN!StatementBlock();
        var psl = new TTCN!FunctionStatementList();
        psl.statements.add("pass".getVerdictStatement());
        sfs.basic.conditional.statement.stat.add(psl);
        
        //else - otherwise verdict
        sfs.basic.conditional.`else` = new TTCN!ElseClause();
        sfs.basic.conditional.`else`.statement = new TTCN!StatementBlock();
        var osl = new TTCN!FunctionStatementList();
        if (self.otherwise.isDefined()) {
	        osl.statements.add(self.otherwise.getVerdictStatement());
        } else {
            osl.statements.add("fail".getVerdictStatement());
        } 
        sfs.basic.conditional.`else`.statement.stat.add(osl); 
        
        ssl.statements.add(sfs);
        ssl.sc.add(";");

        sb.stat.add(ssl);
    }
}

operation TDL!ActionReference execute() {
    //TODO: skip actions without componentInstance assigned (excluded by standard but still)
    var ci = self.componentInstance;
    if (ci.isUndefined()) {
        ("  Skipping ActionReference without ComponentInstance: "+self).log(1);
        return;
    }
    if (simulateSUT or ci.role = TDL!ComponentInstanceRole#Tester) {
        var sc = self.getMainContext(ci);
        var sb = stack.get(sc).last();
        var ssl = new TTCN!FunctionStatementList();
        
        var sfs = new TTCN!FunctionStatement();
        
        sfs.behavior = new TTCN!BehaviourStatements();
        sfs.behavior.`function` = new TTCN!FunctionInstance();
        sfs.behavior.`function`.ref = self.action.equivalent();

        if (not self.actualParameter.isEmpty()) {
            var pl = new TTCN!FunctionActualParList();
            sfs.behavior.`function`.params = pl;  
        
            for (ap in self.actualParameter) {
                var p = new TTCN!FunctionActualPar();
                p.template = new TTCN!InLineTemplate();
                p.template.template = new TTCN!TemplateBody();
                p.template.template.simple = new TTCN!SimpleSpec();
                p.template.template.simple.expr = ap.dataUse.getDataUseValue();
                pl.params.add(p); 
            }
        }        
        
        ssl.statements.add(sfs);
        ssl.sc.add(";");

        sb.stat.add(ssl);
    }
}

operation TDL!TestDescriptionReference execute() {
    //TODO: support parameters (also in initialisation in TD2FD)
    //TODO: support bindings
    for (ci in self.getTestDescription().testConfiguration.componentInstance.select(c|simulateSUT or c.role = TDL!ComponentInstanceRole#Tester)) {
        var sc = self.getMainContext(ci);
        var sb = stack.get(sc).last();

        var tc = self.testDescription.getMainContext(ci);
        var cf = TTCN!FunctionDef.all().select(f|f.name = tc).first();
        if (cf.isDefined()) {
	        var ssl = new TTCN!FunctionStatementList();
	        var s = new TTCN!FunctionStatement();
	        s.behavior = new TTCN!BehaviourStatements();
	        s.behavior.`function` = new TTCN!FunctionInstance();
	        s.behavior.`function`.ref = cf;
	        ssl.statements.add(s);
	        ssl.sc.add(";");
	        sb.stat.add(ssl);
        } else {
            ("  Could not resolve "+tc).log(1);
            //TODO
        }
    }    
}


operation TDL!VerdictAssignment execute() {
    for (ci in self.getTestDescription().testConfiguration.componentInstance.select(c|simulateSUT or c.role = TDL!ComponentInstanceRole#Tester)) {
        var sc = self.getMainContext(ci);
        var sb = stack.get(sc).last();

	    var ssl = new TTCN!FunctionStatementList();
	    
	    var sfs = self.verdict.getVerdictStatement();
        
        ssl.statements.add(sfs);
        ssl.sc.add(";");

        sb.stat.add(ssl);
    }    
}

operation TDL!CompoundBehaviour execute() {
    //TODO
    for (b in self.block.behaviour) {
        b.~rule = self.~rule;
        b.execute();
    }
}


operation TDL!TimerStart execute() {
    var ci = self.componentInstance;
    if (simulateSUT or ci.role = TDL!ComponentInstanceRole#Tester) {
        var sc = self.getMainContext(ci);
        var sb = stack.get(sc).last();
        var ssl = new TTCN!FunctionStatementList();
        
        var sfs = new TTCN!FunctionStatement();
        
        sfs.timer = new TTCN!TimerStatements();
        sfs.timer.start = new TTCN!StartTimerStatement();
        sfs.timer.start.ref = self.timer.equivalent();
        sfs.timer.start.expr = self.period.getDataUseValue();
        
        ssl.statements.add(sfs);
        ssl.sc.add(";");

        sb.stat.add(ssl);
    }
}

operation TDL!TimerStop execute() {
    var ci = self.componentInstance;
    if (simulateSUT or ci.role = TDL!ComponentInstanceRole#Tester) {
        var sc = self.getMainContext(ci);
        var sb = stack.get(sc).last();
        var ssl = new TTCN!FunctionStatementList();
        
        var sfs = new TTCN!FunctionStatement();
        
        sfs.timer = new TTCN!TimerStatements();
        sfs.timer.stop = new TTCN!StopTimerStatement();
        sfs.timer.stop.ref = new TTCN!TimerRefOrAll();
        sfs.timer.stop.ref.ref = self.timer.equivalent();
        
        ssl.statements.add(sfs);
        ssl.sc.add(";");

        sb.stat.add(ssl);
    }
}

operation TDL!TimeOut execute() {
    //TODO: handle in an alt statement?
    var ci = self.componentInstance;
    if (simulateSUT or ci.role = TDL!ComponentInstanceRole#Tester) {
        var sc = self.getMainContext(ci);
        var sb = stack.get(sc).last();
        var ssl = new TTCN!FunctionStatementList();
        
        var sfs = new TTCN!FunctionStatement();
        
        sfs.timer = new TTCN!TimerStatements();
        sfs.timer.timeout = new TTCN!TimeoutStatement();
        sfs.timer.timeout.ref = new TTCN!TimerRefOrAny();
        sfs.timer.timeout.ref.ref = self.timer.equivalent();
        
        ssl.statements.add(sfs);
        ssl.sc.add(";");

        sb.stat.add(ssl);
    }
}

operation TDL!Behaviour execute() {
    ("  Skipping " +self.type.name+" (not yet supported)").println();
    if (self.type.name = "ParallelBehaviour" and false) {
        (indent+"group Parallel").printlno();
        var i = 0;
        for (block in self.block) {
            for (b in block.behaviour) {
            	b.~rule = self.~rule;
                b.execute(indent+"  ");
            }
            if (i <= block.size()) {
                (indent+"else").printlno();
            }
            i = i+1;
        }
        (indent+"end").printlno();
    }
}
