-module(stm).

-export([new/1, add/2, add/3, read/1, write/2, pull/2, push/2, example/0, example_buffer/0]). 

%% new(Tm) -> Pid
%%   create the transaction memory store (TM)
%%
%% add(Tm, Name) -> Var
%%   add a uninitialized variable to the TM store
%%
%% add(Tm, Name, Init) -> Var 
%%   add a variable to the TM store which is initialized with Init
%%   If it already exists, it returns the variable
%%
%% read(Var) -> Value 
%%   return the value of the TM variable
%%
%% write(Var, Value) -> ok 
%%   write Value into TM variable Var  
%%
%% pull(Tm, Name) -> Var 
%%   lookup variable V in the TM store
%%
%% push(Tm, Var) -> Bool
%%   update variable Var in the TM store


new(Tm) -> register(Tm, genserver:start(dict:new(), fun loop/2)).


add(Tm, Name) ->
    genserver:request(Tm, {add, Name, void}). 

add(Tm, Name, Init) ->
    genserver:request(Tm, {add, Name, Init}). 

read({_Name, _Version, Value}) -> Value. 

write({Name, Version, _Value}, NewValue) ->
    {Name, Version, NewValue}.

pull(Tm, Var) -> 
    genserver:request(Tm, {pull, Var}).

push(Tm, L) ->
    genserver:request(Tm, {push, L}).  
             
%% TM store logic!
loop(Vars, {add, Name, Init}) ->
    case dict:find(Name, Vars) of
         {ok, _} -> {reply, ok, Vars} ;
         error   -> {reply, ok, dict:store(Name, {0,Init}, Vars)}
    end ;

loop(Vars, {pull, Name}) ->
    case dict:find(Name, Vars) of 
         {ok, {Ver, Value}} -> {reply, {Name, Ver, Value}, Vars} ;
         _                  -> {reply, error, Vars}
    end ;

loop(Vars, {push, L}) ->
    case update (Vars, L) of
      {true, NewVars} -> {reply, true, NewVars};
      false           -> {reply, false, Vars}
    end.

update(Vars, [{Name, Ver, Value}|L]) ->
    {VerTM, _} = dict:fetch(Name, Vars),
    case Ver of 
         VerTM -> update(dict:store(Name, {Ver+1, Value}, Vars), L) ;
         _     -> false
    end;
update(Vars, []) -> {true, Vars}.
                           
%% Example

example() ->
    catch(unregister(tm)),
    new(tm),
    add(tm, x, 42),
    add(tm, v, 70),    
    spawn(fun () -> transaction1(tm) end),
    spawn(fun () -> transaction2(tm) end),
    timer:sleep(500),    
    io:format("Final value of x:~p~n", [read(pull(tm,x))]),
    tm ! stop.

transaction1(Tm) ->
    X    = pull(Tm, x),
    I    = read(X),
    timer:sleep(150),  
    NewX = write(X, I + 1),
    case push(Tm, [NewX]) of 
        true  -> io:format("Transaction 1 succeeded!~n",[]) ;
        false -> io:format("Transaction 1 failed!~n",[])
    end.
             

transaction2(Tm) ->
    X = pull(Tm, x),
    V = pull(Tm, v),
    timer:sleep(200),
    I = read(V),
    J = read(X),
    NewX = write(X, I+J),
    case push(Tm, [NewX]) of 
        true  -> io:format("Transaction 2 succeeded!~n",[]) ;
        false -> io:format("Transaction 2 failed!~n",[]) 
    end.

%% Example with the buffer 
get_buff(Tm, Name) -> 
   Buff = pull(Tm, Name),
   [X | Xs] = read(Buff),
   NewBuff  = write(Buff, Xs),  
   case push(Tm, [NewBuff]) of 
        true -> X ;
        false -> get_buff(Tm, Name) 
   end.

put_buff(Tm, Name, X) ->
    Buff = pull(Tm, Name), 
    Xs      = read(Buff),
    NewBuff = write(Buff, Xs ++ [X]),
    case push(Tm, [NewBuff]) of 
         true  -> ok ;
         false -> put_buff(Tm, Name, X)
    end.

get_put_buff(Tm, Name1, Name2) ->
    Elem  = get_buff(Tm, Name1),
    put_buff(Tm, Name2, Elem).


example_buffer() ->
    catch(unregister(tm)),
    new(tm),
    add(tm, buff1, [1,2]),
    add(tm, buff2, []),

    io:format("Buffer 1 = ~p~n", [read(pull(tm, buff1))]),
    io:format("Buffer 2 = ~p~n", [read(pull(tm, buff2))]),

    get_put_buff(tm, buff1, buff2), 

    io:format("Buffer 1 = ~p~n", [read(pull(tm, buff1))]),
    io:format("Buffer 2 = ~p~n", [read(pull(tm, buff2))]),

    tm ! stop.