(*
  V2 +
  In this example we model is a falttering of the example toy.
  we may add some lifecycle management.
*)

type core = C1| C2| C3| C4




(* An automaton to model the lifecycyle of a component *)   
node lifecycle(change, kill, fifoEmpty :bool) returns(started, unbound:bool; c_start, c_connect, c_stop, c_disconnect:bool)
let 
  automaton 
    state STOPPED do 
      started = false; 
      unbound =  true;
      (c_start, c_connect, c_stop, c_disconnect)=(change, change, false, false);
    until change then STARTED

      
   state STARTED do 
     started =  true; 
     unbound = false;
     (c_start, c_connect, c_stop, c_disconnect)=(false, false, (change&fifoEmpty)or(kill),(change or kill));
     until change & not fifoEmpty then STOPPING
       |   change & fifoEmpty then STOPPED
       |   kill then STOPPED

   state STOPPING do 
     started = true;
     unbound = true;
     (c_start, c_connect, c_stop, c_disconnect)=(false, change, (fifoEmpty or kill), false);
     until fifoEmpty or  kill then STOPPED
       | change then STARTED
 end;
tel 

(* An atamaton to model the state of a FIFIO *)
node fifo(f:bool) returns (full:bool)
let 
automaton 
  state EMPTY do 
    full = false;
    until f then FULL
  state FULL do 
    full = true;
    until not f then EMPTY
 end;
tel  


(* defines the position of a task *)
(* It has two boolean inputs to encode 4 possible outgoing transitions *)

node position(a, b :bool)returns(position:core; c_mig1, c_mig2, c_mig3, c_mig4:bool)

(* !a!b go to the next in the numerical order
   !ab to the second 
   a!b to the last  *)
     let 
	 
     automaton 
     	       state C1 do 
	       position=C1;
		 (c_mig1, c_mig2, c_mig3, c_mig4)=(false, not a & not b, not a & b, a & not b);
	       until not a & not b then C2
	       | not a & b  then C3
	       | a & not b then C4
			   
     	       state C2 do 
	       position=C2;
		 (c_mig1, c_mig2, c_mig3, c_mig4)=(a & not b, false, not a & not b, not a & b);
	       until not a & not b then C3
	       | not a & b  then C4
	       | a & not b then C1

     	       state C3 do 
	       position=C3;
		 (c_mig1, c_mig2, c_mig3, c_mig4)=( not a & b, a & not b, false, not a & not b);
	       until not a & not b then C4
	       | not a & b  then C1
	       | a & not b then C2


     	       state C4 do 
	       position=C4;
		 (c_mig1, c_mig2, c_mig3, c_mig4)=( not a & not b, not a & b, a & not b, false);
	       until not a & not b then C1
	       | not a & b  then C2
	       | a & not b then C3
	    end;
     tel 



(* core availability  in the paper it's called proc *)
node core_mode(disable:bool)returns(active:bool)
let 
    automaton 
      state COREON do 
	active= true; 
      until disable then COREOFF

      state COREOFF do 
	active= false; 
      until not disable  then COREON
 end; 
tel


(* computes the communication cost associated with each core hosting the tasks *)
(* in the paper there is additional inputs f: frequency c: function cost *)

node communication_cost(core1,  core2:core)returns(costcpu1, costcpu2:int)
     var sameproc, samecore:bool;
     task1_cpu, task1_core, task2_cpu, task2_core:bool;
     let

	 switch core1 
	 | C1 do (task1_cpu, task1_core)=(false, false)
	 | C2 do (task1_cpu, task1_core)=(false, true);
	 | C3 do (task1_cpu, task1_core)=(true, false);
	 | C4 do (task1_cpu, task1_core)=(true, true);
         end ;

	 switch core2 
	 | C1 do (task2_cpu, task2_core)=(false, false)
	 | C2 do (task2_cpu, task2_core)=(false, true);
	 | C3 do (task2_cpu, task2_core)=(true, false);
	 | C4 do (task2_cpu, task2_core)=(true, true);
         end ;	 

     sameproc = (task1_cpu & task2_cpu) or (not task1_cpu & not task2_cpu);
     samecore = sameproc & ((task1_core & task2_core) or (not task1_core & not task2_core));

    (costcpu1, costcpu2)= if samecore then (10,20)
     else if sameproc then (20,40)
     else (50,100);
     tel


(* associates the cost to the given coreId *)
node core_cost(coreId:core; cost:int)returns(c1, c2, c3, c4:int)     
     let 	 
	 switch coreId 
	 | C1 do (c1,c2,c3,c4)= (cost, 0,0,0)
	 | C2 do (c1,c2,c3,c4)= (0,cost,0,0) 
	 | C3 do (c1,c2,c3,c4)= (0,0,cost,0)
         | C4 do (c1,c2,c3,c4)= (0,0,0,cost);
         end; 	 
     tel 

(* modeling asynchronous commands *)
node command(start, isdone: bool)returns(pending:bool)
let 
    automaton 
      state DONE do 
	pending = false; 
      until start then PENDING

      state PENDING do 
	pending=true;
      until isdone  then DONE
 end; 
tel
        

(* ======================================== some utility nodes =============================================  *)




(* retruns an integer representation of the coreId*)  
node compute_core(coreId:core; active:bool)returns(icore:int)
let
    
    	 switch coreId 
	 | C1 do icore = if(active) then 1 else 0
	 | C2 do icore = if(active) then 2 else 0
	 | C3 do icore = if(active) then 3 else 0
         | C4 do icore = if(active) then 4 else 0
         end; 
tel


(* tells whether a task is running on core 3*)
node comOn3(coreId:core)returns(yes:bool)
let
  switch coreId 
	 | C1 do yes = false
	 | C2 do yes = false
	 | C3 do yes = true
         | C4 do yes = false
         end; 	 
tel

(* tells whether a task is running on core 1*)
node comOn1(coreId:core)returns(yes:bool)
let
  switch coreId 
	 | C1 do yes = true
	 | C2 do yes = false
	 | C3 do yes = false
         | C4 do yes = false
         end; 	 
tel


(* tells whether two cores are equivalent *)
node same_core(core1:core; core2: core) returns(samecore:bool)
var sameproc:bool;
task1_cpu, task1_core, task2_cpu, task2_core:bool;
let 
	 switch core1 
	 | C1 do (task1_cpu, task1_core)=(false, false)
	 | C2 do (task1_cpu, task1_core)=(false, true);
	 | C3 do (task1_cpu, task1_core)=(true, false);
	 | C4 do (task1_cpu, task1_core)=(true, true);
         end ;

	 switch core2 
	 | C1 do (task2_cpu, task2_core)=(false, false)
	 | C2 do (task2_cpu, task2_core)=(false, true);
	 | C3 do (task2_cpu, task2_core)=(true, false);
	 | C4 do (task2_cpu, task2_core)=(true, true);
         end ;	 

     sameproc = (task1_cpu & task2_cpu) or (not task1_cpu & not task2_cpu);
     samecore = sameproc & ((task1_core & task2_core) or (not task1_core & not task2_core));
tel 


(* this implments the boolean equivalence *)

node bool_equi(a,b:bool) returns (res:bool)
let 
    res = ((a or not b) & (b or not a));
tel 

(* this implments the boolean implication *)

node bool_impl(a,b:bool) returns (res:bool)
let 
    res = (not a or b) ;
tel 



(* ==================================================================================*)





(* main program *)
node main(disable: bool; fifoH1F, fifoH2F, fifoL2F, changeL2:bool; c_startH2_done, c_stopH2_done:bool) returns(
									  h2Required:bool;
									  h2NotRequired:bool;
									  noOneOnC3WhenOff:bool;
									  costgood:bool;
									  notF2whenLogger:bool;
									  startH2:bool; unboundH2:bool;
									  startL2:bool; unboundL2:bool;
							c_startH2, c_stopH2, c_connectH2, c_diconnectH2:bool;
							c_startL, c_stopL, c_connectL, c_diconnectL:bool;
							a_cmd_pending:bool
									  )



(* BEGIN CONTRACT *)
(*====================================================*)
  contract

let 


tel 
    assume(true)

  enforce((a_cmd_pending or h2Required) 
	  & (a_cmd_pending or h2NotRequired) 
	  & noOneOnC3WhenOff 
	  & costgood 
	  & (a_cmd_pending or notF2whenLogger))
 with(cDa, cDb:bool;
      cSAa, cSAb:bool;
      cSBa, cSBb:bool;
      changeH2, killH2:bool
     )
(*====================================================*)
(* END CONTRACT *)


	var 
      c_startH2_pending, c_stopH2_pending:bool;
   c1,c2,c3,c4:int;  pf, pa, pd, pf1, pf2, pl:int;

      (* these vars are true when no one is running on the associate core  *)
      noOneOnC3:bool; 
      
      (* vars to describe the desired max corecosts 
	 when there is only 3 cores and when there is 4 cores available
      *)
      
       costNActive:bool; costActive:bool;

     (* component cores *)
     frend_core, ana_core, disp_core, file1_core, file2_core, logger_core :core;


      (* communication costs for each binding  server/client side*)
      costAs, costAc:int; (*cost of the communication line between frend and ana*)
      costBs, costBc:int; (*cost of the communication line between ana and disp*)
      costCs, costCc:int; (*cost of the communication line between ana and logger*)
      costDs, costDc:int; (*cost of the communication line between disp and file1*)
      costFs, costFc:int; (*cost of the communication line between disp  and file2*)

      (*core 1,2,3,4 workload*)
      cost1, cost2, cost3, cost4: int; 
      
      costAs1, costAs2, costAs3, costAs4:int; (*cost of the communication line A on each core -- server side*)
      costBs1, costBs2, costBs3, costBs4:int; (*cost of the communication line B on each core -- server side*)
      costCs1, costCs2, costCs3, costCs4:int; (*cost of the communication line C on each core -- server side*)
      costDs1, costDs2, costDs3, costDs4:int; (*cost of the communication line D on each core -- server side*)
      costFs1, costFs2, costFs3, costFs4:int; (*cost of the communication line F on each core -- server side*)
      
      costAc1, costAc2, costAc3, costAc4:int; (*cost of the communication line A on each core -- client side*)
      costBc1, costBc2, costBc3, costBc4:int; (*cost of the communication line B on each core -- client side*)
      costCc1, costCc2, costCc3, costCc4:int; (*cost of the communication line C on each core -- client side*)      
      costDc1, costDc2, costDc3, costDc4:int; (*cost of the communication line D on each core -- client side*)
      costFc1, costFc2, costFc3, costFc4:int; (*cost of the communication line F on each core -- client side*)

      core3:bool; (* state of the core number 3 ON/OFF*)
      
      fifoH1Full, fifoH2Bull, fifoL2Bull : bool; (*satte of the fifos associated with filehandlers and logger*)
      
      (* intermediate variables to control task positions *)
      iFa, iFb : bool;
      iAa, iAb : bool;
      iDa, iDb : bool;
      iF1a, iF1b : bool;
      iF2a, iF2b : bool;
      iLa, iLb : bool;
      
      (* migration commands. those that are not output*)
     c_mig1f , c_mig2f , c_mig3f , c_mig4f,
     c_mig1a , c_mig2a , c_mig3a , c_mig4a,
     c_mig1f1 , c_mig2f1 , c_mig3f1 , c_mig4f1,
     c_mig1l , c_mig2l , c_mig3l , c_mig4l,
     c_mig1f2 , c_mig2f2 , c_mig3f2 , c_mig4f2 ,
     c_mig1d , c_mig2d , c_mig3d , c_mig4d:bool;
      

      (* two boolean  variables to set the enforced propertie true for the 3 first instants *)
      count0, count1 : bool  ;

let 


(* controlling task positions *)
    (iFa, iFb)=(false->true, false->true); (* frontend always on Core 2*)
  (iAa, iAb)=(false->true, false->true); (* analyzer manager always on Core2*)
  (iDa, iDb)=(true->cDa, false->cDb); (* dispa core 4 at the begining*)
  (iF1a, iF1b)=(true->cSAa, false->cSAb); (* fileh1 on core 4 at first *)
  (iF2a, iF2b)=(true->cSBa, false->cSBb); (* file2 core 4 at first *)
  (iLa, iLb)=(true->true, false->true); (* logger always on Core 4*)
 

  (* Computnig the cost of each core   *)
  cost1= costAs1+ costBs1 + costCs1 + costDs1  + costFs1 
  + costAc1 + costBc1 + costCc1 + costDc1  + costFc1;
  
  cost2= costAs2+ costBs2 + costCs2 + costDs2 + costFs2
  + costAc2 + costBc2 + costCc2 + costDc2  + costFc2;

  cost3= costAs3+ costBs3 + costCs3 + costDs3 + costFs3 + 
  costAc3 + costBc3 + costCc3 + costDc3 +  costFc3;

  cost4= costAs4+ costBs4 + costCs4 + costDs4 +  costFs4 + 
  costAc4 + costBc4 + costCc4 + costDc4 +  costFc4;  
  


  (* filehandler2 lifecycyle and the workload it induces *) 
  (startH2, unboundH2, c_startH2,  c_connectH2, c_stopH2, c_diconnectH2)= inlined lifecycle(not changeH2, not killH2 , not fifoH2Bull);
  (costFc, costFs) = if (not startH2) then  (0,0) else inlined communication_cost(disp_core,file2_core);


  (* Logger lifeCycle and the workload it induces *) 
  (startL2, unboundL2, c_startL,  c_connectL, c_stopL, c_diconnectL)= inlined lifecycle(changeL2,  false, not fifoL2Bull);
  (costCc, costCs) = if (not startL2) then  (0,0) else inlined communication_cost(ana_core,logger_core);


  (*switch on/off core 3*)
  core3= inlined core_mode(disable);


  (* the fifio associated with file1 component input*)
  fifoH1Full = inlined fifo(fifoH1F);
 
 (* the fifio associated with file2 component input*)
 fifoH2Bull =  inlined fifo(fifoH2F);

 (* the fifio associated with logger component input*)
 fifoL2Bull =  inlined fifo(fifoL2F);
 

 (* component mapping *)
 (frend_core, c_mig1f , c_mig2f , c_mig3f , c_mig4f)= inlined position(iFa, iFb);
 (ana_core, c_mig1a , c_mig2a , c_mig3a , c_mig4a)= inlined position(iAa, iAb);
 (file1_core,  c_mig1f1 , c_mig2f1 , c_mig3f1 , c_mig4f1)= inlined position(iF1a, iF1b);
 (disp_core,  c_mig1d , c_mig2d , c_mig3d , c_mig4d)= inlined position(iDa, iDb);
 (file2_core, c_mig1f2 , c_mig2f2 , c_mig3f2 , c_mig4f2)= inlined position(iF2a, iF2b);
 (logger_core,  c_mig1l , c_mig2l , c_mig3l , c_mig4l)= inlined position(iLa, iLb);

 (* communication lines cost server/client side  *)
 (costAc, costAs) = inlined communication_cost(frend_core, ana_core);
 (costBc, costBs) = inlined communication_cost(ana_core,  disp_core);
 (costDc, costDs) = inlined communication_cost(disp_core,  file1_core);

 

 (* associating each communiaction line cost to the concerned processor *)
 (costAs1, costAs2, costAs3, costAs4) = inlined core_cost(ana_core, costAs);
 (costAc1, costAc2, costAc3, costAc4) = inlined core_cost(frend_core, costAc);

 (costBs1, costBs2, costBs3, costBs4) = inlined core_cost(disp_core, costBs);
 (costBc1, costBc2, costBc3, costBc4) = inlined core_cost(ana_core, costBc);

 (costCs1, costCs2, costCs3, costCs4) = inlined core_cost(logger_core, costCs);
 (costCc1, costCc2, costCc3, costCc4) = inlined core_cost(ana_core, costCc);

 (costDs1, costDs2, costDs3, costDs4) = inlined core_cost(file1_core, costDs);
 (costDc1, costDc2, costDc3, costDc4) = inlined core_cost(disp_core, costDc);

 (costFs1, costFs2, costFs3, costFs4) = inlined core_cost(file2_core, costFs);
 (costFc1, costFc2, costFc3, costFc4) = inlined core_cost(disp_core, costFc);

 (* outputs/properties *)

 (* core costs *)
 (c1, c2, c3, c4)= (cost1, cost2, cost3, cost4);

 (* component positions -- from enumerated type to integers *)
 pf = inlined compute_core(frend_core, true);
 pa = inlined compute_core(ana_core, true);
 pf1 = inlined compute_core(file1_core, true);
 pf2 = inlined compute_core(file2_core, startH2);
 pl = inlined compute_core(logger_core, startL2);
 pd = inlined compute_core(disp_core, true);
 
 


 
 noOneOnC3 =       not (inlined comOn3(file1_core) 
			or inlined comOn3(file2_core) 
			or inlined comOn3(disp_core)
			or inlined comOn3(logger_core)
			or inlined comOn3(frend_core)
			or inlined comOn3(ana_core)
		       );





 count0 = false -> true; 
 count1 = false fby count0;


 (* Asychronous commands *)
 c_startH2_pending= inlined command(c_startH2, c_startH2_done);
 c_stopH2_pending= inlined command(c_stopH2, c_stopH2_done);
 a_cmd_pending =  c_startH2_pending or  c_stopH2_pending;

(* properties to enforce *)

(* when logger  is on, the file2 cannot be started *)
 notF2whenLogger = inlined bool_impl(startL2 & not unboundL2, unboundH2); 

 (*When core 3 is off, no one can run on it *)  (*may be done without ifthenelse*)
 noOneOnC3WhenOff = if(core3) then true else noOneOnC3;


(* FileHandler 2 is required when the fifo of file1 is full  *)
 h2Required =  inlined bool_impl(not startL2 & fifoH1Full, startH2 & not unboundH2);

(* FileHandler 2 is not required when the fifo of file1 is Empty  *)
(* We just disconnect it, it will be stopped once its fifo is empty*)
 h2NotRequired = inlined bool_impl(not fifoH1Full,unboundH2);

(* workload when 3 cores or 4 cores *)
 costActive=(c1<=170 & (c2<=180) & c3<=180 & c4<=140);
 costNActive=(c1<=280 & c2<=280 & c3<=280 & c4<=280);
 costgood = if(not count1)then true else (if core3 then costActive else costNActive);
 tel 
