%% :- module(examples, [main/0], []).

:- use_module(library(concurrency)).
:- use_module(library(format)).
:- use_module(library(random)).
:- use_module(library(system)).
:- use_module(library(prolog_sys)).
:- use_module(library(lists)).

main:-
        message("Creating agents"),
        agent_creation(10),
        message("Waiting for an agent to finish"),
        wait_for_agent,
        message("Waiting for remote failing goal"),
        wait_for_failure,
        message("Asking a remote agent for backtracking"),
        rem_back,
        message("Cutting the execution in a remote agent"),
        rem_cut,
        message("Starting a daemon with a new thread per query"),
        daemon(10),
        message("Waiting for facts"),
        wait_facts,
        message("Two-way communication using the database"),
        two_way,
        message("Add the numbers from 1 to N using two threads"),
        count(2000),
        message("The 5 philosophers"),
        philo_start(100),
        message("All examples finished!").



message(S):-
        display('------------------------------------------------------'),
        nl,
        format("~s~n", S),
        display('------------------------------------------------------'),
        nl.

agent_creation(0):-
        pause(1).
agent_creation(N):-
        eng_call(pause(1), create, create),
        N1 is N - 1,
        agent_creation(N1).



%% Start, try waiting several times, release.

status:-
	display('-------------------------------------------'),
	nl,
	eng_status.

% Start goal, wait for its completion

wait_for_agent:-
	eng_call(pause(5), create, create, Id),
	eng_wait(Id),
	eng_release(Id).


%% Failure of remote goals do not affect locally.   The wam is 
%% released immediately, but the goal definition remains until we free it.

wait_for_failure:-
	eng_call(fail, create, create, Id),
	eng_wait(Id),
	eng_release(Id).


%% Backtracking in separate thread

:- concurrent finished/0.

rem_back:-
	eng_call(do_append_notify, create, self, Id),
	repeat,
	eng_backtrack(Id, create),
        eng_wait(Id),
	retract_fact_nb(finished),
	eng_release(Id).

do_append_notify:- 
        L =  [1,2,3,4,5,6],
	append(X, Y, L),
	display(append(X, Y, L)),
	nl.
do_append_notify:- asserta_fact(finished).


%% Cut in backtracking

rem_cut:-
	eng_call(do_append_notify, create, create, Id),
	eng_wait(Id),
	eng_cut(Id),
	\+ eng_backtrack(Id, self),
	eng_release(Id).



%% A possible skeleton of a daemon.  A thread is launched and
%% receives queries.  Every query is just a number, generated by the
%% daemon itself, but which could come from, say, a socket connection.
%% When the query arrives a process is launched.  This process executes
%% (concurrently) the server code, which just prints out its thread
%% number and which query arrived.  Simultaneously, the main daemon is
%% "listening" for more queries, actually implementing a loop.  After
%% a given number of queries is receive, the initial damon dies.


daemon(0):- pause(2).
daemon(N):-
        N > 0,
        wait_for_query(Query),
        eng_call(daemon_handler(Query), create, create),
        N1 is N - 1,
        daemon(N1).

daemon_handler(X):- 
        display('daemon received query '),
        display(X),
        nl.

wait_for_query(Q):-
        pause(1),
        random(Q).



%% Threads can communicate and synchronize using facts: a thread
%% will wait for concurrent facts to appear.  The thread is suspended
%% and does not waste cpu.

:- concurrent proceed/1.

wait_facts:-
        eng_call(wait_for_a_fact, create, create),
        eng_call(wait_for_a_fact, create, create),
        eng_call(wait_for_a_fact, create, create),
        pause(1),
        asserta_fact(proceed(1)),
        pause(1),
        asserta_fact(proceed(2)),
        pause(1),
        asserta_fact(proceed(3)),
        pause(1).


wait_for_a_fact:-
        retract_fact(proceed(X)),
        display(proceeding(X)), 
        nl.

        
% Two-way communication: a concurrent goal produces tokens, which are
% consumed by another concurrent goal, which in turn produces another
% token which is brought back to the initial producer.


:- concurrent go_token/1, return_token/1. 

number_of_tokens(2000).

two_way:-
        number_of_tokens(N),
        p19(N).


p19(NTokens):-
        retractall_fact(go_token(_)),
        retractall_fact(return_token(_)),
        eng_call(token_return, create, create, GId1),
        eng_call(token_go, create, create, GId2),
        asserta_fact(go_token(NTokens)),  % This actually triggers execution...
        eng_wait(GId1),
        eng_release(GId1),
        eng_wait(GId2),
        eng_release(GId2).

token_go:-
        retract_fact(go_token(What)),
        Answ is What - 1,
        assertz_fact(return_token(Answ)),
        Answ = 0.

token_return:-
        repeat,
        retract_fact(return_token(What)),
        assertz_fact(go_token(What)),
        What = 0.



% add the numbers from 1 to N.  One worker writes down facts for the
% numbers, the other reads them and adds.  Note the cuts after
% retraction.

:- concurrent counter/1.

count(N):-
        retractall_fact(counter(_)),
        eng_call(generate_numbers(N), create, create),
        eng_call(sum_up_numbers, create, create, Id2),
        eng_wait(Id2),
        eng_release(Id2),
        retract_fact(counter(Res)),
        display(result(Res)), nl.

generate_numbers(0):-
        assertz_fact(counter(0)).
generate_numbers(N):-
        N > 0,
        assertz_fact(counter(N)),
        N1 is N - 1,
        generate_numbers(N1).

sum_up_numbers:-
        retract_fact(counter(CurrNum)), !,
        sum_them_up(CurrNum, 0, Res),
        asserta_fact(counter(Res)).

sum_them_up(0, SoFar, SoFar).
sum_them_up(Current, Added, Res):-
        Current > 0,
        NewAdded is Current + Added,
        retract_fact(counter(NewCurrent)), !,
        sum_them_up(NewCurrent, NewAdded, Res).
        



 %% The 5 philosophers.  Yes, the code below does precisely what I told
 %% you not to do: wait for clauses of a predicate others were modifying,
 %% but anyway coding it like this is easier.  I could assign a different
 %% predicate to every fork, but it would not add up to readability.  Yes,
 %% it goes slow.


:- concurrent
        start_philosophers/0,
        f1/0, f2/0, f3/0, f4/0, f5/0,
        room/0.

philo_start(Cicles):-
        launch_philosophers(5, Cicles, GoalIds),
        asserta_fact(start_philosophers),
        wait_and_release_goals(GoalIds),
        retract_fact_nb(start_philosophers).

launch_philosophers(0, _Cicles, []).
launch_philosophers(N, Cicles, [G|Gs]):- 
        N > 0,
        eng_call(philo(N, Cicles), create, create, G),
        N1 is N - 1,
        launch_philosophers(N1, Cicles, Gs).

philo(N, Time):-
        current_fact(start_philosophers),
        philosopher(N, Time).
philosopher(_N, 0).
philosopher(Fork, Time):-
        Time > 0,
        NextTime is Time - 1,
        NextFork is (Fork mod 5) + 1 ,
        fork(Fork, F1),
        fork(NextFork, F2),
        retract_fact(room),
        retract_fact(F1),
        retract_fact(F2),
        some_time_eating(100),
        assertz_fact(F1),
        assertz_fact(F2),
        assertz_fact(room),
        philosopher(Fork, NextTime).

some_time_eating(0).
some_time_eating(N):- 
        N > 0,
        N1 is N - 1,
        some_time_eating(N1).

fork(1,f1).
fork(2,f2).
fork(3,f3).
fork(4,f4).
fork(5,f5).

f1.
f2.
f3.
f4.
f5.

room.
room.
room.
room.

wait_and_release_goals([]).
wait_and_release_goals([Id|Ids]):-
        eng_wait(Id),
        eng_release(Id),
        wait_and_release_goals(Ids).
