/***************************************************************** 
File: top.pl                           15/6/99 
****************************************************************** 

Hierarchical view of predicates in system:

top (top predicate of the system)
   -> meta_match/exec_rule (meta-rules manage all process)
     -> exec_originate/change_rule (occ-rules manage process occs)
       -> exec_pred/postconditions (occ-rules manage entity  occs)

-----------------------------------------------------------
Top Rule to call system: top(+Step_to_execute)

System Predicates:
Note that Begin_time usually starts from 0.

dyn(state(ID, Time)).
dyn(current_state(ID)).
dyn(current_time(Time)).

All entity/process instances are dynamic occs.
Time and states is managed by top level system predicates.
------------------------------------------------------------
Definition of predicates:

dyn(
  agenda(process(Process_type, Process_id, Actions, 
                (Begin_time, End_time)
        ) ).

dyn(state_modified(true)).
------------------------------------------------------------
Note that all trigger IDs are unique; a trigger ID becomes
a process ID of a process which this trigger invokes. 
------------------------------------------------------------
File Hierarchy:

-> top.pl (process model simulation)

    -> library.pl   % predicate library
    -> store.pl     % save the current state

    -> conflict.pl  % process conflict detector
 *  -> cond.pl      % check the pre- and post-conditions
 *  -> exe.pl       % execute the processes
    -> derive.pl    % handle deriving atts. 

  -> model.pl       % reverse and travel between states

*********************************************************************/

:- multifile dyn/1.
:- dynamic   dyn/1.
:- abolish(dyn/1).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% programs body 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


:- ['./data/process.db'].  % load processes
:- ['./data/junction.db']. % load junctions

:- ['./data/init.db'].    % initial state info 
:- ['./data/trigger.db']. % load triggers

:- [library].

% , store,conflict,cond,exe,model]. % load prolog files 

% data files 

% :-  ['./data/cond.db']. 

% :-  ['./data/dyn.db'], ['./data/entity.db'],['./data/process.db'],
%     ['./data/trigger.db']. 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

:- write('**********************************************************'),nl,
   write('Welcome to The Business Model Simulator for FBPML'),nl,
   write('This simulator is a part of the tool KBST-BM.'), nl,
   write('If you want to run one step at a time, type run. '), nl, 
   write('To run several steps at a time, type top(X). '),nl,
   write('Where X is the number of steps required for enactment.'),nl,
   write('At the end of the enactment type look. to see results.'),nl,   
   write('**********************************************************'),
   nl, nl.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% top(+Step)
% Top Rule for dynamic model reasoning/simulation
% When called, the simulator will run a specified number of Steps.
%
% operates at time and states level
% export_data_to_archive is given in store.pl 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

run :-  top(1). 

%% (1) end of enactment 
top(Step) :-
    Step =< 0, 
    dyn(current_time(Time)),
    write('* End of enactment'),nl, 
    W is Time - 1, 
    write('* Enactment stops at the end of time: '), write(W),nl,
    export_data_to_archive, !. % store.pl 

%% (2) find matching processes 
top(Step) :-
    Step > 0,
    \+     dyn(flag(done_matching))), 
    assert(dyn(flag(done_matchine))), 
    meta_match_process,
    top(Step).

%% (3) execute processes
top(Step) :-
    Step > 0,
    \+     dyn(flag(done_execution)), 
    assert(dyn(flag(done_execution))), 
    meta_exec_process, 
    top(Step).

%% (4) update time and state_id, and get new triggers

top(Step) :-
    dyn(flag(done_matching))), 
    dyn(flag(done_execution))), 

    Step > 0,
    Current_step is Step - 1,
    
    % update time
    dyn(current_time(Time)),
    Newtime is Time + 1,
    retract(dyn(current_time(Time))),
    assert(dyn(current_time(Newtime))),

    % If any changes are made to the state, update state.
    % Otherwise, do not. 

    (          dyn(flag(state_changed)), 
       retract(dyn(flag(state_changed)), 
       update_state;  
       true
    ),

    write('* Finish one enactment step/time unit. '), nl, 
    write('* Enactment time ends at: '), write(Time),nl, 
    write('* Enactment steps left: '), write(Current_step),nl, 
  
%   query_for_adding_triggers,
    top(Current_step). 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% user interfact need to specify the entity instances
% and its types, and its parents, if there is an alternative
% parents.

% Meta level of handing processes, i.e. to determine which
% process should be queued, and which one should be executed.
% It also handles changes of the system status, and enable
% dependency backtracking if necessary.

% check events, identify process, identify entities,
% put process in the agenda, retract event.

% Check how notatin of time has been decided in the agenda.
% Actions of a process is committed at its end-time !

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

meta_match_process :-
        write('>>>>> Searching for new events... '),nl,

        dyn(event(Instance, Event, Tstate, Priority, 
                Begin_time/End_time, 
                (Provider/Provider_type, Receiver/Receiver_type), 
                 Event_content)), 

        %% (1) check timing 
        dyn(current_time(Time)), Time >= Begin_time,

        %% get short-hand variable
        T = dyn(event(Instance, Event, TStatus, Priority, 
                Begin_time/End_time, 
                (Provider/Provider_type, Receiver/Receiver_type), 
                 Event_content)), 

        TState = Status/IDlist, 

        \+ Status = suspended, \+ Status = cancelled, 
        \+ Status = finished, 

        %%%%% identify and add triggered process to process agenda 
        add_process_to_agenda(T), 

        %% to allow all eligible events to be processed in the same state
        meta_match_process. 

meta_match_process. 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Given an external event, trigger all relevant processes. 
% 
% Note: in the current implementation, a process may or may not 
%       have a trigger. 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

add_process_to_agenda(E) :-
    
  %%%%%% get new events as potential triggering conditions 
  E = dyn(event(Instance, Event, EStatus, Priority, B/E, 
          (Provider/Provider_type, Receiver/Receiver_type), 
          Event_content)), 

  EStatus = _/ID_list, 

  %%%%%%%%%%%%%%%%%% match process %%%%%%%%%%%%%%%
  process(Instance, Process/PID, PStatus, Priority, 
          Begin_time/End_time, Time_cost, 
          (Provider/Provider_type, Receiver/Receiver_type), 
          P_Trigger, Precond, Action, Postcond), 

    ( P_Trigger = [true]; P_Trigger = [E] ), 

  P = process(Instance, Process/PID, PStatus, Priority, 
              Begin_time/End_time, Time_cost, 
              (Provider/Provider_type, Receiver/Receiver_type), 
              P_Trigger, Precond, Action, Postcond), 

  %%% match junctions: if it is followed by a start junction, or 
  %%%                  if it is followed by a control-link, or 
  %%%                  if it is followed by an and-joint junction, or 
  %%%                  if it is followed by an and-split junction, or 
  %%%                  if it is followed by an or-joint junction, or 
  %%%                  if it is followed by an or-split junction, 
  %%% and it fits the process, add it to the agenda

    ( EStatus = received/[], start[PID];
      EStatus = processed/List, control_link(A, PID), 
      member(A, List);

      EStatus = processed/List, and_split(A, Blist), 
      member(A, List), member(PID, Blist);
      EStatus = processed/List, or_split(A, Blist), 
      member(A, List), member(PID, Blist);
      
      EStatus = processed/List, and_joint(Alist, PID), 
      subset(Alist, List); 
      EStatus = processed/List, or_joint(Alist, PID), 
      member(X, List), member(X, Alist); )

      %%% update process time, and 
      %%% add process to process agenda 

      dyn(current_time(Time)), 
      Begin_time = Time, 
      End_time is Time + Time_cost,
      assert(dyn(agenda(P))),

      %% update the event status to "processed", to indicated that it is 
      %% being processed. 

       New_event = dyn(event(Instance, Event, processed/ID_list, 
                             Priority, B/E, 
                            (Provider/Provider_type, Receiver/Receiver_type), 
                             Event_content)), 
      retract(E),
      assert(New_event), 

      add_process_to_agenda(T).

add_process_to_agenda(_). 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% meta_exec_process
%
% Function: Allow the user to execute all of the qualified processes.
% Note: The unique event_id later on becomes a process ID
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

meta_exec_process :-

        write('>>>>> Searching for valid processes'),nl,
 
        % find all processes in the agenda ready to be executed
        findall( (Process_type, Tri_id), 
                 ( dyn(agenda(process(Process_type, Tri_id, _Content,
                                     (_Begin_time, End_time)))),
                   dyn(current_time(Time)),        % check time 
                   Time >= End_time ),
                   Process_list),  
   
        \+  Process_list = [], 

        % when there is any process in the agenda, 
        % report these processes 

        % write('********************************************'),nl,
        % write('All processes in the Agenda are given below:'),nl,nl,
        % write_list(Process_list),nl,
        % write('********************************************'),nl,

        % checking that no conflict processes are to be executed 
        % at the same time !
        % calling process conflict detector in 'conflict.pl'

        % write('* Searching for contradicatory processes...'),nl, 
        % \+ detect_conflicted_processes(Process_list),
        % write('  No contradicatory processes found.'),nl, 

        % choose and execute these processes
        choose_process(Process_list). 

meta_exec_process. 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% choose process for execution from process agenda 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
choose_process( []) :- !. 

choose_process( [(Process, PID)|Tail] ) :-

       dyn(agenda(process(Instance, Process/PID, PStatus, Priority, 
              Begin_time/End_time, Time_cost, 
              (Provider/Provider_type, Receiver/Receiver_type), 
              P_Trigger, Precond, Action, Postcond))), 

       P = process(Instance, Process/PID, PStatus, Priority, 
              Begin_time/End_time, Time_cost, 
              (Provider/Provider_type, Receiver/Receiver_type), 
              P_Trigger, Precond, Action, Postcond),

        % decide whether to execute the process or not, 
        % this is determined by the user
        nl, 
        write('*****************************************'),nl,
        write('Would you like to execute this process:'),nl,nl,
        write('Process: '), write(Process_type),nl,
        write('Process/Event Instance: '),write(Tri_id),nl,
        write('The full process:'),nl, 
        pretty_print(P), nl, 
        write('*****************************************'),nl,
        write('Execution? (y/n) '), read(Ans), nl, 

      (\+ Ans = y, choose_process(Tail); 

          Ans = y, 

        write('**********************************'),nl,
        write('     Process Execution Phase      '),nl,       
        write('**********************************'),nl,

        (A) check all actions are correct, 'top.pl'
        write('* Checking the trigger....'),nl, 
        \+ triggering_conditions_satisfied(Process_type, Tri_id),
        write('  checking trigger succeed.'),nl,

%%%%%%% work stops here %%%%%%%

        % (1) check all preconditions are satisfied, 'cond.pl'
        write('* Checking preconditions....'),nl, 
        all_preconditions_satisfied(Process_type, Tri_id),  
        write('  checking precond succeed.'),nl,

        % (B) execute the process, 'exe.pl'
        write('* Executing the process ...'),nl,
        execute_process(Process_type, Tri_id),
        write('  Process execution succeed.'),nl,

        % update the system state status 
        (\+ dyn(flag(state_changed)), 
         assert(flag(state_changed)) ;
         true), 

        % (C) all postconditions are satisfied, 'cond.pl'
        %     if an error occurs, it is reported, but 
        %     it normally does not happen.

        % write('* Verifying postconditions...'),nl,
        % all_postconditions_satisfied(Process_type, Tri_id), 
        % write('  Verifying postconditions succeed.'),nl,

        % (D) Clear up stuff: remove dyn(ent_fun()) predicates, 
        %     these are only used for this process, in 'exe.pl'
        % remove_ref_scope(Process_type, Tri_id), 

        choose_process( Tail ), ! 
      ). 

choose_process( [_|Tail] ) :-
        write('*****************************************'),nl,
        write('WARNING: Failed to execute this process, execute the next one.'),nl, 
        write('*****************************************'),
        nl,nl, 

        choose_process( Tail ). 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

triggering_conditions_satisfied(Process, PID) :- 

   dyn(agenda(process(Instance, Process/PID, PStatus, Priority, 
                      Begin_time/End_time, Time_cost, 
                      (Provider/Provider_type, Receiver/Receiver_type), 
                      [Trigger], Precond, Action, Postcond))), 

       (Trigger = true; 
        Trigger = dyn(event(Instance, Event, _, Priority, 
                 Begin_time/End_time, 
                (Provider/Provider_type, Receiver/Receiver_type), 
                 Event_content)), 

        dyn(event(Instance, Event, TStatus, Priority, 
                 Begin_time/End_time, 
                (Provider/Provider_type, Receiver/Receiver_type), 
                 Event_content)), 

        TState = Status/_, 
        \+ Status = suspended, \+ Status = cancelled, 
        \+ Status = finished ). 

triggering_conditions_satisfied(Process, PID) :- 
  write('Error: triggering conditions for the process is no longer true.'), nl, 
  fail. 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% execute the process, in 'exe.pl'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

execute_process(Process_type, Tri_id) :-

        dyn(current_state(S)),
        read_next_state(S1),
        dyn(agenda(process(Instance, Process/PID, PStatus, Priority, 
                      Begin_time/End_time, Time_cost, 
                      (Provider/Provider_type, Receiver/Receiver_type), 
                      [Trigger], Precond, Action, Postcond))), 

        exec_process(process(Instance, Process/PID,  PStatus, Priority, 
                      Begin_time/End_time, Time_cost, 
                      (Provider/Provider_type, Receiver/Receiver_type), 
                      [Trigger], Precond, Action, Postcond )), 

        % record the change of state

        P = dyn(agenda(process(Instance, Process/PID, PStatus, Priority, 
                      Begin_time/End_time, Time_cost, 
                      (Provider/Provider_type, Receiver/Receiver_type), 
                      [Trigger], Precond, Action, Postcond))), 

        New = dyn(agenda(process(Instance, Process/PID, PStatus, Priority, 
                      Begin_time/End_time, Time_cost, 
                      (Provider/Provider_type, Receiver/Receiver_type), 
                      [Trigger], Precond, Action, Postcond))), 

        retract(P),

        assert(dyn(pro_occ(Process_type, Tri_id))),
        assert(dyn(occ_begin( Process_type, Tri_id, Begin_time))),
        assert(dyn(occ_end(   Process_type, Tri_id, End_time))),

        assert(dyn(change((S, S1), (Process_type, Tri_id),
	       [Del],
	       [dyn(pro_occ(Process_type, Tri_id)),
	        dyn(occ_begin(Process_type, Tri_id, Begin_time)),
		dyn(occ_end(  Process_type, Tri_id, End_time))] ))). 

%----------------------------------------------------------------------
% adding new trigger occurrences from the dynamic business model !!

query_for_adding_triggers :-
   
    write('Would you like to add new triggers to the system? (y./n.)'),nl,
    read(Ans),
    (Ans = y, ['/hame/jessicac/kbst-bm/simulator/data/new.db'],
     write('* Finish importing a new trigger.'), nl; 
     \+ Ans = y ). 

%-------------------------- Common Predicates-------------------------
% move current state to the next state.

update_state :-
    dyn(current_state(S)),
    read_next_state(ID),

    retract(dyn(current_state(S))),
    assert(dyn(current_state(ID))),

    dyn(state_id(X)),
    retract(dyn(state_id(X))), 
    assert(dyn(state_id(ID))),

    dyn(current_time(Time)),
    assert(dyn(state(ID, time(Time)))).

%-----------------------------------------------------------------------
%% Initial Value. state_id/1 is separated from current_state ID so that
%% state_id always generated with an unique ID (since state ID can be a
%% revisited state number). 

read_next_state(ID1) :-
   dyn(state_id(ID)),
   ID1 is ID + 1.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
look :-
    report_state.
%----------------------------------------------------------------------
report_state :-
    write('------- report dynamic system state -------'),nl,
    dyn(current_state(S)), dyn(state(S, time(T))),
    write('Shown State: '), write(S),
    write('  State Time: '), write(T),
    dyn(current_time(Tc)),
    Tw is Tc -1,
    write('. It is now time: '), write(Tw),nl,nl,
    findall(X, ( dyn(X), portray_clause(X),nl), _).
%------------------------- End of Document -----------------------------




