/*******************************************************************************

    File:     classes/process.pl
    Contents: Process methods for PsyCOGENT
    Author:   Pete Yule
    Copyright 2000 Peter G. Yule

    This file is part of COGENT.

    COGENT is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    COGENT is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with COGENT; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*******************************************************************************/

:- module(process, [check_fired/3]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% process_box_content/3 method.

process_box_content(Id, Path, 'Process') :-
    get_generic_dumpfile(Path, 'Process', FileName, _DumpFile, MessageFile),
    oos_exists(FileName), !,
    oos_read(FileName, Rules),
    box_module(Id, Module),
    assert_rules(Id, Module, 1, Rules),
    initial_state(Id, [], [], MessageFile).

/*--------------------------------------------------------------------------*/

assert_rules(_, _, _, []).
assert_rules(Id, Module, N, [Head|Tail]) :-
    assert_rule(Id, Module, N, Head, N1),
    assert_rules(Id, Module, N1, Tail).

% triggered, fire once, refracted
assert_rule(Id, Module, N, '!rule'(_, Properties, ConditionList, Actions), N1) :-
    memberchk(triggered(Trigger), Properties), 
    memberchk(fire_once, Properties), 
    memberchk(refracted, Properties), !,
    N1 is N + 1,
    check_special_triggers(Trigger, NewTrigger),
    flatten_condition(Id, N, enough(ConditionList), Conditions),
    get_refract_property(Refract, Properties, (Trigger,Conditions)),
    elaborate_conditions(Refract, Conditions, Id, N, FullConditions),
    assert( (Module:(rule(Id, NewTrigger, Refract, Conditions, N, Actions) :- FullConditions)) ).

% triggered, fire once, unrefracted
assert_rule(Id, Module, N, '!rule'(_, Properties, ConditionList, Actions), N1) :-
    memberchk(triggered(Trigger), Properties), 
    memberchk(fire_once, Properties), !,
    N1 is N + 1,
    check_special_triggers(Trigger, NewTrigger),
    flatten_condition(Id, N, once(ConditionList), Conditions),
    get_refract_property(Refract, Properties, (Trigger,Conditions)),
    elaborate_conditions(Refract, Conditions, Id, N, FullConditions),
    assert( (Module:(rule(Id, NewTrigger, Refract, Conditions, N, Actions) :- FullConditions)) ).

% triggered, unrestricted firing, (un)refracted
assert_rule(Id, Module, N, '!rule'(_, Properties, ConditionList, Actions), N1) :-
    memberchk(triggered(Trigger), Properties), !,
    N1 is N + 1,
    check_special_triggers(Trigger, NewTrigger),
    flatten_conditions(Id, N, ConditionList, Conditions),
    get_refract_property(Refract, Properties, (Trigger,Conditions)),
    elaborate_conditions(Refract, Conditions, Id, N, FullConditions),
    assert( (Module:(rule(Id, NewTrigger, Refract, Conditions, N, Actions) :- FullConditions)) ).

% untriggered, fire once, refracted
assert_rule(Id, Module, N, '!rule'(_, Properties, ConditionList, Actions), N1) :-
    memberchk(fire_once, Properties), 
    memberchk(refracted, Properties), !,
    N1 is N + 1,
    flatten_condition(Id, N, enough(ConditionList), Conditions),
    get_refract_property(Refract, Properties, Conditions),
    elaborate_conditions(Refract, Conditions, Id, N, FullConditions),
    assert( (Module:(rule(Id, Refract, Conditions, N, Actions) :- FullConditions)) ).

% untriggered, fire once, unrefracted
assert_rule(Id, Module, N, '!rule'(_, Properties, ConditionList, Actions), N1) :-
    memberchk(fire_once, Properties), !,
    N1 is N + 1,
    flatten_condition(Id, N, once(ConditionList), Conditions),
    get_refract_property(Refract, Properties, Conditions),
    elaborate_conditions(Refract, Conditions, Id, N, FullConditions),
    assert( (Module:(rule(Id, Refract, Conditions, N, Actions) :- FullConditions)) ).

% untriggered, unrestricted firing, (un)refracted
assert_rule(Id, Module, N, '!rule'(_, Properties, ConditionList, Actions), N1) :-
    N1 is N + 1,
    flatten_conditions(Id, N, ConditionList, Conditions),
    get_refract_property(Refract, Properties, Conditions),
    elaborate_conditions(Refract, Conditions, Id, N, FullConditions),
    assert( (Module:(rule(Id, Refract, Conditions, N, Actions) :- FullConditions)) ).

% condition
assert_rule(Id, Module, N, '!condition'(_, Functor, Arity, Clauses), N) :-
    functor(Head, Functor, Arity),
    retractall(Module:Head),
    assert_condition(Id, Module, Clauses).

get_refract_property(refracted(Vars), Properties, Conditions) :-
    memberchk(refracted, Properties),
    free_variables(Conditions, Vars).
get_refract_property(unrefracted, Properties, _Conditions) :-
    \+ memberchk(refracted, Properties).

check_special_triggers(Trigger, Trigger) :-
    \+ var(Trigger),
    check_system_trigger(Trigger),
    logical_assert(system_trigger(Trigger)),
    fail.
check_special_triggers(Trigger, Trigger).

check_system_trigger(system_end(Level)) :-
    fast_member(Level, [trial, block, subject, experiment]).
check_system_trigger(system_quiescent).

elaborate_conditions(Refract, Conditions, Id, N, ActualConditions) :-
    refract_conditions(Refract, Conditions, Id, N, TmpConditions),
    get_firing_rate(Id, Rate),
    check_firing_rate(Rate, TmpConditions, ActualConditions).

refract_conditions(unrefracted, Conditions, _, _, Conditions).
refract_conditions(refracted(Vars), Conditions, Id, N, (Conditions, \+ check_fired(Id, N, Vars))).

check_firing_rate(Rate, Conditions, (Conditions, random(uniform(0,1),R), R < Rate)) :-
    Rate < 1.0, !.
check_firing_rate(_Rate, Conditions, Conditions).

get_firing_rate(Id, Rate) :-
    object_properties(Id, Properties),
    memberchk(real('Firing Rate', Rate), Properties), !.
get_firing_rate(_Id, 1.0).

check_fired(Id, N, Vars) :-
    convert_precision(Vars, NewVars),
    fired(_, Id, N, NewVars).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% initialise_object/3 method.

% PROCESSES don't have a state. Their rules and conditions have already been
% loaded directly into Prolog. 
% Just reset message file and clear refraction state.

initialise_object(Id, 'Process', Type) :-
    object(Id, _Name, Path, _Parent, 'Process', _),
    object_properties(Id, Properties),
    reinitialise_state(Id, Properties, Type),
    build_message_filename(Path, MessageFile),
    reset_file(MessageFile),
    retractall(user:fired(_Cycle, Id, _N, _ConvVars)).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% step_data/2 method.

step_data(_, _) :-
    fail.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% step_box/2 method.

%    For each triggered rule we need to do two things:
%    1) generate the output corresponding to any input waiting on the bus
%       for that rule; and
%    2) remove any input waiting on the bus for that process.

step_box(Id, 'Process') :-
    object(Id, _, _, Parent, _, Module),
    clock(Cyc),
    LCyc is Cyc-1,
    retract(user : data(LCyc, _, Input, Id)),
    apply_triggered_rules(Id, Input, Parent, Module),
    fail.

%   In addition, we must apply any autonomous rules for each process.

step_box_autonomous(Id, 'Process') :-
    object(Id, _, _, Parent, _, Module),
    apply_autonomous_rules(Id, Parent, Module),
    fail.

apply_triggered_rules(Id, Input, Parent, Module) :-
    (Module : rule(Id, Input, Refract, Conditions, N, Actions)),
    refract_rule(Refract, Id, N, Conditions),
    add_messages_to_bus(Id/N, Actions, Parent).

apply_autonomous_rules(Id, Parent, Module) :-
    (Module : rule(Id, Refract, Conditions, N, Actions)),
    refract_rule(Refract, Id, N, Conditions),
    add_messages_to_bus(Id/N, Actions, Parent).

refract_rule(refracted(Vars), Id, N, _Conditions) :- !,
    convert_precision(Vars, ConvVars), 
    clock(Cycle),
    assert(user:fired(Cycle, Id, N, ConvVars)).
refract_rule(_, _Id, _N, _Conditions).

add_messages_to_bus(Id/N, Actions, Parent) :-
    decompose_message(Actions, Output, Parent, Target),
    add_to_bus(Id/N, Output, Target),
    fail.
add_messages_to_bus(_, _, _).

/*---------------------------------------------------------------------------*/

% decompose_message(Actions, Output, Parent, Target)

% For each RHS message of the form send(Output, Box), this predicate succeeds,
% returning Output and the identifier of the Box. All solutions are generated
% by backtracking through this predicate.

% Messages to processes, sinks, networks, ...:

decompose_message(Actions, Output, _, TargetId) :-
    fast_member(send(Output, TargetId), Actions). %,
%    object(TargetId, _, _, _, _, _).

% special system message to set script variable
decompose_message(Actions, set_dv(Var, Val), _, system) :-
    fast_member(set_dv(Var, Val), Actions). 

% Messages to buffers:

decompose_message(Actions, add(Output), _, TargetId) :-
    fast_member(add(Output, TargetId), Actions). %,
%    object(TargetId, _, _, _, _, _).

decompose_message(Actions, del(Output), _, TargetId) :-
    fast_member(delete(Output, TargetId), Actions). %,
%    object(TargetId, _, _, _, _, _).

decompose_message(Actions, del_all(Output), _, TargetId) :-
    fast_member(delete_all(Output, TargetId), Actions). %,
%    object(TargetId, _, _, _, _, _).

decompose_message(Actions, clear, _, TargetId) :-
    fast_member(clear(TargetId), Actions). %,
%    object(TargetId, _, _, _, _, _).

% Messages to compounds:

% stop messages are sent to the parent of the process that generated them.

decompose_message(Actions, stop, Parent, Parent) :-
    fast_member(stop, Actions).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% dump_state/2 method.

dump_state(_Type, _Id, 'Process').

