   Copyright ( C ) 2008   Pablo Polvorin <  >
%%%
%%%  This program 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.
%%%
%%%  This program 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 this program; if not, write to the Free Software
   Foundation , Inc. , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 , USA .
%%%
%%%  In addition, as a special exception, you have the permission to
%%%  link the code of this program with any library released under
   the EPL license and distribute linked combinations including
   the two ; the MPL ( Mozilla Public License ) , which EPL ( Erlang
%%%  Public License) is based on, is included in this exception.

   ( C ) 2008   Pablo Polvorin <  >
  @author     >
  @author   <  >
  @doc functions to manipulate dynamic variables , sort of   Abstract Data Type
%%% @end

  created on 2008 - 08 - 22
  modified by   : Add merge and multi keys / values ( new , set )

-module(ts_dynvars).

-export([new/0,new/2, merge/2, lookup/2,lookup/3,set/3,entries/1,map/4]).


-define(IS_DYNVARS(X),is_list(X)).

%% @type dynvar() = {Key::atom(), Value::string()} | [].
%% @type dynvars() = [dynvar()]

  @spec new ( ) - > [ dynvar ( ) ]
new() ->
    [].

new(Key, Val) when is_atom(Key)->
    [{Key,Val}];
new(VarNames, Values) when is_list(VarNames),is_list(Values)->
  FIXME : check if VarNames is a list of atoms
    case {length(VarNames), length(Values)} of
        {A,A} ->
            lists:zip(VarNames,Values);
        {A,B} when A > B -> % more names than values, use empty values
            lists:zip(VarNames,Values ++ lists:duplicate(A-B,""));
        {C,D} when C < D -> % more values than names, remove unused values
            lists:zip(VarNames,lists:sublist(Values, C))
    end.

  @spec lookup(Key::atom ( ) ,  ( ) ) - > { ok , Value::term ( ) } | false
lookup(_Key, []) ->
    false;
lookup(Key, DynVars) when ?IS_DYNVARS(DynVars), is_atom(Key)->
    case lists:keysearch(Key,1,DynVars) of
        {value,{Key,Value}} -> {ok,Value};
        false -> false
    end;
lookup({Key, Index}, DynVars) when ?IS_DYNVARS(DynVars), is_atom(Key), is_integer(Index)->
    case lists:keysearch(Key,1,DynVars) of
        {value,{Key,Value}} -> {ok,lists:nth(Index,Value)};
        false -> false
    end.


%% @doc same as lookup/2, only that if the key isn't present, the default
%%      value is returned instead of returning false.
lookup(Key, DynVars, Default) when ?IS_DYNVARS(DynVars), is_atom(Key)->
    case lookup(Key, DynVars) of
        false -> {ok,Default};
        R -> R
    end.

    ( ) , Value::term ( ) , DynVars::dynvars ( ) ) - >  ( )
set(Key,Value,DynVars) when ?IS_DYNVARS(DynVars),is_atom(Key) ->
    merge([{Key, Value}],DynVars);

  optimization : only one key and one value
set([Key],[Value],DynVars) ->
    set(Key,Value,DynVars);
set([Key],Value,DynVars) -> %% for backward compatibility
    set(Key,Value,DynVars);
%% general case: list of keys and values
set(Keys,Values,DynVars) when ?IS_DYNVARS(DynVars),is_list(Keys),is_list(Values) ->
    merge(new(Keys,Values),DynVars).

entries(DynVars) when ?IS_DYNVARS(DynVars) ->
    DynVars.

  @spec map(Fun::function(),Key::atom(),Default::term(),DynVars::dynvars ( ) )
              - >  ( )
  @doc The value associated to key Key is replaced with
%%      the result of applying function Fun to its previous value.
%%      If there is no such previous value, Fun is applied to the default
%%      value Default.
%%      map(fun(I) -> I +1 end,b,0,[{a,5}]) => [{a,5},{b,1}]
%%      map(fun(I) -> I +1 end,b,0,[{a,1}]) => [{a,5},{b,2}]
map(Fun,Key,Default,DynVars) when ?IS_DYNVARS(DynVars),is_atom(Key),is_function(Fun,1) ->
    do_map(Fun,Key,Default,DynVars,[]).

do_map(Fun,Key,Default,[],Acc) ->
    [{Key,Fun(Default)}| Acc];

do_map(Fun,Key,_Default,[{Key,Value}|Rest],Acc) ->
    lists:append([[{Key,Fun(Value)}], Rest, Acc]);

do_map(Fun,Key,Default,[H|Rest], Acc) ->
    do_map(Fun,Key,Default,Rest, [H|Acc]).


   merge(DynVars::dynvars(),DynVars::dynvars ( ) ) - >  ( )
  @doc merge two set of dynamic variables
merge(DynVars1, DynVars2) when ?IS_DYNVARS(DynVars1),?IS_DYNVARS(DynVars2) ->
    ts_utils:keyumerge(1,DynVars1,DynVars2);
merge(_, DynVars2) -> DynVars2.



  This program is free software; you can redistribute it and/or modify
  (at your option) any later version.

  This program 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.

  along with this program; if not, write to the Free Software

  In addition, as a special exception, you have the permission to
  link the code of this program with any library released under
  Public License) is based on, is included in this exception.
 @end
 @type dynvar() = {Key::atom(), Value::string()} | [].
 @type dynvars() = [dynvar()]
 more names than values, use empty values
 more values than names, remove unused values
 @doc same as lookup/2, only that if the key isn't present, the default
      value is returned instead of returning false.
 for backward compatibility
 general case: list of keys and values
      the result of applying function Fun to its previous value.
      If there is no such previous value, Fun is applied to the default
      value Default.
      map(fun(I) -> I +1 end,b,0,[{a,5}]) => [{a,5},{b,1}]
      map(fun(I) -> I +1 end,b,0,[{a,1}]) => [{a,5},{b,2}]
   Copyright ( C ) 2008   Pablo Polvorin <  >
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation ; either version 2 of the License , or
   You should have received a copy of the GNU General Public License
   Foundation , Inc. , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 , USA .
   the EPL license and distribute linked combinations including
   the two ; the MPL ( Mozilla Public License ) , which EPL ( Erlang

   ( C ) 2008   Pablo Polvorin <  >
  @author     >
  @author   <  >
  @doc functions to manipulate dynamic variables , sort of   Abstract Data Type

  created on 2008 - 08 - 22
  modified by   : Add merge and multi keys / values ( new , set )

-module(ts_dynvars).

-export([new/0,new/2, merge/2, lookup/2,lookup/3,set/3,entries/1,map/4]).


-define(IS_DYNVARS(X),is_list(X)).


  @spec new ( ) - > [ dynvar ( ) ]
new() ->
    [].

new(Key, Val) when is_atom(Key)->
    [{Key,Val}];
new(VarNames, Values) when is_list(VarNames),is_list(Values)->
  FIXME : check if VarNames is a list of atoms
    case {length(VarNames), length(Values)} of
        {A,A} ->
            lists:zip(VarNames,Values);
            lists:zip(VarNames,Values ++ lists:duplicate(A-B,""));
            lists:zip(VarNames,lists:sublist(Values, C))
    end.

  @spec lookup(Key::atom ( ) ,  ( ) ) - > { ok , Value::term ( ) } | false
lookup(_Key, []) ->
    false;
lookup(Key, DynVars) when ?IS_DYNVARS(DynVars), is_atom(Key)->
    case lists:keysearch(Key,1,DynVars) of
        {value,{Key,Value}} -> {ok,Value};
        false -> false
    end;
lookup({Key, Index}, DynVars) when ?IS_DYNVARS(DynVars), is_atom(Key), is_integer(Index)->
    case lists:keysearch(Key,1,DynVars) of
        {value,{Key,Value}} -> {ok,lists:nth(Index,Value)};
        false -> false
    end.


lookup(Key, DynVars, Default) when ?IS_DYNVARS(DynVars), is_atom(Key)->
    case lookup(Key, DynVars) of
        false -> {ok,Default};
        R -> R
    end.

    ( ) , Value::term ( ) , DynVars::dynvars ( ) ) - >  ( )
set(Key,Value,DynVars) when ?IS_DYNVARS(DynVars),is_atom(Key) ->
    merge([{Key, Value}],DynVars);

  optimization : only one key and one value
set([Key],[Value],DynVars) ->
    set(Key,Value,DynVars);
    set(Key,Value,DynVars);
set(Keys,Values,DynVars) when ?IS_DYNVARS(DynVars),is_list(Keys),is_list(Values) ->
    merge(new(Keys,Values),DynVars).

entries(DynVars) when ?IS_DYNVARS(DynVars) ->
    DynVars.

  @spec map(Fun::function(),Key::atom(),Default::term(),DynVars::dynvars ( ) )
              - >  ( )
  @doc The value associated to key Key is replaced with
map(Fun,Key,Default,DynVars) when ?IS_DYNVARS(DynVars),is_atom(Key),is_function(Fun,1) ->
    do_map(Fun,Key,Default,DynVars,[]).

do_map(Fun,Key,Default,[],Acc) ->
    [{Key,Fun(Default)}| Acc];

do_map(Fun,Key,_Default,[{Key,Value}|Rest],Acc) ->
    lists:append([[{Key,Fun(Value)}], Rest, Acc]);

do_map(Fun,Key,Default,[H|Rest], Acc) ->
    do_map(Fun,Key,Default,Rest, [H|Acc]).


   merge(DynVars::dynvars(),DynVars::dynvars ( ) ) - >  ( )
  @doc merge two set of dynamic variables
merge(DynVars1, DynVars2) when ?IS_DYNVARS(DynVars1),?IS_DYNVARS(DynVars2) ->
    ts_utils:keyumerge(1,DynVars1,DynVars2);
merge(_, DynVars2) -> DynVars2.

