/*	This example illustrates how to associate a set of predicates with a 
	compound term.   Parameters can be accessed from within an object by 
	using the execution-context built-in methods this/1 and parameter/2; 
	both alternatives are illustrated below.
*/



/*	The first parametric object defines some useful predicates for working  
	with lists.
*/


% dealing with non-empty lists is easy:

:- object(.(_, _)).			% note that the [X, Y, ...] notation
 							% is just syntactic sugar for ./2
	:- public(last/1).
	:- mode(last(?term), zero_or_one).

	:- public(member/1).
	:- mode(member(?term), zero_or_more).

	:- public(nextto/2).
	:- mode(nextto(?term, ?term), zero_or_more).

	last(Last) :-
		this([Head| Tail]),
		last(Tail, Head, Last).

	last([], Last, Last).
	last([Head| Tail], _, Last) :-
		last(Tail, Head, Last).

	member(Element) :-
		this(List),
		member(Element, List).

	member(Element, [Element| _]).
	member(Element, [_| Tail]) :-
		member(Element, Tail).

	nextto(X, Y) :-
		this([Head| Tail]),
		nextto(X, Y, [Head| Tail]).

	nextto(X, Y, [X, Y| _]).
	nextto(X, Y, [_| Tail]) :-
		nextto(X, Y, Tail).

:- end_object.


% dealing with empty lists must also be done but it's a bit tricky:

:- object([],				% the empty list is an atom, not a compound term, 
	extends([.(_, _)])).	% so the "extends" relation would be always wrong

	last(_) :-				% the trick is to redefine all inherited predicates
		fail.				% to do the right thing for empty lists

	member(_) :-
		fail.

	nextto(_, _) :-
		fail.

:- end_object.



/*	The next two parametric objects represent time and date values as 
	compound terms using the object's identifiers.
*/


:- object(date(_Year, _Month, _Day)).

	:- info([
		version is 1.1,
		author is 'Paulo Moura',
		date is 2005/9/5,
		comment is 'Dates as parametric objects.',
		parnames is ['Year', 'Month', 'Day']]).

	:- public(year/1).
	:- mode(year(?integer), one).

	:- public(month/1).
	:- mode(month(?integer), one).

	:- public(day/1).
	:- mode(day(?integer), one).

	:- public(today/0).
	:- mode(today, one).

	:- public(leap_year/0).
	:- mode(leap_year, zero_or_one).

	year(Year) :-
		parameter(1, Year).

	month(Month) :-
		parameter(2, Month).

	day(Day) :-
		parameter(3, Day).

	today :-
		{'$lgt_current_date'(Year, Month, Day)},	% defined in the config files
		parameter(1, Year),
		parameter(2, Month),
		parameter(3, Day).

/*	Alternative predicate definitions using this/1 instead of parameter/2
	(see the User Manual for the pros and cons of both alternatives):

	year(Year) :-
		this(date(Year, _, _)).

	month(Month) :-
		this(date(_, Month, _)).

	day(Day) :-
		this(date(_, _, Day)).

	today :-
		{'$lgt_current_date'(Year, Month, Day)},	% defined in the config files
		this(date(Year, Month, Day)).

*/

	leap_year :-
		parameter(1, Year),
		(0 =:= mod(Year, 4), 0 =\= mod(Year, 100)
		 ;
		 0 =:= mod(Year, 400)),
		!.

:- end_object.


:- object(time(_Hours, _Mins, _Secs)).

	:- info([
		version is 1.1,
		author is 'Paulo Moura',
		date is 2005/9/5,
		comment is 'Time as parametric objects.',
		parnames is ['Hours', 'Mins', 'Secs']]).

	:- public(hours/1).
	:- mode(hours(?integer), one).

	:- public(mins/1).
	:- mode(mins(?integer), one).

	:- public(secs/1).
	:- mode(secs(?integer), one).

	:- public(now/0).
	:- mode(now, one).

	hours(Hours) :-
		parameter(1, Hours).

	mins(Mins) :-
		parameter(2, Mins).

	secs(Secs) :-
		parameter(3, Secs).

	now :-
		{'$lgt_current_time'(Hours, Mins, Secs)},	% defined in the config files
		parameter(1, Hours),
		parameter(2, Mins),
		parameter(3, Secs).

/*	Alternative predicate definitions using this/1 instead of parameter/2
	(see the User Manual for the pros and cons of both alternatives):

	hours(Hours) :-
		this(time(Hours, _, _)).

	mins(Mins) :-
		this(time(_, Mins, _)).

	secs(Secs) :-
		this(time(_, _, Secs)).

	now :-
		{'$lgt_current_time'(Hours, Mins, Secs)},	% defined in the config files
		this(time(Hours, Mins, Secs)).

*/

:- end_object.



/*	The following parametric object illustrates a solution for implementing 
	backtracable object state. The idea is to represent object state by using 
	object parameters, defining "setter" predicates/methods that return the 
	updated object identifier.
*/

:- object(rectangle(_Width, _Height, _X, _Y)).

	:- info([
		version is 1.0,
		author is 'Paulo Moura',
		date is 2005/9/5,
		comment is 'A simple implementation of a geometric rectangle using parametric objects.',
		parnames is ['Width', 'Height', 'X', 'Y']]).

	:- public(init/0).
	:- mode(init, one).
	:- info(init/0,
		[comment is 'Initialize rectangle position.']).

	:- public(area/1).
	:- mode(area(-integer), one).
	:- info(area/1,
		[comment is 'Rectangle area.',
		 argnames is ['Area']]).

	:- public(move/3).
	:- mode(move(+integer, +integer, -compound), one).
	:- info(move/3, [
		comment is 'Moves a rectangle to a new position, returning the updated rectangle.',
		argnames is ['X', 'Y', 'NewRectangle']]).

	:- public(position/2).
	:- mode(position(?integer, ?integer), zero_or_one).
	:- info(position/2, [
		comment is 'Rectangle current position.',
		argnames is ['X', 'Y']]).

	init :-
		parameter(1, 2),	% Width
		parameter(2, 1),	% Height
		parameter(3, 0),	% X
		parameter(4, 0).	% Y

	area(Area) :-
		parameter(1, Width),
		parameter(2, Height),
		Area is Width*Height.

	move(X, Y, rectangle(Width, Height, X, Y)) :-
		parameter(1, Width),
		parameter(2, Height).

	position(X, Y) :-
		parameter(3, X),
		parameter(4, Y).

/*	Alternative predicate definitions using this/1 instead of parameter/2
	(see the User Manual for the pros and cons of both alternatives):

	init :-
		this(rectangle(2, 1, 0, 0)).

	area(Area) :-
		this(rectangle(Width, Height, _, _)),
		Area is Width*Height.

	move(X, Y, rectangle(Width, Height, X, Y)) :-
		this(rectangle(Width, Height, _, _)).

	position(X, Y) :-
		this(rectangle(_, _, X, Y)).

*/

:- end_object.



/*	The following parametric objects show a solution for dealing with inheritance when
	defining "setter" predicates/methods that return updated object identifiers.
*/

:- object(person(_Name, _Age)).

	:- info([
		version is 1.0,
		author is 'Paulo Moura',
		date is 2007/6/19,
		comment is 'A simple representation for people using parametric objects.',
		parnames is ['Name', 'Age']]).

	:- public(grow_older/1).
	:- mode(grow_older(-object_identifier), one).
	:- info(grow_older/1,
		[comment is 'Increments the person''s age, returning the updated object identifier.',
		 argnames is ['NewId']]).

	grow_older(NewId) :-
		::age(OldAge, NewAge, NewId),
		NewAge is OldAge + 1.

	:- protected(age/3).
	:- mode(age(?integer, ?integer, -object_identifier), zero_or_one).
	:- info(age/3,
		[comment is 'Rectangle area.',
		 argnames is ['OldAge', 'NewAge', 'NewId']]).

	age(OldAge, NewAge, person(Name, NewAge)) :-	% this rule is compiled into a fact due to
		this(person(Name, OldAge)).					% compilation of the this/1 call inline

:- end_object.


:- object(employee(Name, Age, _Salary),
	extends(person(Name, Age))).

	:- info([
		version is 1.0,
		author is 'Paulo Moura',
		date is 2007/6/19,
		comment is 'A simple representation for employees using parametric objects.',
		parnames is ['Name', 'Age', 'Salary']]).

	:- public(give_raise/2).
	:- mode(give_raise(+integer, -object_identifier), one).
	:- info(give_raise/2,
		[comment is 'Gives a raise to the employee, returning the updated object identifier.',
		 argnames is ['Amount', 'NewId']]).

	give_raise(Amount, NewId) :-
		::salary(OldSalary, NewSalary, NewId),
		NewSalary is OldSalary + Amount.

	:- protected(salary/3).
	:- mode(salary(?integer, ?integer, -object_identifier), zero_or_one).
	:- info(salary/3,
		[comment is 'Rectangle area.',
		 argnames is ['OldSalary', 'NewSalary', 'NewId']]).

	salary(OldSalary, NewSalary, employee(Name, Age, NewSalary)) :-
		this(employee(Name, Age, OldSalary)).

	age(OldAge, NewAge, employee(Salary, Name, NewAge)) :-
		this(employee(Salary, Name, OldAge)).

:- end_object.