%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  This file is part of Logtalk <https://logtalk.org/>
%  Copyright 1998-2021 Paulo Moura <pmoura@logtalk.org>
%  SPDX-License-Identifier: Apache-2.0
%
%  Licensed under the Apache License, Version 2.0 (the "License");
%  you may not use this file except in compliance with the License.
%  You may obtain a copy of the License at
%
%      http://www.apache.org/licenses/LICENSE-2.0
%
%  Unless required by applicable law or agreed to in writing, software
%  distributed under the License is distributed on an "AS IS" BASIS,
%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%  See the License for the specific language governing permissions and
%  limitations under the License.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


:- protocol(dictionaryp).

	:- info([
		version is 2:3:0,
		author is 'Paulo Moura',
		date is 2021-04-12,
		comment is 'Dictionary protocol.',
		see_also is [avltree, bintree, rbtree]
	]).

	:- public(as_dictionary/2).
	:- mode(as_dictionary(@list(pairs), -dictionary), one).
	:- info(as_dictionary/2, [
		comment is 'Converts a list of key-value pairs to a dictionary.',
		argnames is ['Pairs', 'Dictionary']
	]).

	:- public(as_list/2).
	:- mode(as_list(@dictionary, -list(pairs)), one).
	:- info(as_list/2, [
		comment is 'Converts a dictionary to an ordered list (as per standard order) of key-value pairs.',
		argnames is ['Dictionary', 'Pairs']
	]).

	:- public(as_curly_bracketed/2).
	:- mode(as_curly_bracketed(+dictionary, --term), one).
	:- info(as_curly_bracketed/2, [
		comment is 'Creates a curly-bracketed term representation of a dictionary.',
		argnames is ['Dictionary', 'Term']
	]).

	:- public(clone/3).
	:- mode(clone(+dictionary, -dictionary, -list(pairs)), one).
	:- info(clone/3, [
		comment is 'Clones a dictionary using the same keys but with all values unbound and returning a list of all the pairs in the new clone.',
		argnames is ['Dictionary', 'Clone', 'ClonePairs']
	]).

	:- public(clone/4).
	:- mode(clone(+dictionary, -list(pairs), -dictionary, -list(pairs)), one).
	:- info(clone/4, [
		comment is 'Clones a dictionary using the same keys but with all values unbound and returning the list of all pairs in the dictionary and in the clone.',
		argnames is ['Dictionary', 'Pairs', 'Clone', 'ClonePairs']
	]).

	:- public(insert/4).
	:- mode(insert(+dictionary, +ground, @term, -dictionary), one).
	:- info(insert/4, [
		comment is 'Inserts a key-value pair into a dictionary, returning the updated dictionary. When the key already exists, the associated value is updated.',
		argnames is ['OldDictionary', 'Key', 'Value', 'NewDictionary']
	]).

	:- public(delete/4).
	:- mode(delete(+dictionary, @ground, ?term, -dictionary), zero_or_one).
	:- info(delete/4, [
		comment is 'Deletes a matching key-value pair from a dictionary, returning the updated dictionary.',
		argnames is ['OldDictionary', 'Key', 'Value', 'NewDictionary']
	]).

	:- public(update/4).
	:- mode(update(+dictionary, @ground, +term, -dictionary), zero_or_one).
	:- info(update/4, [
		comment is 'Updates the value associated with Key in a dictionary, returning the updated dictionary. Fails if it cannot find the key.',
		argnames is ['OldDictionary', 'Key', 'NewValue', 'NewDictionary']
	]).

	:- public(update/5).
	:- mode(update(+dictionary, @ground, ?term, +term, -dictionary), zero_or_one).
	:- info(update/5, [
		comment is 'Updates the value associated with a key in a dictionary, returning the updated dictionary. Fails if it cannot find the key or if the existing value does not match the old value.',
		argnames is ['OldDictionary', 'Key', 'OldValue', 'NewValue', 'NewDictionary']
	]).

	:- public(update/3).
	:- mode(update(+dictionary, @list(pair), -dictionary), zero_or_one).
	:- info(update/3, [
		comment is 'Updates the key-value pairs in a dictionary, returning the updated dictionary. Fails if it cannot find one of the keys.',
		argnames is ['OldDictionary', 'Pairs', 'NewDictionary']
	]).

	:- public(empty/1).
	:- mode(empty(@dictionary), zero_or_one).
	:- info(empty/1, [
		comment is 'True iff the dictionary is empty.',
		argnames is ['Dictionary']
	]).

	:- public(lookup/3).
	:- mode(lookup(+ground, ?term, @dictionary), zero_or_one).
	:- mode(lookup(-ground, ?term, @dictionary), zero_or_more).
	:- info(lookup/3, [
		comment is 'Lookups a matching key-value pair from a dictionary.',
		argnames is ['Key', 'Value', 'Dictionary']
	]).

	:- public(lookup/2).
	:- mode(lookup(+list(pair), @dictionary), zero_or_one).
	:- info(lookup/2, [
		comment is 'Lookups all matching key-value pairs from a dictionary. Fails if it cannot find one of the keys or if a value for a key does not unify.',
		argnames is ['Pairs', 'Dictionary']
	]).

	:- public(intersection/2).
	:- mode(intersection(+dictionary, +dictionary), zero_or_one).
	:- info(intersection/2, [
		comment is 'True iff the values of the dictionaries common keys unify. Trivially true when there are no common keys.',
		argnames is ['Dictionary1', 'Dictionary2']
	]).

	:- public(intersection/3).
	:- mode(intersection(+dictionary, +dictionary, -dictionary), zero_or_one).
	:- info(intersection/3, [
		comment is 'Returns the (possibly empty) intersection between two dictionaries when the values of their common keys unify.',
		argnames is ['Dictionary1', 'Dictionary2', 'Intersection']
	]).

	:- public(previous/4).
	:- mode(previous(+dictionary, +key, -key, -value), zero_or_one).
	:- info(previous/4, [
		comment is 'Returns the previous pair in a dictionary given a key.',
		argnames is ['Dictionary', 'Key', 'Previous', 'Value']
	]).

	:- public(next/4).
	:- mode(next(+dictionary, +key, -key, -value), zero_or_one).
	:- info(next/4, [
		comment is 'Returns the next pair in a dictionary given a key.',
		argnames is ['Dictionary', 'Key', 'Next', 'Value']
	]).

	:- public(min/3).
	:- mode(min(+dictionary, -key, -value), zero_or_one).
	:- info(min/3, [
		comment is 'Returns the pair with the minimum key (as per standard order) in a dictionary. Fails if the dictionary is empty.',
		argnames is ['Dictionary', 'Key', 'Value']
	]).

	:- public(max/3).
	:- mode(max(+dictionary, -key, -value), zero_or_one).
	:- info(max/3, [
		comment is 'Returns the pair with the maximum key (as per standard order) in a dictionary. Fails if the dictionary is empty.',
		argnames is ['Dictionary', 'Key', 'Value']
	]).

	:- public(delete_min/4).
	:- mode(delete_min(+dictionary, -key, -value, -dictionary), zero_or_one).
	:- info(delete_min/4, [
		comment is 'Deletes the pair with the minimum key (as per standard order) from a dictionary, returning the deleted pair and the updated dictionary.',
		argnames is ['OldDictionary', 'Key', 'Value', 'NewDictionary']
	]).

	:- public(delete_max/4).
	:- mode(delete_max(+dictionary, -key, -value, -dictionary), zero_or_one).
	:- info(delete_max/4, [
		comment is 'Deletes the pair with the maximum key (as per standard order) from a dictionary, returning the deleted pair and the updated dictionary.',
		argnames is ['OldDictionary', 'Key', 'Value', 'NewDictionary']
	]).

	:- public(keys/2).
	:- mode(keys(@dictionary, -list), one).
	:- info(keys/2, [
		comment is 'Returns a list with all the dictionary keys in ascending order (as per standard order).',
		argnames is ['Dictionary', 'Keys']
	]).

	:- public(values/2).
	:- mode(values(@dictionary, -list), one).
	:- info(values/2, [
		comment is 'Returns a list with all the dictionary values in ascending order of the keys (as per standard order).',
		argnames is ['Dictionary', 'Values']
	]).

	:- public(map/2).
	:- meta_predicate(map(1, *)).
	:- mode(map(@callable, +dictionary), zero_or_more).
	:- info(map/2, [
		comment is 'Maps a closure over each dictionary key-value pair. Fails if the mapped closure attempts to modify the keys.',
		argnames is ['Closure', 'Dictionary']
	]).

	:- public(map/3).
	:- meta_predicate(map(2, *, *)).
	:- mode(map(@callable, +dictionary, -dictionary), zero_or_more).
	:- info(map/3, [
		comment is 'Maps a closure over each dictionary key-value pair, returning the new dictionary. Fails if the mapped closure attempts to modify the keys.',
		argnames is ['Closure', 'OldDictionary', 'NewDictionary']
	]).

	:- public(apply/4).
	:- meta_predicate(apply(2, *, *, *)).
	:- mode(apply(+callable, +dictionary, +key, -dictionary), zero_or_one).
	:- info(apply/4, [
		comment is 'Applies a closure to a specific key-value pair, returning the new dictionary. Fails if the key cannot be found or if the mapped closure attempts to modify the key.',
		argnames is ['Closure', 'OldDictionary', 'Key', 'NewDictionary']
	]).

	:- public(size/2).
	:- mode(size(@dictionary, ?integer), zero_or_one).
	:- info(size/2, [
		comment is 'Number of dictionary entries.',
		argnames is ['Dictionary', 'Size']
	]).

:- end_protocol.
