-module(rlx_overlay).

-export([render/2,
         format_error/1]).

-export([generate_overlay_vars/2,
         render_string/2]).

-define(DIRECTORY_RE, ".*(\/|\\\\)$").

-include("relx.hrl").
-include("rlx_log.hrl").

render(Release, State) ->
    case generate_overlay_vars(State, Release) of
        {error, Reason} ->
            erlang:error(?RLX_ERROR(Reason));
        OverlayVars ->
            Files = rlx_util:template_files(),
            case do_overlay(State, Release, Files, OverlayVars) of
                {error, _} = E -> erlang:error(E);
                {ok, State1} -> {ok, State1}
            end
    end.

-spec format_error(ErrorDetail::term()) -> iolist().
format_error({unresolved_release, RelName, RelVsn}) ->
    io_lib:format("The release has not been resolved ~p-~s", [RelName, RelVsn]);
format_error({unable_to_read_varsfile, FileName, Reason}) ->
    io_lib:format("Unable to read vars file (~s) for overlay due to: ~p",
                  [FileName, Reason]);
format_error({read_template, FileName, Reason}) ->
    io_lib:format("Unable to read template file (~s) for overlay due to: ~s",
                  [FileName, file:format_error(Reason)]);
format_error({overlay_failed, Errors}) ->
    [[format_error(rlx_util:error_reason(Error)), "\n"] || Error <- Errors];
format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) ->
    io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s",
                  [AppDir, TargetDir, rlx_util:indent(2),
                   file:format_error(Reason)]);
format_error({copy_failed, FromFile, ToFile, Err}) ->
    io_lib:format("Unable to copy from ~s to ~s because of ~p",
                  [FromFile, ToFile, Err]);
format_error({unable_to_write, ToFile, Reason}) ->
    io_lib:format("Unable to write to ~s because ~p",
                  [ToFile, Reason]);
format_error({unable_to_enclosing_dir, ToFile, Reason}) ->
    io_lib:format("Unable to create enclosing directory for ~s because ~p",
                  [ToFile, Reason]);
format_error({unable_to_render_template, FromFile, Reason}) ->
    io_lib:format("Unable to render template ~s because ~p",
                  [FromFile, Reason]);
format_error({unable_to_compile_template, FromFile, Reason}) ->
    io_lib:format("Unable to compile template ~s because \n~s",
                  [FromFile, [format_errors(F, Es) || {F, Es} <- Reason]]);
format_error({unable_to_make_dir, Absolute, Error}) ->
    io_lib:format("Unable to make directory ~s because ~p",
                  [Absolute, Error]);
format_error({unable_to_chmod, Mode, File, Err}) ->
    io_lib:format("Unable to chmod ~p ~s because of ~p",
                  [Mode, File, Err]);
format_error({link_failed, FromFile, ToFile, Err}) ->
    io_lib:format("Unable to symlink ~s to ~s because of ~p",
                  [FromFile, ToFile, Err]);
format_error({malformed_overlay, Invalid}) ->
    io_lib:format("Overlay section malformed: ~p", [Invalid]).

%%%===================================================================
%%% Internal Functions
%%%===================================================================

format_errors(File, [{none, Mod, E}|Es]) ->
    [io_lib:format("~s~s: ~ts~n",
                   [rlx_util:indent(2), File,
                    Mod:format_error(E)])
     |format_errors(File, Es)];
format_errors(File, [{{Line, Col}, Mod, E}|Es]) ->
    [io_lib:format("~s~s:~w:~w: ~ts~n",
                   [rlx_util:indent(2), File, Line, Col,
                    Mod:format_error(E)])
     |format_errors(File, Es)];
format_errors(File, [{Line, Mod, E}|Es]) ->
    [io_lib:format("~s~s:~w: ~ts~n",
                   [rlx_util:indent(2), File, Line,
                    Mod:format_error(E)])
     |format_errors(File, Es)];
format_errors(_, []) -> [].


-spec generate_overlay_vars(rlx_state:t(), rlx_release:t()) ->
                                   proplists:proplist() | relx:error().
generate_overlay_vars(State, Release) ->
    StateVars = generate_state_vars(Release, State),
    ReleaseVars = generate_release_vars(Release),
    get_overlay_vars_from_file(State, StateVars ++ ReleaseVars).

-spec get_overlay_vars_from_file(rlx_state:t(), proplists:proplist()) ->
                                        proplists:proplist() | relx:error().
get_overlay_vars_from_file(State, OverlayVars) ->
    case rlx_state:overlay_vars(State) of
        [] ->
            OverlayVars;
        [H | _]=FileNames when is_list(H) ;
                               is_tuple(H) ->
            read_overlay_vars(State, OverlayVars, FileNames);
        FileName when is_list(FileName) ->
            read_overlay_vars(State, OverlayVars, [FileName])
    end.

-spec read_overlay_vars(rlx_state:t(), proplists:proplist(), [file:name()]) ->
                               proplists:proplist() | relx:error().
read_overlay_vars(State, OverlayVars, FileNames) ->
    OverlayVarsValues = rlx_state:overlay_vars_values(State),
    Terms = merge_overlay_vars(State, FileNames),
    case render_overlay_vars(OverlayVars ++ OverlayVarsValues, Terms, []) of
        {ok, NewTerms} ->
            % We place `OverlayVarsValues' at the end on purpose; their
            % definitions should be able to be overwritten by both internal
            % and rendered vars, as not to change behaviour in
            % setups preceding the support for overlays from the caller.
            OverlayVars ++ NewTerms ++ OverlayVarsValues;
        Error ->
            Error
    end.

-spec check_overlay_inclusion(rlx_state:t(), string(), proplists:proplist()) ->
                              proplists:proplist().
check_overlay_inclusion(State, RelativeRoot, Terms) ->
    check_overlay_inclusion(State, RelativeRoot, Terms, []).

-spec check_overlay_inclusion(rlx_state:t(), string(), proplists:proplist(), proplists:proplist()) ->
                              proplists:proplist().
check_overlay_inclusion(State, RelativeRoot, [File|T], Terms) when is_list(File) ->
    %% merge terms from the file into the current terms of the system
    %% passing the Terms ensures duplicates delete the existing pair
    %% while keeping the order of terms the same, as is done in `merge_overlay_vars'
    IncludedTerms = merge_overlay_vars(State, Terms, [filename:join(RelativeRoot, File)]),
    check_overlay_inclusion(State, RelativeRoot, T, IncludedTerms);
check_overlay_inclusion(State, RelativeRoot, [Tuple|T], Terms) ->
    check_overlay_inclusion(State, RelativeRoot, T, Terms ++ [Tuple]);
check_overlay_inclusion(_State, _RelativeRoot, [], Terms) ->
    Terms.

-spec merge_overlay_vars(rlx_state:t(), [file:name()]) ->
                                proplists:proplist().
merge_overlay_vars(State, FileNames) ->
    merge_overlay_vars(State, [], FileNames).

merge_overlay_vars(State, Terms0, FileNames) ->
    RelativeRoot = get_relative_root(State),
    lists:foldl(fun(FileName, Acc) when is_list(FileName) ->
                        RelativePath = filename:join(RelativeRoot, iolist_to_binary(FileName)),
                        case file:consult(RelativePath) of
                            {ok, Terms} ->
                                %% the location of the included overlay files will be relative
                                %% to the current one being read
                                OverlayRelativeRoot = filename:dirname(FileName),
                                NewTerms = check_overlay_inclusion(State, OverlayRelativeRoot, Terms),
  Remove already defined variables from Acc ,
  append NewTerms , preserving order
                                lists:foldl(fun(NewTerm, A) ->
                                                    lists:keydelete(element(1, NewTerm), 1, A)
                                            end, Acc, NewTerms) ++ NewTerms;
                            {error, Reason} ->
                                ?log_warn(format_error({unable_to_read_varsfile, FileName, Reason})),
                                Acc
                        end;
                   (Var, Acc) ->
                        lists:keystore(element(1, Var), 1, Acc, Var)
                end, Terms0, FileNames).

-spec render_overlay_vars(proplists:proplist(), proplists:proplist(),
                          proplists:proplist()) ->
                                 {ok, proplists:proplist()} | relx:error().
render_overlay_vars(OverlayVars, [{Key, Value} | Rest], Acc)
  when erlang:is_list(Value) ->
    case io_lib:printable_list(Value) of
        true ->
            case render_template(Acc ++ OverlayVars, erlang:iolist_to_binary(Value)) of
                {ok, Data} ->
                    %% Adding to the end sucks, but ordering needs to be retained
                    render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, Data}]);
                Error ->
                    Error
            end;
        false ->
            case render_overlay_vars(Acc ++ OverlayVars, Value, []) of
                {ok, NewValue} ->
                    render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, NewValue}]);
                Error ->
                    Error
            end
    end;
render_overlay_vars(OverlayVars, [{Key, Value} | Rest], Acc)
  when erlang:is_binary(Value) ->
    case render_template(Acc ++ OverlayVars, erlang:iolist_to_binary(Value)) of
        {ok, Data} ->
            render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, erlang:iolist_to_binary(Data)}]);
        Error ->
            Error
    end;
render_overlay_vars(OverlayVars, [KeyValue | Rest], Acc) ->
    render_overlay_vars(OverlayVars, Rest, Acc ++ [KeyValue]);
render_overlay_vars(_OverlayVars, [], Acc) ->
    {ok, Acc}.

-spec generate_release_vars(rlx_release:t()) -> proplists:proplist().
generate_release_vars(Release) ->
    [{erts_vsn, rlx_release:erts(Release)},
     {erts_dir, code:root_dir()},
     {release_erts_version, rlx_release:erts(Release)},
     {release_name, rlx_release:name(Release)},
     {release_version, rlx_release:vsn(Release)},
     {release, rlx_release:canonical_name(Release)}].

-spec generate_state_vars(rlx_release:t(), rlx_state:t()) -> proplists:proplist().
generate_state_vars(Release, State) ->
    [{output_dir, filename:join(rlx_state:base_output_dir(State),
                                rlx_release:name(Release))},
     {target_dir, filename:join(rlx_state:base_output_dir(State),
                                rlx_release:name(Release))},
     {overridden, [AppName || {AppName, _} <- rlx_state:overrides(State)]},
     {overrides, rlx_state:overrides(State)},
     {lib_dirs, rlx_state:lib_dirs(State)},
     {vm_args, rlx_state:vm_args(State)},
     {sys_config, rlx_state:sys_config(State)},
     {root_dir, rlx_state:root_dir(State)}
     %% include a var for each application. makes it easier to copy a file from an application
     | [{rlx_app_info:name(AppInfo), rlx_app_info:dir(AppInfo)}
        || AppInfo <- rlx_release:applications(Release)]].

-spec do_overlay(rlx_state:t(), rlx_release:t(), list(), proplists:proplist()) ->
                                   {ok, rlx_state:t()} | relx:error().
do_overlay(State, Release, Files, OverlayVars) ->
    case rlx_state:overlay(State) of
        [] ->
            {ok, State};
        Overlays ->
            handle_errors(State,
                          lists:map(fun(Overlay) ->
                                            do_individual_overlay(State,
                                                                  Release,
                                                                  Files,
                                                                  OverlayVars,
                                                                  Overlay)
                                    end, Overlays))
    end.

-spec handle_errors(rlx_state:t(), [ok | relx:error()]) ->
                           {ok, rlx_state:t()} | relx:error().
handle_errors(State, Result) ->
    case [Error || Error <- Result,
                   rlx_util:is_error(Error)] of
        Errors = [_|_] ->
            ?RLX_ERROR({overlay_failed, Errors});
        [] ->
            {ok, State}
    end.

-spec do_individual_overlay(rlx_state:t(), rlx_release:t(), list(), proplists:proplist(),
                            OverlayDirective::term()) ->
                                   {ok, rlx_state:t()} | relx:error().
do_individual_overlay(State, Release, _Files, OverlayVars, {chmod, Mode, Path}) ->
    % mode can be specified directly as an integer value, or if it is
    % not an integer we assume it's a template, which we render and convert
    % blindly to an integer.  So this will crash with an exception if for
    % some reason something other than an integer is used
    NewMode =
        case is_integer(Mode) of
            true ->
                Mode;
            false ->
                erlang:binary_to_integer(render_string(OverlayVars, Mode))
        end,

    file_render_do(OverlayVars, Path,
                   fun(NewPath) ->
                            Absolute = absolute_path_to(State, Release, NewPath),
                            case file:change_mode(Absolute, NewMode) of
                                {error, Error} ->
                                    ?RLX_ERROR({unable_to_chmod, NewMode, NewPath, Error});
                                ok -> ok
                            end
                   end);
do_individual_overlay(State, Release, _Files, OverlayVars, {mkdir, Dir}) ->
    file_render_do(OverlayVars, Dir,
                   fun(Dir0) ->
                       Absolute = absolute_path_to(State, Release, Dir0),
                       case rlx_file_utils:mkdir_p(Absolute) of
                           {error, Error} ->
                               ?RLX_ERROR({unable_to_make_dir, Absolute, Error});
                           ok ->
                               ok
                       end
                   end);
do_individual_overlay(State, Release, _Files, OverlayVars, {copy, From, To}) ->
    file_render_do(OverlayVars, From,
                   fun(FromFile) ->
                           file_render_do(OverlayVars, To,
                                          fun(ToFile) ->
                                                  copy_to(State, Release, FromFile, ToFile)
                                          end)
                   end);
do_individual_overlay(State, Release, Files, OverlayVars, {link, From, To}) ->
    case rlx_state:mode(State) of
        dev  ->
            file_render_do(OverlayVars, From,
                           fun(FromFile) ->
                                   file_render_do(OverlayVars, To,
                                                  fun(ToFile) ->
                                                          link_to(State, Release, FromFile, ToFile)
                                                  end)
                           end);
        _ ->
            do_individual_overlay(State, Release, Files, OverlayVars, {copy, From, To})
    end;
do_individual_overlay(State, Release, _Files, OverlayVars, {template, From, To}) ->
    file_render_do(OverlayVars, From,
                   fun(FromFile) ->
                           file_render_do(OverlayVars, To,
                                          fun(ToFile) ->
                                                  write_template(OverlayVars,
                                                                 absolute_path_from(State, FromFile),
                                                                 absolute_path_to(State, Release, ToFile))
                                          end)
                   end);
do_individual_overlay(_State, _Release, _Files, _OverlayVars, Invalid) ->
    ?RLX_ERROR({malformed_overlay, Invalid}).

-spec wildcard_copy(rlx_state:t(), rlx_release:t(), file:filename_all(), file:filename_all(),
      fun((file:filename_all(), file:filename_all()) -> ok | {error, term()}),
      ErrorTag :: atom()) -> ok | relx:error().
wildcard_copy(State, Release, FromFile0, ToFile0, CopyFun, ErrorTag) ->
    FromFile1 = absolute_path_from(State, FromFile0),
    ToFile1 = absolute_path_to(State, Release, ToFile0),

    Res = case is_directory(ToFile0) of
              false ->
                  filelib:ensure_dir(ToFile1),
                  CopyFun(FromFile1, ToFile1);
              true ->
                  Root = absolute_path_from(State, "."),
                  FromFiles = case is_list(FromFile0) of
                                  true ->
                                      filelib:wildcard(FromFile0, Root);
                                  false ->
                                      [FromFile1]
                              end,
                  rlx_file_utils:mkdir_p(ToFile1),
                  lists:foldl(fun(_, {error, _} = Error) -> Error;
                                 (FromFile, ok) ->
                                      CopyFun(filename:join(Root, FromFile),
                                              filename:join(ToFile1, filename:basename(FromFile)))
                              end, ok, FromFiles)
          end,
                  
    case Res of
        ok ->
            ok;
        {error, Err} ->
            ?RLX_ERROR({ErrorTag,
                        FromFile1,
                        ToFile1, Err})
    end.

-spec copy_to(rlx_state:t(), rlx_release:t(), file:name(), file:name()) -> ok | relx:error().
copy_to(State, Release, FromFile0, ToFile0) ->
    wildcard_copy(State, Release, FromFile0, ToFile0,
                  fun(FromPath, ToPath) ->
                          rlx_file_utils:copy(FromPath, ToPath, [recursive, {file_info, [mode, time]}]) end,
                  copy_failed).

-spec link_to(rlx_state:t(), rlx_release:t(), file:name(), file:name()) -> ok | relx:error().
link_to(State, Release, FromFile0, ToFile0) ->
    wildcard_copy(State, Release, FromFile0, ToFile0,
                  fun make_link/2,
                  link_failed).

make_link(FromFile, ToFile) ->
    case rlx_file_utils:is_symlink(ToFile) of
        true -> file:delete(ToFile);
        false -> rlx_file_utils:remove(ToFile, [recursive])
    end,
    file:make_symlink(FromFile, ToFile).

get_relative_root(State) ->
    rlx_state:root_dir(State).

absolute_path_from(State, Path) ->
    absolutize(State, filename:join(get_relative_root(State), Path)).

absolute_path_to(State, Release, Path) ->
    absolutize(State, filename:join([rlx_state:base_output_dir(State),
                                     rlx_release:name(Release),
                                     Path])).

-spec is_directory(file:name()) -> boolean().
is_directory(ToFile0) ->
    case re:run(ToFile0, ?DIRECTORY_RE) of
        nomatch ->
            false;
        _ ->
            true
    end.

-spec render_template(proplists:proplist(), binary()) ->
                             {ok, binary()} | relx:error().
render_template(OverlayVars, Data) ->
    case rlx_util:render(Data, OverlayVars) of
        {ok, IoData} ->
            {ok, IoData};
        {error, Reason} ->
            ?RLX_ERROR({unable_to_render_template, Data, Reason})
    end.

-spec write_template(proplists:proplist(), file:name(), file:name()) ->
                            ok | relx:error().
write_template(OverlayVars, FromFile, ToFile) ->
    case file:read_file(FromFile) of
        {ok, File} ->
            case render_template(OverlayVars, File) of
                {ok, IoData} ->
                    case filelib:ensure_dir(ToFile) of
                        ok ->
                            %% we were asked to render a template
                            %% onto a symlink, this would cause an overwrite
                            %% of the original file, so we delete the symlink
                            %% and go ahead with the template render
                            case rlx_file_utils:is_symlink(ToFile) of
                                true -> rlx_file_utils:remove(ToFile);
                                false -> ok
                            end,
                            case file:write_file(ToFile, IoData) of
                                ok ->
                                    ok = rlx_file_utils:copy_file_info(ToFile, FromFile, [mode, time]),
                                    ok;
                                {error, Reason} ->
                                    ?RLX_ERROR({unable_to_write, ToFile, Reason})
                            end;
                        {error, Reason} ->
                            ?RLX_ERROR({unable_to_enclosing_dir, ToFile, Reason})
                    end;
                Error ->
                    Error
            end;
        {error, Error} ->
            ?RLX_ERROR({read_template, FromFile, Error})
    end.

-spec render_string(proplists:proplist(), iolist()) ->
                      binary() | relx:error().
render_string(OverlayVars, Data) ->
    case rlx_util:render(Data, OverlayVars) of
        {ok, Bin} -> Bin;
        {error, Error} ->
            ?RLX_ERROR({render_failed, Data, Error})
    end.

-spec file_render_do(proplists:proplist(), iolist(),
                     fun((string() | binary()) -> {ok, rlx_state:t()} | relx:error())) ->
                            {ok, rlx_state:t()} | relx:error().
file_render_do(OverlayVars, File, NextAction) ->
    case rlx_util:render(File, OverlayVars) of
        {ok, Binary} when is_binary(File) ->
            NextAction(Binary);
        {ok, Binary} when is_list(File) ->
            NextAction(binary_to_list(Binary));
        {error, Error} ->
            ?RLX_ERROR({render_failed, File, Error})
    end.

absolutize(State, FileName) ->
    filename:absname(filename:join(rlx_state:root_dir(State), FileName)).

===================================================================
 Internal Functions
===================================================================
 We place `OverlayVarsValues' at the end on purpose; their
 definitions should be able to be overwritten by both internal
 and rendered vars, as not to change behaviour in
 setups preceding the support for overlays from the caller.
 merge terms from the file into the current terms of the system
 passing the Terms ensures duplicates delete the existing pair
 while keeping the order of terms the same, as is done in `merge_overlay_vars'
 the location of the included overlay files will be relative
 to the current one being read
 Adding to the end sucks, but ordering needs to be retained
 include a var for each application. makes it easier to copy a file from an application
 mode can be specified directly as an integer value, or if it is
 not an integer we assume it's a template, which we render and convert
 blindly to an integer.  So this will crash with an exception if for
 some reason something other than an integer is used
 we were asked to render a template
 onto a symlink, this would cause an overwrite
 of the original file, so we delete the symlink
 and go ahead with the template render
-module(rlx_overlay).

-export([render/2,
         format_error/1]).

-export([generate_overlay_vars/2,
         render_string/2]).

-define(DIRECTORY_RE, ".*(\/|\\\\)$").

-include("relx.hrl").
-include("rlx_log.hrl").

render(Release, State) ->
    case generate_overlay_vars(State, Release) of
        {error, Reason} ->
            erlang:error(?RLX_ERROR(Reason));
        OverlayVars ->
            Files = rlx_util:template_files(),
            case do_overlay(State, Release, Files, OverlayVars) of
                {error, _} = E -> erlang:error(E);
                {ok, State1} -> {ok, State1}
            end
    end.

-spec format_error(ErrorDetail::term()) -> iolist().
format_error({unresolved_release, RelName, RelVsn}) ->
    io_lib:format("The release has not been resolved ~p-~s", [RelName, RelVsn]);
format_error({unable_to_read_varsfile, FileName, Reason}) ->
    io_lib:format("Unable to read vars file (~s) for overlay due to: ~p",
                  [FileName, Reason]);
format_error({read_template, FileName, Reason}) ->
    io_lib:format("Unable to read template file (~s) for overlay due to: ~s",
                  [FileName, file:format_error(Reason)]);
format_error({overlay_failed, Errors}) ->
    [[format_error(rlx_util:error_reason(Error)), "\n"] || Error <- Errors];
format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) ->
    io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s",
                  [AppDir, TargetDir, rlx_util:indent(2),
                   file:format_error(Reason)]);
format_error({copy_failed, FromFile, ToFile, Err}) ->
    io_lib:format("Unable to copy from ~s to ~s because of ~p",
                  [FromFile, ToFile, Err]);
format_error({unable_to_write, ToFile, Reason}) ->
    io_lib:format("Unable to write to ~s because ~p",
                  [ToFile, Reason]);
format_error({unable_to_enclosing_dir, ToFile, Reason}) ->
    io_lib:format("Unable to create enclosing directory for ~s because ~p",
                  [ToFile, Reason]);
format_error({unable_to_render_template, FromFile, Reason}) ->
    io_lib:format("Unable to render template ~s because ~p",
                  [FromFile, Reason]);
format_error({unable_to_compile_template, FromFile, Reason}) ->
    io_lib:format("Unable to compile template ~s because \n~s",
                  [FromFile, [format_errors(F, Es) || {F, Es} <- Reason]]);
format_error({unable_to_make_dir, Absolute, Error}) ->
    io_lib:format("Unable to make directory ~s because ~p",
                  [Absolute, Error]);
format_error({unable_to_chmod, Mode, File, Err}) ->
    io_lib:format("Unable to chmod ~p ~s because of ~p",
                  [Mode, File, Err]);
format_error({link_failed, FromFile, ToFile, Err}) ->
    io_lib:format("Unable to symlink ~s to ~s because of ~p",
                  [FromFile, ToFile, Err]);
format_error({malformed_overlay, Invalid}) ->
    io_lib:format("Overlay section malformed: ~p", [Invalid]).


format_errors(File, [{none, Mod, E}|Es]) ->
    [io_lib:format("~s~s: ~ts~n",
                   [rlx_util:indent(2), File,
                    Mod:format_error(E)])
     |format_errors(File, Es)];
format_errors(File, [{{Line, Col}, Mod, E}|Es]) ->
    [io_lib:format("~s~s:~w:~w: ~ts~n",
                   [rlx_util:indent(2), File, Line, Col,
                    Mod:format_error(E)])
     |format_errors(File, Es)];
format_errors(File, [{Line, Mod, E}|Es]) ->
    [io_lib:format("~s~s:~w: ~ts~n",
                   [rlx_util:indent(2), File, Line,
                    Mod:format_error(E)])
     |format_errors(File, Es)];
format_errors(_, []) -> [].


-spec generate_overlay_vars(rlx_state:t(), rlx_release:t()) ->
                                   proplists:proplist() | relx:error().
generate_overlay_vars(State, Release) ->
    StateVars = generate_state_vars(Release, State),
    ReleaseVars = generate_release_vars(Release),
    get_overlay_vars_from_file(State, StateVars ++ ReleaseVars).

-spec get_overlay_vars_from_file(rlx_state:t(), proplists:proplist()) ->
                                        proplists:proplist() | relx:error().
get_overlay_vars_from_file(State, OverlayVars) ->
    case rlx_state:overlay_vars(State) of
        [] ->
            OverlayVars;
        [H | _]=FileNames when is_list(H) ;
                               is_tuple(H) ->
            read_overlay_vars(State, OverlayVars, FileNames);
        FileName when is_list(FileName) ->
            read_overlay_vars(State, OverlayVars, [FileName])
    end.

-spec read_overlay_vars(rlx_state:t(), proplists:proplist(), [file:name()]) ->
                               proplists:proplist() | relx:error().
read_overlay_vars(State, OverlayVars, FileNames) ->
    OverlayVarsValues = rlx_state:overlay_vars_values(State),
    Terms = merge_overlay_vars(State, FileNames),
    case render_overlay_vars(OverlayVars ++ OverlayVarsValues, Terms, []) of
        {ok, NewTerms} ->
            OverlayVars ++ NewTerms ++ OverlayVarsValues;
        Error ->
            Error
    end.

-spec check_overlay_inclusion(rlx_state:t(), string(), proplists:proplist()) ->
                              proplists:proplist().
check_overlay_inclusion(State, RelativeRoot, Terms) ->
    check_overlay_inclusion(State, RelativeRoot, Terms, []).

-spec check_overlay_inclusion(rlx_state:t(), string(), proplists:proplist(), proplists:proplist()) ->
                              proplists:proplist().
check_overlay_inclusion(State, RelativeRoot, [File|T], Terms) when is_list(File) ->
    IncludedTerms = merge_overlay_vars(State, Terms, [filename:join(RelativeRoot, File)]),
    check_overlay_inclusion(State, RelativeRoot, T, IncludedTerms);
check_overlay_inclusion(State, RelativeRoot, [Tuple|T], Terms) ->
    check_overlay_inclusion(State, RelativeRoot, T, Terms ++ [Tuple]);
check_overlay_inclusion(_State, _RelativeRoot, [], Terms) ->
    Terms.

-spec merge_overlay_vars(rlx_state:t(), [file:name()]) ->
                                proplists:proplist().
merge_overlay_vars(State, FileNames) ->
    merge_overlay_vars(State, [], FileNames).

merge_overlay_vars(State, Terms0, FileNames) ->
    RelativeRoot = get_relative_root(State),
    lists:foldl(fun(FileName, Acc) when is_list(FileName) ->
                        RelativePath = filename:join(RelativeRoot, iolist_to_binary(FileName)),
                        case file:consult(RelativePath) of
                            {ok, Terms} ->
                                OverlayRelativeRoot = filename:dirname(FileName),
                                NewTerms = check_overlay_inclusion(State, OverlayRelativeRoot, Terms),
  Remove already defined variables from Acc ,
  append NewTerms , preserving order
                                lists:foldl(fun(NewTerm, A) ->
                                                    lists:keydelete(element(1, NewTerm), 1, A)
                                            end, Acc, NewTerms) ++ NewTerms;
                            {error, Reason} ->
                                ?log_warn(format_error({unable_to_read_varsfile, FileName, Reason})),
                                Acc
                        end;
                   (Var, Acc) ->
                        lists:keystore(element(1, Var), 1, Acc, Var)
                end, Terms0, FileNames).

-spec render_overlay_vars(proplists:proplist(), proplists:proplist(),
                          proplists:proplist()) ->
                                 {ok, proplists:proplist()} | relx:error().
render_overlay_vars(OverlayVars, [{Key, Value} | Rest], Acc)
  when erlang:is_list(Value) ->
    case io_lib:printable_list(Value) of
        true ->
            case render_template(Acc ++ OverlayVars, erlang:iolist_to_binary(Value)) of
                {ok, Data} ->
                    render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, Data}]);
                Error ->
                    Error
            end;
        false ->
            case render_overlay_vars(Acc ++ OverlayVars, Value, []) of
                {ok, NewValue} ->
                    render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, NewValue}]);
                Error ->
                    Error
            end
    end;
render_overlay_vars(OverlayVars, [{Key, Value} | Rest], Acc)
  when erlang:is_binary(Value) ->
    case render_template(Acc ++ OverlayVars, erlang:iolist_to_binary(Value)) of
        {ok, Data} ->
            render_overlay_vars(OverlayVars, Rest, Acc ++ [{Key, erlang:iolist_to_binary(Data)}]);
        Error ->
            Error
    end;
render_overlay_vars(OverlayVars, [KeyValue | Rest], Acc) ->
    render_overlay_vars(OverlayVars, Rest, Acc ++ [KeyValue]);
render_overlay_vars(_OverlayVars, [], Acc) ->
    {ok, Acc}.

-spec generate_release_vars(rlx_release:t()) -> proplists:proplist().
generate_release_vars(Release) ->
    [{erts_vsn, rlx_release:erts(Release)},
     {erts_dir, code:root_dir()},
     {release_erts_version, rlx_release:erts(Release)},
     {release_name, rlx_release:name(Release)},
     {release_version, rlx_release:vsn(Release)},
     {release, rlx_release:canonical_name(Release)}].

-spec generate_state_vars(rlx_release:t(), rlx_state:t()) -> proplists:proplist().
generate_state_vars(Release, State) ->
    [{output_dir, filename:join(rlx_state:base_output_dir(State),
                                rlx_release:name(Release))},
     {target_dir, filename:join(rlx_state:base_output_dir(State),
                                rlx_release:name(Release))},
     {overridden, [AppName || {AppName, _} <- rlx_state:overrides(State)]},
     {overrides, rlx_state:overrides(State)},
     {lib_dirs, rlx_state:lib_dirs(State)},
     {vm_args, rlx_state:vm_args(State)},
     {sys_config, rlx_state:sys_config(State)},
     {root_dir, rlx_state:root_dir(State)}
     | [{rlx_app_info:name(AppInfo), rlx_app_info:dir(AppInfo)}
        || AppInfo <- rlx_release:applications(Release)]].

-spec do_overlay(rlx_state:t(), rlx_release:t(), list(), proplists:proplist()) ->
                                   {ok, rlx_state:t()} | relx:error().
do_overlay(State, Release, Files, OverlayVars) ->
    case rlx_state:overlay(State) of
        [] ->
            {ok, State};
        Overlays ->
            handle_errors(State,
                          lists:map(fun(Overlay) ->
                                            do_individual_overlay(State,
                                                                  Release,
                                                                  Files,
                                                                  OverlayVars,
                                                                  Overlay)
                                    end, Overlays))
    end.

-spec handle_errors(rlx_state:t(), [ok | relx:error()]) ->
                           {ok, rlx_state:t()} | relx:error().
handle_errors(State, Result) ->
    case [Error || Error <- Result,
                   rlx_util:is_error(Error)] of
        Errors = [_|_] ->
            ?RLX_ERROR({overlay_failed, Errors});
        [] ->
            {ok, State}
    end.

-spec do_individual_overlay(rlx_state:t(), rlx_release:t(), list(), proplists:proplist(),
                            OverlayDirective::term()) ->
                                   {ok, rlx_state:t()} | relx:error().
do_individual_overlay(State, Release, _Files, OverlayVars, {chmod, Mode, Path}) ->
    NewMode =
        case is_integer(Mode) of
            true ->
                Mode;
            false ->
                erlang:binary_to_integer(render_string(OverlayVars, Mode))
        end,

    file_render_do(OverlayVars, Path,
                   fun(NewPath) ->
                            Absolute = absolute_path_to(State, Release, NewPath),
                            case file:change_mode(Absolute, NewMode) of
                                {error, Error} ->
                                    ?RLX_ERROR({unable_to_chmod, NewMode, NewPath, Error});
                                ok -> ok
                            end
                   end);
do_individual_overlay(State, Release, _Files, OverlayVars, {mkdir, Dir}) ->
    file_render_do(OverlayVars, Dir,
                   fun(Dir0) ->
                       Absolute = absolute_path_to(State, Release, Dir0),
                       case rlx_file_utils:mkdir_p(Absolute) of
                           {error, Error} ->
                               ?RLX_ERROR({unable_to_make_dir, Absolute, Error});
                           ok ->
                               ok
                       end
                   end);
do_individual_overlay(State, Release, _Files, OverlayVars, {copy, From, To}) ->
    file_render_do(OverlayVars, From,
                   fun(FromFile) ->
                           file_render_do(OverlayVars, To,
                                          fun(ToFile) ->
                                                  copy_to(State, Release, FromFile, ToFile)
                                          end)
                   end);
do_individual_overlay(State, Release, Files, OverlayVars, {link, From, To}) ->
    case rlx_state:mode(State) of
        dev  ->
            file_render_do(OverlayVars, From,
                           fun(FromFile) ->
                                   file_render_do(OverlayVars, To,
                                                  fun(ToFile) ->
                                                          link_to(State, Release, FromFile, ToFile)
                                                  end)
                           end);
        _ ->
            do_individual_overlay(State, Release, Files, OverlayVars, {copy, From, To})
    end;
do_individual_overlay(State, Release, _Files, OverlayVars, {template, From, To}) ->
    file_render_do(OverlayVars, From,
                   fun(FromFile) ->
                           file_render_do(OverlayVars, To,
                                          fun(ToFile) ->
                                                  write_template(OverlayVars,
                                                                 absolute_path_from(State, FromFile),
                                                                 absolute_path_to(State, Release, ToFile))
                                          end)
                   end);
do_individual_overlay(_State, _Release, _Files, _OverlayVars, Invalid) ->
    ?RLX_ERROR({malformed_overlay, Invalid}).

-spec wildcard_copy(rlx_state:t(), rlx_release:t(), file:filename_all(), file:filename_all(),
      fun((file:filename_all(), file:filename_all()) -> ok | {error, term()}),
      ErrorTag :: atom()) -> ok | relx:error().
wildcard_copy(State, Release, FromFile0, ToFile0, CopyFun, ErrorTag) ->
    FromFile1 = absolute_path_from(State, FromFile0),
    ToFile1 = absolute_path_to(State, Release, ToFile0),

    Res = case is_directory(ToFile0) of
              false ->
                  filelib:ensure_dir(ToFile1),
                  CopyFun(FromFile1, ToFile1);
              true ->
                  Root = absolute_path_from(State, "."),
                  FromFiles = case is_list(FromFile0) of
                                  true ->
                                      filelib:wildcard(FromFile0, Root);
                                  false ->
                                      [FromFile1]
                              end,
                  rlx_file_utils:mkdir_p(ToFile1),
                  lists:foldl(fun(_, {error, _} = Error) -> Error;
                                 (FromFile, ok) ->
                                      CopyFun(filename:join(Root, FromFile),
                                              filename:join(ToFile1, filename:basename(FromFile)))
                              end, ok, FromFiles)
          end,
                  
    case Res of
        ok ->
            ok;
        {error, Err} ->
            ?RLX_ERROR({ErrorTag,
                        FromFile1,
                        ToFile1, Err})
    end.

-spec copy_to(rlx_state:t(), rlx_release:t(), file:name(), file:name()) -> ok | relx:error().
copy_to(State, Release, FromFile0, ToFile0) ->
    wildcard_copy(State, Release, FromFile0, ToFile0,
                  fun(FromPath, ToPath) ->
                          rlx_file_utils:copy(FromPath, ToPath, [recursive, {file_info, [mode, time]}]) end,
                  copy_failed).

-spec link_to(rlx_state:t(), rlx_release:t(), file:name(), file:name()) -> ok | relx:error().
link_to(State, Release, FromFile0, ToFile0) ->
    wildcard_copy(State, Release, FromFile0, ToFile0,
                  fun make_link/2,
                  link_failed).

make_link(FromFile, ToFile) ->
    case rlx_file_utils:is_symlink(ToFile) of
        true -> file:delete(ToFile);
        false -> rlx_file_utils:remove(ToFile, [recursive])
    end,
    file:make_symlink(FromFile, ToFile).

get_relative_root(State) ->
    rlx_state:root_dir(State).

absolute_path_from(State, Path) ->
    absolutize(State, filename:join(get_relative_root(State), Path)).

absolute_path_to(State, Release, Path) ->
    absolutize(State, filename:join([rlx_state:base_output_dir(State),
                                     rlx_release:name(Release),
                                     Path])).

-spec is_directory(file:name()) -> boolean().
is_directory(ToFile0) ->
    case re:run(ToFile0, ?DIRECTORY_RE) of
        nomatch ->
            false;
        _ ->
            true
    end.

-spec render_template(proplists:proplist(), binary()) ->
                             {ok, binary()} | relx:error().
render_template(OverlayVars, Data) ->
    case rlx_util:render(Data, OverlayVars) of
        {ok, IoData} ->
            {ok, IoData};
        {error, Reason} ->
            ?RLX_ERROR({unable_to_render_template, Data, Reason})
    end.

-spec write_template(proplists:proplist(), file:name(), file:name()) ->
                            ok | relx:error().
write_template(OverlayVars, FromFile, ToFile) ->
    case file:read_file(FromFile) of
        {ok, File} ->
            case render_template(OverlayVars, File) of
                {ok, IoData} ->
                    case filelib:ensure_dir(ToFile) of
                        ok ->
                            case rlx_file_utils:is_symlink(ToFile) of
                                true -> rlx_file_utils:remove(ToFile);
                                false -> ok
                            end,
                            case file:write_file(ToFile, IoData) of
                                ok ->
                                    ok = rlx_file_utils:copy_file_info(ToFile, FromFile, [mode, time]),
                                    ok;
                                {error, Reason} ->
                                    ?RLX_ERROR({unable_to_write, ToFile, Reason})
                            end;
                        {error, Reason} ->
                            ?RLX_ERROR({unable_to_enclosing_dir, ToFile, Reason})
                    end;
                Error ->
                    Error
            end;
        {error, Error} ->
            ?RLX_ERROR({read_template, FromFile, Error})
    end.

-spec render_string(proplists:proplist(), iolist()) ->
                      binary() | relx:error().
render_string(OverlayVars, Data) ->
    case rlx_util:render(Data, OverlayVars) of
        {ok, Bin} -> Bin;
        {error, Error} ->
            ?RLX_ERROR({render_failed, Data, Error})
    end.

-spec file_render_do(proplists:proplist(), iolist(),
                     fun((string() | binary()) -> {ok, rlx_state:t()} | relx:error())) ->
                            {ok, rlx_state:t()} | relx:error().
file_render_do(OverlayVars, File, NextAction) ->
    case rlx_util:render(File, OverlayVars) of
        {ok, Binary} when is_binary(File) ->
            NextAction(Binary);
        {ok, Binary} when is_list(File) ->
            NextAction(binary_to_list(Binary));
        {error, Error} ->
            ?RLX_ERROR({render_failed, File, Error})
    end.

absolutize(State, FileName) ->
    filename:absname(filename:join(rlx_state:root_dir(State), FileName)).
