/***************************************************************** 
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 and junctions

:- ['./data/init.db'].    % initial state info 
:- ['./data/trigger.db']. % load triggers

% :-  [library, store,conflict,cond,exe,model].

% data files 

% :-  ['./data/cond.db']. 

% :-  ['./data/dyn.db'], ['./data/entity.db'],['./data/process.db'],
%     ['./data/trigger.db']. 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% initialisation

:- dyn(current_state(S)), assert(dyn(state_id(S))). % work, not sure needed. 

:- assert(dyn(skip_process(no))). % to allow proecess execution. 

:- 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('Please activate this program by typing 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.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 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 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%   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(done_matching), 
    assert(dyn(done_matchine)), 
    meta_match_process,
    top(Step).

%% (2) execute processes
top(Step) :-
    Step > 0,
    \+ dyn(done_execution), 
    assert(dyn(done_execution)), 
    meta_exec_process, 
%   state_is_updated, <--- need to move into the sub-predicate
    top(Step).

%% (4) update time and state_id, and get new triggers

top(Step) :-
    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(state_modified(true)),
       retract(dyn(state_modified(true))),
       update_state ;
       \+ dyn(state_modified(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. 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 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). 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% choose process for execution 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
choose_process( []) :- !. 

choose_process( [(Process_type, Tri_id)|Tail] ) :-

        % decide whether to execute the process or not, 
        % this is determined by the user
        nl, 
        write('**********************************'),nl,
        write('Would you like to execute process:'),nl,nl,
        write(Process_type),nl,
        write('Process/Event Instance: '),write(Tri_id),nl,
        write('**********************************'),nl,
        write('Execution? (y/n) '), read(Ans), nl, 

      (\+ Ans = y, retract(dyn(skip_process(_))),
          assert(dyn(skip_process(yes))), 
          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, 
        \+ error_trigger_action(Process_type, Tri_id),
        write('  checking trigger succeed.'),nl,

        % (1) check all preconditions are satisfied, 'cond.pl'
        write('* Checking preconditions....'),nl, 
        all_preconditions_satisfied(Process_type, Tri_id),  
        write('  checking precond succeed.'),nl,

        % (2) check all ref_att are existed, 'cond.pl'
        write('* Checking referred attributes...'),nl, 
        all_ref_att_exist(Process_type, Tri_id), 
        write('  checking referred attributes succeed.'),nl,

        % (3) check all add_att are not existed, 'cond.pl'
        write('* Checking adding attributes...'),nl, 
        all_add_att_not_exist(Process_type, Tri_id), 
        write('  checking adding attributes succeed.'),nl,

        % (4) check all chg_att are existed, 'cond.pl'
        write('* Checking changing attributes...'),nl, 
        all_chg_att_exist(Process_type, Tri_id), 
        write('  checking changing attributes succeed.'),nl,

        % (B) execute the process, 'exe.pl'
        write('* Executing the process ...'),nl,
        execute_process(Process_type, Tri_id),
        write('  Process execution succeed.'),nl,

        % (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('Failed to execute this process, execute the next one.'),nl,
        retract(dyn(skip_process(_))),
        assert(dyn(skip_process(yes))), 
        choose_process( Tail ). 

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% execute the process, in 'exe.pl'
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

execute_process(Process_type, Tri_id) :-

        dyn(current_state(S)),
        read_next_state(S1),
        dyn(agenda(process(Process_type, Tri_id, Content,
                           (Begin_time, End_time)))),

        % write('>>>>>> debug: before exec process'),nl, 

        exec_process((S, S1), Process_type, Tri_id, 
                     Content, (Begin_time, End_time)),

        % write('>>>>>> debug: after exec process'),nl, 

        % record the change of state
        Del =  dyn(agenda(process(Process_type, Tri_id, Content,
                                 (Begin_time, End_time) ))),
        retract(Del),

        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-------------------------
state_is_updated :-
    dyn(state_modified(true)).

state_is_updated :-
    \+ dyn(state_modified(true)),
    assert(dyn(state_modified(true))).
%----------------------------------------------------------------------
% 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.

%------------------------------------------------------------------------

error_trigger_action(Process_type, Tri_id) :- 

        dyn(agenda(process(Process_type, Tri_id, 
                           Actions, _Time))), 

        member(X, Actions),
        \+ member(X, [originate_focal_entity(_,_), 
                      originate_normal_entity,
                      originate_if_entity(_, _),
                      change_focal_entity(_, _), 
                      change_normal_entity(_, _),
                      refer_normal_entity(_, _),
                      refer_focal_entity(_, _),
                      add_att(_, _, _, _),
                      chg_att(_, _, _, _),
                      derive_att(_, _, _),
                      ref_att(_, _, _)    ] ),

        write('Error: wrong specification in the trigger.'),nl,
        write('Process Type and Trigger ID'),nl,
        write(Process_type),write(': '), write(Tri_id),nl, 
        write('Action: '), write(X),nl.

%---------------------------- common predicates -----------------------
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 -----------------------------


