%% Convenience functions used to manipulate scope and its variables.
-module(elixir_erl_var).
-export([translate/4, assign/2, build/2,
  load_binding/3, dump_binding/3
]).
-include("elixir.hrl").

%% VAR HANDLING

translate(Meta, '_', _Kind, S) ->
  {{var, ?ann(Meta), '_'}, S};

translate(Meta, Name, Kind, #elixir_erl{var_names=VarNames} = S) ->
  {version, Version} = lists:keyfind(version, 1, Meta),

  case VarNames of
    #{Version :=  ErlName} -> {{var, ?ann(Meta), ErlName}, S};
    #{} when Kind /= nil -> assign(Meta, '_', Version, S);
    #{} -> assign(Meta, Name, Version, S)
  end.

assign(Meta, #elixir_erl{var_names=VarNames} = S) ->
  Version = -(map_size(VarNames)+1),
  ExVar = {var, [{version, Version} | Meta], ?var_context},
  {ErlVar, SV} = assign(Meta, '_', Version, S),
  {ExVar, ErlVar, SV}.

assign(Meta, Name, Version, #elixir_erl{var_names=VarNames} = S) ->
  {NewVar, NS} = build(Name, S),
  NewVarNames = VarNames#{Version => NewVar},
  {{var, ?ann(Meta), NewVar}, NS#elixir_erl{var_names=NewVarNames}}.

build(Key, #elixir_erl{counter=Counter} = S) ->
  Count =
    case Counter of
      #{Key := Val} -> Val + 1;
      _ -> 1
    end,
  {build_name(Key, Count),
   S#elixir_erl{counter=Counter#{Key => Count}}}.

build_name('_', Count) -> list_to_atom("_@" ++ integer_to_list(Count));
build_name(Name, Count) -> list_to_atom("_" ++ atom_to_list(Name) ++ "@" ++ integer_to_list(Count)).

%% BINDINGS

load_binding(Binding, #{current_vars := {ExVars, _}}, #elixir_erl{var_names=ErlVars}) ->
  lists:foldl(fun({Key, Value}, Acc) ->
    Version = maps:get(Key, ExVars),
    Name = maps:get(Version, ErlVars),
    orddict:store(Name, Value, Acc)
  end, [], Binding).

dump_binding(Binding, #{current_vars := {ExVars, _}}, #elixir_erl{var_names=ErlVars}) ->
  maps:fold(fun
    ({Var, Kind} = Pair, Version, Acc) when is_atom(Kind) ->
      Key = case Kind of
        nil -> Var;
        _ -> Pair
      end,

      ErlName = maps:get(Version, ErlVars),

      Value = case orddict:find(ErlName, Binding) of
        {ok, V} -> V;
        error -> nil
      end,

      orddict:store(Key, Value, Acc);
    (_, _, Acc) ->
      Acc
  end, [], ExVars).
