/******************************************************************************
* Copyright (c) 2000-2021 Ericsson Telecom AB
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
*
* Contributors:
*   Balasko, Jeno
*   Baranyi, Botond
*   Forstner, Matyas
*   Raduly, Csaba
*   Szabados, Kristof
*   Szabo, Janos Zoltan – initial implementation
*   Tatarka, Gabor
*
******************************************************************************/
module ProcPort {

type record MyRecord { }

type record MyRecord2 {
  integer num,
  universal charstring str
}
with {
  encode "XML";
  variant (num) "attribute";
  variant (str) "untagged";
}

type record MyRecord3 {
  record of MyRecord2 elems
}

type record MyRecord4 {
  charstring attr,
  octetstring val
}
with {
  encode "XML";
  variant "name as 'MyRecord2'";
  variant (attr) "attribute";
  variant (attr) "name as 'num'";
  variant (val) "untagged";
}

type octetstring MyOctetstring with { encode "RAW"; }

signature MyProc(in integer Par1,inout charstring Par2,out float Par3)
return boolean
exception(integer);
signature MyProc2();
type MyProc2 MyProc2Alias; // testing type alias
signature MyProc3(in integer I) return float exception(integer);
signature MyProc4(in float F) noblock;
signature MyProc5(in integer A, in integer B)
return float exception(charstring, MyRecord);
signature s_StopPTC();
signature MyProc6(inout integer I);
signature MyProc7(inout universal charstring x) return MyRecord2 exception(MyRecord2, MyRecord3);

type integer MyInt with { extension "done" }; 

template s_StopPTC StopPTC := { }

template MyProc5 MyProc5Template_any := {
  A:=?,
  B:=?
}

template MyProc5 MyProc5TemplateP(integer pA,integer pB) := {
  A:=pA,
  B:=pB
}

template MyProc3 MyProc3Template:={
  I:=2003
}

template MyProc3 MyProc3Template_any:={I:=?}

template MyProc3 MyProc3TemplateP (integer P) := {
  I:=P
}

template MyProc MyProcTemplateP (integer i,charstring c,float f) := {
  Par1:=i,
  Par2:=c,
  Par3:=f
}

template MyProc MyProcTemplate:= {
  Par1:=44,
  Par2:="Q",
  Par3:=324.664
}

template MyProc MyProcTemplate2:= {
  Par1:=4200,
  Par2:="T",
  Par3:=170.01
}

template MyProc MyProcTemplate_any:= {
  Par1:=?,
  Par2:=?,
  Par3:=?
}

template MyProc2 MyProc2Template := { }

type record address {
  integer a1,
  integer a2
}

template address MyAddress(integer p1,integer p2) := {
  a1:=p1,
  a2:=p2
}

template MyProc7 MyProc7TemplatePard(template universal charstring p) := {
  x := p
}

type port PortAddress procedure {
  inout MyProc,s_StopPTC;
  in MyProc2;
} with { extension "address" }

type port ProcPort1 procedure
{
  inout MyProc,s_StopPTC;
  in MyProc2;
  out MyProc3;
} with {extension "internal"}

type port ProcPort2 procedure
{
  inout MyProc,s_StopPTC;
  out MyProc2;
  in MyProc3;
} with {extension "internal"}

type port ExtProcPort procedure
{
  inout MyProc5;
}

type port CompileOnlyPort procedure
{
  in MyProc;
  inout MyProc2;
  out MyProc3;
  in MyProc4;
  inout MyProc5;
}

type port CompileOnlyPortAddress procedure {
  out MyProc;
  in MyProc2;
  inout MyProc3;
  out MyProc4;
  in MyProc5;
} with { extension "address" }

type port ProcPort3 procedure {
  inout MyProc6;
} with { extension "internal" }

type port ProcPort4 procedure {
  inout MyProc7;
}
with { extension "internal" }

type port ProcPort5 procedure {
  inout MyProc;
}
with { extension "internal" }

type component ProcComponent
{
  port ProcPort1 Port0;
}

type component ProcComponent2
{
  port ProcPort2 Port1;
}

type component ProcComponentMultiPort
{
  port ProcPort1 Port0;
  port ProcPort2 Port1[4];
  port ExtProcPort Port2;
  port CompileOnlyPort Port3;
  port PortAddress Port4;
}

type component eComponent
{
  port ExtProcPort ePort;
}

type component addressComponent
{
  port PortAddress P;
}

type component ProcComponent3 {
  port ProcPort3 pt;
}

type component ProcComponent4 {
  port ProcPort4 Port4;
}

type component ProcComponent5 {
  port ProcPort5 Port5;
}

function GetCall_behav1() runs on ProcComponent2 {
  while(true) {
    alt {
      []Port1.getcall(MyProcTemplate_any) {
        Port1.reply(MyProcTemplate2 value true);
      }
      []Port1.getcall(MyProc3Template_any) {
        Port1.reply(MyProc3Template value 3.1415);
      }
      []Port1.getcall(StopPTC) {
        Port1.reply(StopPTC);
        stop;
      }
    }
  }
}

function GetCall_behav2() runs on ProcComponent {
  while(true) {
    alt {
      []Port0.getcall(MyProcTemplate2) {
        Port0.raise(MyProc,integer:555);
      }
      []Port0.getcall(MyProcTemplate_any) {
        Port0.reply(MyProcTemplate value false);
      }
      []Port0.getcall(MyProc2Template) {
        Port0.reply(MyProc2Template);
      }
      []Port0.getcall(StopPTC) {
        Port0.reply(StopPTC);
        stop;
      }
    }
  }
}

function GetCall_behav3() runs on ProcComponent2 {
  while(true) {
    alt {
      []Port1.getcall(MyProcTemplate2) {
        Port1.raise(MyProc,integer:272869);
      }
      []Port1.getcall(MyProcTemplate_any) {
        Port1.reply(MyProcTemplateP(22,"G",199.99) value false);
      }
      []Port1.getcall(MyProc3Template_any) {
        Port1.reply(MyProc3Template value 2.78);
      }
      []Port1.getcall(StopPTC) {
        Port1.reply(StopPTC);
        stop;
      }
    }
  }
}

function Check_getcall_behav() runs on ProcComponent2 {
  while(true) {
    alt {
      []Port1.check(getcall(MyProcTemplate2)) {
        Port1.getcall;
        Port1.reply(MyProcTemplate value true);
      }
      []Port1.getcall(StopPTC) {
        Port1.reply(StopPTC);
        stop;
      }
      []Port1.check(getcall) {
        Port1.getcall;
        Port1.reply(MyProcTemplate2 value false);
      }
    }
  }
}

// parameters values
const integer c_CallParam := 10;
const integer c_ReplyParam := 19;

// error codes
template integer t_getCall_invalidValue := -1;
template integer t_getCall_timeout := -2;

function GetCallParameters_behav() runs on ProcComponent3 {
  var integer x := 0;
  timer t := 1.0;
  t.start;
  alt {
    [] pt.getcall(MyProc6:{?}) -> param (x) {
      if (c_CallParam == x) { pt.reply(MyProc6:{c_ReplyParam}); }
      else { pt.reply(MyProc6:{t_getCall_invalidValue}); }
    }
    [] t.timeout { pt.reply(MyProc6:{t_getCall_timeout}); }
  }
}

function Done_behav(in MyInt p) runs on ProcComponent return MyInt {
  return p;
}

testcase tc1_Call() runs on ProcComponent {
  /* Non-blocking calls */
  var ProcComponent2 PC2;
  var integer i:=0;
  var boolean b:=false;
  var float f:=0.0;
  timer T:=1.0;
  PC2:=ProcComponent2.create;
  PC2.start(GetCall_behav1());
  connect(self:Port0,PC2:Port1);
  T.start;
  Port0.call(MyProcTemplate,nowait);
  Port0.call(MyProc3Template,nowait);
  while(i<2) {
    alt {
      []Port0.getreply(MyProcTemplate2) -> value b {
        if(i==0) {
          if(b==true) {i:=1;}
          else {setverdict(fail);stop;}
        } else {
          setverdict(inconc);stop;
        }
      }
      []Port0.getreply(MyProc3Template) -> value f {
        if((i==1)and(f==3.1415)) {i:=2;}
        else {log(f);setverdict(inconc);stop;}
      }
      []T.timeout {
        setverdict(fail);
        stop;
      }
    }
  }
  if(i==2) {setverdict(pass);}
  Port0.call(StopPTC,0.5) {
    []Port0.getreply(StopPTC) { }
    []Port0.catch(timeout) { }
  }
  disconnect(self:Port0,PC2:Port1);
}

testcase tc2_Call() runs on ProcComponent2 {
  /* Blocking calls */
  var ProcComponent PC;
  var boolean b:=true;
  PC:=ProcComponent.create;
  PC.start(GetCall_behav2());
  connect(self:Port1,PC:Port0);
  Port1.call(MyProcTemplate,1.0) {
    []Port1.getreply(MyProcTemplate value ?) -> value b{
      Port1.call(MyProc2Template,0.7) {
        []Port1.getreply(MyProc2Template) {
          Port1.call(MyProcTemplate2,0.4) {
            []Port1.catch(MyProc,integer:555) {
              if(b==false) {setverdict(pass);}
              else {setverdict(inconc);}
            }
            []Port1.catch(timeout) {
              setverdict(fail);
              stop;
            }
          }
        }
        []Port1.catch(timeout) {
          setverdict(fail);
          stop;
        }
      }
    }
    []Port1.catch(timeout) {
      setverdict(fail);
      stop;
    }
  }
  Port1.call(StopPTC,0.5) {
    []Port1.getreply(StopPTC) { }
    []Port1.catch(timeout) { }
  }
}

testcase tc_extCall() runs on eComponent system eComponent {
  map(self:ePort,system:ePort);
  ePort.call(MyProc5TemplateP(128,32),0.5) {
    []ePort.getreply(MyProc5Template_any value 4.0) {
      setverdict(pass);
    }
    []ePort.catch(timeout) {
      setverdict(fail);stop;
    }
  }
  unmap(self:ePort,system:ePort);
}

testcase tc_extCall_2() runs on eComponent system eComponent {
  map(self:ePort,system:ePort);
  ePort.call(MyProc5TemplateP(32,128),0.5) {
    []ePort.getreply(MyProc5Template_any value 0.25) {
      setverdict(pass);
    }
    []ePort.catch(timeout) {
      setverdict(fail);stop;
    }
  }
  unmap(self:ePort,system:ePort);
}

testcase tc_extCall_3() runs on eComponent system eComponent {
  map(self:ePort,system:ePort);
  ePort.call(MyProc5TemplateP(128,0),0.5) {
    []ePort.catch(MyProc5,charstring:"Divide by 0.") {
      setverdict(pass);
    }
    []ePort.catch(timeout) {
      setverdict(fail);stop;
    }
  }
  unmap(self:ePort,system:ePort);
}

testcase tc_Call_MultiPTC() runs on ProcComponent {
  /* procedure based communication with multiple PTCs */
  var ProcComponent2 PC1,PC2;
  var integer i:=0;
  timer T:=1.0;
  PC1:=ProcComponent2.create;
  PC1.start(GetCall_behav1());
  connect(self:Port0,PC1:Port1);
  PC2:=ProcComponent2.create;
  PC2.start(GetCall_behav3());
  connect(self:Port0,PC2:Port1);
  T.start;
  Port0.call(MyProcTemplate,nowait) to PC1;
  Port0.call(MyProc3Template,nowait) to PC2;
  while(i<2) {
    alt {
      []Port0.getreply(MyProcTemplate2 value true) from PC1 {
        i:=i+1;
      }
      []Port0.getreply(MyProc3Template value 2.78) from PC2 {
        i:=i+1;
      }
      []T.timeout {
        setverdict(fail);stop;
      }
    }
  }
  setverdict(pass);
  Port0.call(StopPTC,0.5) to PC1 {
    []Port0.getreply(StopPTC) from PC1 { }
    []Port0.catch(timeout) { setverdict(fail);stop; }
  }
  Port0.call(StopPTC,0.5) to PC2 {
    []Port0.getreply(StopPTC) from PC2 { }
    []Port0.catch(timeout) { setverdict(fail);stop; }
  }
  disconnect(self:Port0,PC1:Port1);
  disconnect(self:Port0,PC2:Port1);
}

testcase tc_Call_MultiPTC_anyport() runs on ProcComponentMultiPort {
  /* testing any-port operations with multiple PTCs */
  var ProcComponent2 PC1,PC2;
  var ProcComponent2 sndr;
  var integer i:=0;
  var boolean chk1:=true,chk2:=true;
  timer T:=1.5;
  PC1:=ProcComponent2.create;
  PC1.start(GetCall_behav1());
  connect(self:Port0,PC1:Port1);
  PC2:=ProcComponent2.create;
  PC2.start(GetCall_behav3());
  connect(self:Port0,PC2:Port1);
  T.start;
  Port0.call(MyProcTemplate,nowait) to PC1;
  Port0.call(MyProc3Template,nowait) to PC2;
  while(i<4) {
    alt {
      [chk1]any port.check {
        i:=i+1;
        chk1:=false;
        log("any port.check OK");
      }
      [chk2]any port.check(getreply -> sender sndr) {
        i:=i+1;
        chk2:=false;
        log("any port.check(getreply) OK, sender: ",sndr);
      }
      []any port.check(catch) {
        log("any port.check(catch) matched --> fail");
        setverdict(fail);stop;
      }
      []any port.getcall {
        log("any port.getcall matched --> fail");
        setverdict(fail);stop;
      }
      []any port.getreply from PC1 {
        log("any port.getreply from PC1 OK");
        i:=i+1;
      }
      []any port.getreply from PC2 {
        log("any port.getreply from PC2 OK");
        i:=i+1;
      }
      []T.timeout {
        setverdict(fail);stop;
      }
    }
  }
  setverdict(pass);
  Port0.call(StopPTC,0.5) to PC1 {
    []Port0.getreply(StopPTC) from PC1 { }
    []Port0.catch(timeout) { setverdict(fail);stop; }
  }
  Port0.call(StopPTC,0.5) to PC2 {
    []Port0.getreply(StopPTC) from PC2 { }
    []Port0.catch(timeout) { setverdict(fail);stop; }
  }
  disconnect(self:Port0,PC1:Port1);
  disconnect(self:Port0,PC2:Port1);
}

testcase tc_Check_1() runs on ProcComponent {
  var ProcComponent2 PC2;
  timer T:=1.5;
  PC2:=ProcComponent2.create;
  PC2.start(GetCall_behav3());
  connect(self:Port0,PC2:Port1);
  Port0.call(MyProcTemplate2,nowait);
  T.start;
  alt {
    []Port0.check(catch) { }
    []T.timeout {
      setverdict(fail);
      stop;
    }
  }
  alt {
    []any port.check(catch) {
      log("any port.check(catch) OK");
    }
    []T.timeout {
      setverdict(fail);
      stop;
    }
  }
  alt {
    []Port0.check(catch(MyProc,integer:272869)) {
      Port0.catch;
    }
    []T.timeout {
      setverdict(fail);
      stop;
    }
  }
  Port0.call(MyProcTemplate,nowait);
  alt {
    []Port0.check(getreply) { }
    []T.timeout {
      setverdict(fail);
      stop;
    }
  }
  alt {
    []Port0.check(getreply(MyProcTemplateP(22,"G",199.99) value false)){
      Port0.getreply;
      setverdict(pass);
    }
    []T.timeout {
      setverdict(fail);
      stop;
    }
  }
  Port0.call(StopPTC,0.5) {
    []Port0.getreply(StopPTC) { }
    []Port0.catch(timeout) { }
  }
}

testcase tc_Check_2() runs on ProcComponent {
  var ProcComponent2 PC2;
  PC2:=ProcComponent2.create;
  PC2.start(Check_getcall_behav());
  connect(self:Port0,PC2:Port1);
  Port0.call(MyProcTemplate2,1.0) {
    []Port0.getreply(MyProcTemplate value true) { }
    []Port0.catch(timeout) {
      setverdict(fail);
      stop;
    }
  }
  Port0.call(MyProcTemplate,1.0) {
    []Port0.getreply(MyProcTemplate2 value false) {
      setverdict(pass);
    }
    []Port0.catch(timeout) {
      setverdict(fail);
      stop;
    }
  }
  Port0.call(StopPTC,0.5) {
    []Port0.getreply(StopPTC) { }
    []Port0.catch(timeout) { }
  }
}

// Address port cannot be connected.
/*testcase tc_PortAddress_internal_usage() runs on ProcComponentMultiPort {
  //procedure based internal communication with address-supporting port
  var ProcComponent2 PC2;
  var integer i:=0;
  PC2:=ProcComponent2.create;
  PC2.start(GetCall_behav3());
  connect(self:Port4,PC2:Port1);
  Port4.call(MyProcTemplateP(11,"T",99.012),1.0) {
    []Port4.getreply(MyProcTemplateP(22,"G",199.99) value false) {
      i:=i+1;
    }
    []Port4.catch(timeout) {
      setverdict(fail);stop;
    }
  }
  Port4.call(MyProcTemplate2,1.0) {
    []Port4.catch(MyProc,integer:272869) {
      i:=i+1;
    }
    []Port4.catch(timeout) {
      setverdict(fail);stop;
    }
  }
  if(i==2){setverdict(pass);}
  Port4.call(StopPTC,1.0) {
    []Port4.getreply(StopPTC) { }
    []Port4.catch(timeout) {
      setverdict(fail);
    }
  }
  setverdict(pass);
}*/

testcase tc_PortAddress_external_usage1() runs on addressComponent
system addressComponent {
  map(self:P,system:P);
  P.call(MyProcTemplate,1.0) to valueof(MyAddress(321,67)) {
    []P.getreply(MyProcTemplate_any value true) from MyAddress(67,321) {
      setverdict(pass);
    }
    []P.catch(timeout) {
      setverdict(fail);
    }
  }
  unmap(self:P,system:P);
}

testcase tc_PortAddress_external_usage2() runs on addressComponent
system addressComponent {
  map(self:P,system:P);
  P.call(MyProcTemplate,1.0) to valueof(MyAddress(321,68)) {
    []P.catch(MyProc,integer:389) from MyAddress(68,321) {
      setverdict(pass);
    }
    []P.catch(timeout) {
      setverdict(fail);
    }
  }
  unmap(self:P,system:P);
}

function signatureEncode(template MyProc par1, template MyProc par2) runs on addressComponent
{
  log(par1, par2);
}

// tests the 'param' directive in functions 'getcall' and 'getreply',
// specificly with signatures containing 'inout' parameters (HT93096)
testcase tc_GetReplyParameters() runs on ProcComponent3 {
  var ProcComponent3 c := ProcComponent3.create;
  connect(c:pt, self:pt);
  c.start(GetCallParameters_behav()); 

  var integer x := 0;
  pt.call(MyProc6:{c_CallParam}, 1.0) {
    [] pt.getreply(MyProc6:{t_getCall_invalidValue}) { setverdict(fail, "invalid getcall parameter"); }
    [] pt.getreply(MyProc6:{t_getCall_timeout}) { setverdict(fail, "getcall timed out"); }
    [] pt.getreply(MyProc6:{?}) -> param (x) {
      if (c_ReplyParam == x) { setverdict(pass); }
      else { setverdict(fail, "invalid getreply parameter"); }
    }
    [] pt.catch(timeout) { setverdict(fail, "getreply timed out"); }
  }

  c.done;
}

// '*' is not allowed as a matching template for the return value
// in getreply operations or for the exception in catch operations
testcase tc_MatchingWithAnyOrOmit() runs on ProcComponent5 {
  connect(self:Port5, self:Port5);
  var template boolean vt_bool := *;
  var template integer vt_int := *;
  
  // getreply
  Port5.reply(MyProc: { -, "a", 0.1 } value false);
  @try {
    alt {
      [] Port5.getreply(MyProc: { ?, ?, ? } value vt_bool) { }
    }
    setverdict(fail, "Getreply operation succeeded. Expected error.");
  }
  @catch (msg) {
    if (match(msg, pattern "*Getreply operation using '\*' as return value matching template")) {
      setverdict(pass);
    }
    else {
      setverdict(fail, "Incorrect error message received (getreply test): ", msg);
    }
  }
  
  // check-getreply
  Port5.reply(MyProc: { -, "a", 0.1 } value false);
  @try {
    alt {
      [] Port5.check(getreply(MyProc: { ?, ?, ? } value vt_bool)) { }
    }
    setverdict(fail, "Check-getreply operation succeeded. Expected error.");
  }
  @catch (msg) {
    if (match(msg, pattern "*Check-getreply operation using '\*' as return value matching template")) {
      setverdict(pass);
    }
    else {
      setverdict(fail, "Incorrect error message received (check-getreply test): ", msg);
    }
  }
  
  // catch
  Port5.raise(MyProc, 10);
  @try {
    alt {
      [] Port5.catch(MyProc, vt_int) { }
    }
    setverdict(fail, "Catch operation succeeded. Expected error.");
  }
  @catch (msg) {
    if (match(msg, pattern "*Catch operation using '\*' as matching template")) {
      setverdict(pass);
    }
    else {
      setverdict(fail, "Incorrect error message received (catch test): ", msg);
    }
  }
  
  // check-catch
  Port5.raise(MyProc, 10);
  @try {
    alt {
      [] Port5.check(catch(MyProc, vt_int)) { }
    }
    setverdict(fail, "Check-catch operation succeeded. Expected error.");
  }
  @catch (msg) {
    if (match(msg, pattern "*Check-catch operation using '\*' as matching template")) {
      setverdict(pass);
    }
    else {
      setverdict(fail, "Incorrect error message received (check-catch test): ", msg);
    }
  }
}

testcase tc_doneWithValueRedirect() runs on ProcComponent {
  var MyInt val := 2;
  var MyInt res;
  var ProcComponent comp := ProcComponent.create;
  comp.start(Done_behav(val));
  comp.done(MyInt: ?) -> value res;
  if (res != val) {
    setverdict(fail, "Invalid return value. Got: ", res, ", expected: ", val);
  }
  setverdict(pass);
}

control {
  execute(tc1_Call());
  execute(tc2_Call());
  execute(tc_extCall());
  execute(tc_extCall_2());
  execute(tc_extCall_3());
  execute(tc_Call_MultiPTC());
  execute(tc_Call_MultiPTC_anyport());
  execute(tc_Check_1());
  execute(tc_Check_2());
  //execute(tc_PortAddress_internal_usage());
  execute(tc_PortAddress_external_usage1());
  execute(tc_PortAddress_external_usage2());
  execute(tc_GetReplyParameters());
  execute(tc_MatchingWithAnyOrOmit());
  execute(tc_doneWithValueRedirect());
}
}
