%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2009-2023. All Rights Reserved.
%%
%% 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.
%%
%% %CopyrightEnd%

-module(reltool_server).
-moduledoc false.

%% Public
-export([
         start_link/0, start_link/1,
         get_config/3, load_config/2, save_config/4,
         get_rel/2, get_script/2,
         reset_config/1, undo_config/1,
         get_mod/2,
         get_app/2, set_app/2,
         get_apps/2, set_apps/2,
         get_sys/1, set_sys/2,
         get_status/1,
         gen_rel_files/2, gen_target/2, gen_spec/1
        ]).

%% Internal
-export([init/1, loop/1]).

%% sys callback functions
-export([
         system_continue/3,
         system_terminate/4,
         system_code_change/4
        ]).

-include("reltool.hrl").

-record(state,
        {options,
         parent_pid,
         common,
         sys,
         old_sys,
         status,
         old_status,
	 app_tab,
	 old_app_tab,
	 mod_tab,
	 old_mod_tab,
	 mod_used_by_tab}).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Client

start_link() ->
    start_link([]).

start_link(Options) ->
    proc_lib:start_link(?MODULE,
			init,
			[[{parent, self()} | Options]],
			infinity,
			[]).

get_config(Pid, InclDef, InclDeriv) ->
    reltool_utils:call(Pid, {get_config, InclDef, InclDeriv}).

load_config(Pid, FilenameOrConfig) ->
    reltool_utils:call(Pid, {load_config, FilenameOrConfig}).

save_config(Pid, Filename, InclDef, InclDeriv) ->
    reltool_utils:call(Pid, {save_config, Filename, InclDef, InclDeriv}).

reset_config(Pid) ->
    reltool_utils:call(Pid, reset_config).

undo_config(Pid) ->
    reltool_utils:call(Pid, undo_config).

get_rel(Pid, RelName) ->
    reltool_utils:call(Pid, {get_rel, RelName}).

get_script(Pid, RelName) ->
    reltool_utils:call(Pid, {get_script, RelName}).

get_mod(Pid, ModName) ->
    reltool_utils:call(Pid, {get_mod, ModName}).

get_app(Pid, AppName) ->
    reltool_utils:call(Pid, {get_app, AppName}).

set_app(Pid, App) ->
    reltool_utils:call(Pid, {set_app, App}).

get_apps(Pid, Kind) ->
    reltool_utils:call(Pid, {get_apps, Kind}).

set_apps(Pid, Apps) ->
    reltool_utils:call(Pid, {set_apps, Apps}).

get_sys(Pid) ->
    reltool_utils:call(Pid, get_sys).

set_sys(Pid, Sys) ->
    reltool_utils:call(Pid, {set_sys, Sys}).

get_status(Pid) ->
    reltool_utils:call(Pid, get_status).

gen_rel_files(Pid, Dir) ->
    reltool_utils:call(Pid, {gen_rel_files, Dir}).

gen_target(Pid, Dir) ->
    reltool_utils:call(Pid, {gen_target, Dir}).

gen_spec(Pid) ->
    reltool_utils:call(Pid, gen_spec).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Server

-spec init(_) -> no_return().
init([{parent,Parent}|_] = Options) ->
    try
        do_init(Options)
    catch
	throw:{error,Reason} ->
	    proc_lib:init_fail(Parent,{error,Reason},{exit,normal});
        error:Reason:Stacktrace ->
            exit({Reason, Stacktrace})
    end.

do_init(Options) ->
    AppTab = ets:new(reltool_apps1, [public, ordered_set, {keypos,#app.name}]),
    OldAppTab = ets:new(reltool_apps2, [public, ordered_set, {keypos,#app.name}]),
    ModTab = ets:new(reltool_mods1, [public, ordered_set, {keypos,#mod.name}]),
    OldModTab = ets:new(reltool_mods2, [public, ordered_set, {keypos,#mod.name}]),
    ModUsesTab = ets:new(reltool_mod_uses, [public, bag, {keypos, 1}]),
    S = #state{options = Options,
	       app_tab = AppTab,
	       old_app_tab = OldAppTab,
	       mod_tab = ModTab,
	       old_mod_tab = OldModTab,
	       mod_used_by_tab = ModUsesTab},

    S2 = parse_options(S),
    {S3, Apps, Status2} = refresh(S2),
    Status3 =  analyse(S3, Apps, Status2),
    %% Set old_xxx equal to xxx to allow undo=nop
    FakeBackup = {ets:tab2list(S3#state.app_tab),ets:tab2list(S3#state.mod_tab)},
    S4 = save_old(S3, S3, FakeBackup, Status3),
    #state{parent_pid = Parent, sys=Sys, common=C} = S4,
    proc_lib:init_ack(Parent, {ok, self(), C, Sys#sys{apps=undefined}}),
    loop(S4).

parse_options(S) ->
    Sys = default_sys(),
    C = #common{sys_debug = [],
		wx_debug = 0,
		trap_exit = true},
    parse_options(S#state.options, S, C, Sys).

default_sys() ->
    #sys{root_dir          = reltool_utils:root_dir(),
	 lib_dirs          = reltool_utils:erl_libs(),
	 escripts          = [],
	 incl_cond         = ?DEFAULT_INCL_COND,
	 mod_cond          = ?DEFAULT_MOD_COND,
	 apps              = ?DEFAULT_APPS,
	 boot_rel          = ?DEFAULT_REL_NAME,
	 rels              = reltool_utils:default_rels(),
	 emu_name          = ?DEFAULT_EMU_NAME,
	 profile           = ?DEFAULT_PROFILE,
	 incl_sys_filters  = dec_re(incl_sys_filters,
				    ?DEFAULT_INCL_SYS_FILTERS,
				    []),
	 excl_sys_filters  = dec_re(excl_sys_filters,
				    ?DEFAULT_EXCL_SYS_FILTERS,
				    []),
	 incl_app_filters  = dec_re(incl_app_filters,
				    ?DEFAULT_INCL_APP_FILTERS,
				    []),
	 excl_app_filters  = dec_re(excl_app_filters,
				    ?DEFAULT_EXCL_APP_FILTERS,
				    []),
	 relocatable       = ?DEFAULT_RELOCATABLE,
	 rel_app_type      = ?DEFAULT_REL_APP_TYPE,
	 embedded_app_type = ?DEFAULT_EMBEDDED_APP_TYPE,
	 app_file          = ?DEFAULT_APP_FILE,
	 debug_info        = ?DEFAULT_DEBUG_INFO}.

dec_re(Key, Regexps, Old) ->
    reltool_utils:decode_regexps(Key, Regexps, Old).

parse_options([{Key, Val} | KeyVals], S, C, Sys) ->
    case Key of
        parent ->
            parse_options(KeyVals, S#state{parent_pid = Val}, C, Sys);
        sys_debug ->
            parse_options(KeyVals, S, C#common{sys_debug = Val}, Sys);
        wx_debug ->
            parse_options(KeyVals, S, C#common{wx_debug = Val}, Sys);
        trap_exit ->
            parse_options(KeyVals, S, C#common{trap_exit = Val}, Sys);
        config ->
            Sys2 = read_config(Sys, Val),
            parse_options(KeyVals, S, C, Sys2);
        sys ->
            Sys2 = read_config(Sys, {sys, Val}),
            parse_options(KeyVals, S, C, Sys2);
        _ ->
	    reltool_utils:throw_error("Illegal option: ~tp", [{Key, Val}])
    end;
parse_options([], S, C, Sys) ->
    S#state{common = C, sys = Sys};
parse_options(KeyVals, _S, _C, _Sys) ->
    reltool_utils:throw_error("Illegal option: ~tp", [KeyVals]).

loop(#state{sys = Sys} = S) ->
    receive
        {system, From, Msg} ->
            sys:handle_system_msg(Msg,
				  From,
				  S#state.parent_pid,
				  ?MODULE,
				  (S#state.common)#common.sys_debug,
				  S);
        {call, ReplyTo, Ref, {get_config, InclDef, InclDeriv}} ->
            Reply = do_get_config(S, InclDef, InclDeriv),
            reltool_utils:reply(ReplyTo, Ref, Reply),
            ?MODULE:loop(S);
        {call, ReplyTo, Ref, {load_config, SysConfig}} ->
	    Fun = fun() -> do_load_config(S, SysConfig) end,
	    {S3, Status2} = config_and_refresh(S, Fun),
            reltool_utils:reply(ReplyTo, Ref, Status2),
            ?MODULE:loop(S3);
        {call, ReplyTo, Ref, {save_config, Filename, InclDef, InclDeriv}} ->
            Reply = do_save_config(S, Filename, InclDef, InclDeriv),
            reltool_utils:reply(ReplyTo, Ref, Reply),
            ?MODULE:loop(S);
        {call, ReplyTo, Ref, reset_config} ->
	    Fun = fun() -> parse_options(S) end,
	    {S3, Status2} = config_and_refresh(S, Fun),
            reltool_utils:reply(ReplyTo, Ref, Status2),
            ?MODULE:loop(S3);
        {call, ReplyTo, Ref, undo_config} ->
            S2 = S#state{sys = S#state.old_sys,
                         old_sys = Sys,
			 status = S#state.old_status,
			 old_status = S#state.status,
			 app_tab = S#state.old_app_tab,
			 old_app_tab = S#state.app_tab,
			 mod_tab = S#state.old_mod_tab,
			 old_mod_tab = S#state.mod_tab},
            reltool_utils:reply(ReplyTo, Ref, ok),
            ?MODULE:loop(S2);
        {call, ReplyTo, Ref, {get_rel, RelName}} ->
            Reply =
                case lists:keysearch(RelName, #rel.name, Sys#sys.rels) of
                    {value, Rel} ->
                        reltool_target:gen_rel(Rel, sys_all_apps(S));
                    false ->
                        {error, "No such release: " ++ RelName}
                end,
            reltool_utils:reply(ReplyTo, Ref, Reply),
            ?MODULE:loop(S);
        {call, ReplyTo, Ref, {get_script, RelName}} ->
            Reply =
                case lists:keysearch(RelName, #rel.name, Sys#sys.rels) of
                    {value, Rel} ->
                        PathFlag = true,
                        Vars = [],
                        reltool_target:gen_script(Rel, sys_all_apps(S),
						  PathFlag, Vars);
                    false ->
                        {error, "No such release: " ++ RelName}
                end,
            reltool_utils:reply(ReplyTo, Ref, Reply),
            ?MODULE:loop(S);
        {call, ReplyTo, Ref, {get_mod, ModName}} ->
            Reply =
                case ets:lookup(S#state.mod_tab, ModName) of
                    [M] ->
                        {ok, M};
                    [] ->
                        {ok, missing_mod(ModName, ?MISSING_APP_NAME)}
                end,
            reltool_utils:reply(ReplyTo, Ref, Reply),
            ?MODULE:loop(S);
        {call, ReplyTo, Ref, {get_app, AppName}} when is_atom(AppName) ->
            Reply =
                case ets:lookup(S#state.app_tab,AppName) of
                    [App] ->
                        {ok, App};
                    [] ->
                        {error, "No such application: " ++
			 atom_to_list(AppName)}
                end,
            reltool_utils:reply(ReplyTo, Ref, Reply),
            ?MODULE:loop(S);
        {call, ReplyTo, Ref, {set_app, App}} ->
	    Fun = fun() -> do_set_apps(S, [App]) end,
	    {S3, Status2} = config_and_refresh(S, Fun),
	    Reply =
		case Status2 of
		    {ok, Warnings} ->
			[App2] = ets:lookup(S3#state.app_tab,App#app.name),
			{ok, App2, Warnings};
		    {error, _} ->
			Status2
		end,
	    reltool_utils:reply(ReplyTo, Ref, Reply),
	    ?MODULE:loop(S3);
        {call, ReplyTo, Ref, {get_apps, Kind}} ->
            AppNames =
                case Kind of
                    whitelist ->
			%% Pre-included
			ets:select(S#state.app_tab,
				   [{#app{is_pre_included=true,_='_'},
				     [],
				     ['$_']}]);
		    blacklist ->
			%% Pre-excluded
			ets:select(S#state.app_tab,
				   [{#app{is_pre_included=false,_='_'},
				     [],
				     ['$_']}]);
		    source ->
			%% Not included and not pre-excluded
			ets:select(S#state.app_tab,
				   [{#app{is_included='$1',
					  is_pre_included='$2',
					  _='_'},
				     [{'=/=','$1',true},
				      {'=/=','$2',false}],
				     ['$_']}]);
                    derived ->
			%% Included, but not pre-included
			ets:select(S#state.app_tab,
				   [{#app{is_included='$1',
					  is_pre_included='$2',
					  _='_'},
				     [{'=:=','$1',true},
				      {'=/=','$2',true}],
				     ['$_']}])
                end,
            reltool_utils:reply(ReplyTo, Ref, {ok, AppNames}),
            ?MODULE:loop(S);
        {call, ReplyTo, Ref, {set_apps, Apps}} ->
	    Fun = fun() -> do_set_apps(S, Apps) end,
	    {S3, Status2} = config_and_refresh(S, Fun),
            reltool_utils:reply(ReplyTo, Ref, Status2),
	    ?MODULE:loop(S3);
        {call, ReplyTo, Ref, get_sys} ->
            reltool_utils:reply(ReplyTo, Ref, {ok, Sys#sys{apps = undefined}}),
            ?MODULE:loop(S);
        {call, ReplyTo, Ref, {set_sys, Sys2}} ->
	    Fun = fun() -> S#state{sys =  Sys2#sys{apps = Sys#sys.apps}} end,
	    {S3, Status} = config_and_refresh(S, Fun),
            reltool_utils:reply(ReplyTo, Ref, Status),
            ?MODULE:loop(S3);
        {call, ReplyTo, Ref, get_status} ->
            reltool_utils:reply(ReplyTo, Ref, S#state.status),
            ?MODULE:loop(S);
        {call, ReplyTo, Ref, {gen_rel_files, Dir}} ->
            Status =
                case reltool_target:gen_rel_files(sys_all_apps(S), Dir) of
                    ok ->
                        {ok, []};
                    {error, Reason} ->
                        {error, Reason}
                end,
            reltool_utils:reply(ReplyTo, Ref, Status),
            ?MODULE:loop(S);
        {call, ReplyTo, Ref, {gen_target, Dir}} ->
            Reply = reltool_target:gen_target(sys_all_apps(S), Dir),
            reltool_utils:reply(ReplyTo, Ref, Reply),
            ?MODULE:loop(S);
        {call, ReplyTo, Ref, gen_spec} ->
            Reply = reltool_target:gen_spec(sys_all_apps(S)),
            reltool_utils:reply(ReplyTo, Ref, Reply),
            ?MODULE:loop(S);
        {'EXIT', Pid, Reason} when Pid =:= S#state.parent_pid ->
            exit(Reason);
        {call, ReplyTo, Ref, Msg} when is_pid(ReplyTo), is_reference(Ref) ->
            error_logger:format("~w~w got unexpected call:\n\t~tp\n",
                                [?MODULE, self(), Msg]),
            reltool_utils:reply(ReplyTo, Ref, {error, {invalid_call, Msg}}),
            ?MODULE:loop(S);
        Msg ->
            error_logger:format("~w~w got unexpected message:\n\t~tp\n",
                                [?MODULE, self(), Msg]),
            ?MODULE:loop(S)
    end.


 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
do_set_apps(#state{sys = Sys} = S, ChangedApps) ->
    %% Create new list of configured applications
    SysApps = app_update_config(ChangedApps, Sys#sys.apps),
    S#state{sys = Sys#sys{apps = SysApps}}.

%% Re-create the #sys.apps list by
%% 1) taking configurable fields from the changed #app records and
%%    create new default records
%% 2) removing #app records if no configurable fields are set
%% 3) keeping #app records that are not changed
app_update_config([#app{name=Name,is_escript={inlined,Escript}}|_],_SysApps) ->
    reltool_utils:throw_error("Application ~w is inlined in ~w. Can not change "
			      "configuration for an inlined application.",
			      [Name,Escript]);
app_update_config([Config|Configs],SysApps) ->
    NewSysApps =
	case app_set_config_only(Config) of
	    {delete,Name} ->
		lists:keydelete(Name,#app.name,SysApps);
	    New ->
		lists:ukeymerge(#app.name,[New],SysApps)
	end,
    app_update_config(Configs,NewSysApps);
app_update_config([],SysApps) ->
    SysApps.

app_set_config_only(#app{mods=ConfigMods} = Config) ->
    app_set_config_only(mod_set_config_only(ConfigMods),Config).

app_set_config_only([],#app{name                 = Name,
			    incl_cond            = undefined,
			    mod_cond             = undefined,
			    use_selected_vsn     = undefined,
			    debug_info           = undefined,
			    app_file             = undefined,
			    app_type             = undefined,
			    incl_app_filters     = undefined,
			    excl_app_filters     = undefined,
			    is_escript           = false})->
    {delete,Name};
app_set_config_only(Mods,#app{name                 = Name,
			      incl_cond            = InclCond,
			      mod_cond             = ModCond,
			      use_selected_vsn     = UseSelectedVsn,
			      debug_info           = DebugInfo,
			      app_file             = AppFile,
			      app_type             = AppType,
			      incl_app_filters     = InclAppFilters,
			      excl_app_filters     = ExclAppFilters,
			      vsn                  = Vsn,
			      is_escript           = IsEscript,
			      label                = Label,
			      info                 = Info,
			      active_dir           = ActiveDir,
			      sorted_dirs          = SortedDirs}) ->
    App = (default_app(Name))#app{incl_cond            = InclCond,
				  mod_cond             = ModCond,
				  use_selected_vsn     = UseSelectedVsn,
				  debug_info           = DebugInfo,
				  app_file             = AppFile,
				  app_type             = AppType,
				  incl_app_filters     = InclAppFilters,
				  excl_app_filters     = ExclAppFilters,
				  vsn                  = Vsn,
				  mods                 = Mods},

    if IsEscript ->
	    %% Some fields shall only be set if it is an escript, e.g. label
	    %% must never be set for any other applications since that will
	    %% prevent refreshing.
	    App#app{is_escript  = IsEscript,
		    active_dir  = ActiveDir,
		    sorted_dirs = SortedDirs,
		    label       = Label,
		    info        = Info};
       UseSelectedVsn =:= dir ->
	    %% Must not loose active_dir if it is configured to be used
	    App#app{active_dir = ActiveDir,
		    sorted_dirs = [ActiveDir]};
       true ->
	    App
    end.

mod_set_config_only(ConfigMods) ->
    [#mod{name       = Name,
	  incl_cond  = InclCond,
	  debug_info = DebugInfo} ||
	#mod{name       = Name,
	     incl_cond  = InclCond,
	     debug_info = DebugInfo} <- ConfigMods,
	(InclCond =/= undefined) orelse (DebugInfo =/= undefined)].


 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

analyse(#state{sys=Sys} = S, Apps, Status) ->
    %% Create a list of {RelName,AppName}, one element for each
    %% AppName that needs to be included for the given release.
    RelApps = apps_in_rels(Sys#sys.rels, Apps),

    %% Initiate is_pre_included and is_included for all applications
    %% based on #sys.incl_cond, #app.incl_cond and if the application
    %% is included in a release (rel spec - see apps_in_rels above).
    %% Then initiate the same for each module, and check that there
    %% are no duplicated module names (in different applications)
    %% where we cannot decide which one to use.
    %% Write all #app to app_tab and all #mod to mod_tab.
    Status2 = apps_init_is_included(S, Apps, RelApps, Status),

    %% For each application that is not (directly or indirectly) part
    %% of a release, but still has #app.is_included==true, propagate
    %% is_included to the dependencies specified in the .app files.
    app_propagate_is_included(S),

    %% For each module that has #mod.is_included==true, propagate
    %% is_included to the modules it uses.
    mod_propagate_is_included(S),

    %% Insert reverse dependencies - i.e. for each
    %% #mod{name=Mod, uses_mods=[UsedMod]},
    %% insert an entry {UsedMod,Mod} in mod_used_by_tab.
    propagate_is_used_by(S),

    %% Set the above reverse dependencies in #mod records
    %% (used_by_mods) and accumulate in #app records.
    %% Make sure #app.is_included is always true if some
    %% #mod.is_included==true for at least one module in the app.
    %% Set status=missing|ok for #app and #mod - indicates if module
    %% (.beam file) is missing in file system.
    app_recap_dependencies(S),

    %% Check that the boot_rel exists.
    %% Check that all applications that are listed in a 'rel' spec are
    %% also really included in the target release.
    %% Check that all mandatory applications are included in all rels.
    verify_config(S, RelApps, Status2).

apps_in_rels(Rels, Apps) ->
    AllRelApps =
	lists:foldl(fun(Rel, RelApps) ->
			    MoreRelApps = apps_in_rel(Rel, Apps),
			    MoreRelApps ++ RelApps
		    end,
		    [],
		    Rels),
    lists:reverse(AllRelApps).

apps_in_rel(#rel{name = RelName, rel_apps = RelApps}, Apps) ->
    Mandatory = [{RelName, kernel}, {RelName, stdlib}],
    Explicit0 = [{RelName, AppName} || #rel_app{name=AppName} <- RelApps],
    Explicit = Mandatory ++ Explicit0,
    Deps =
	[{RelName, AppName, Optional} ||
	    RA <- RelApps,
	    {AppName, Optional} <-
		case lists:keyfind(RA#rel_app.name,
				   #app.name,
				   Apps) of
		    #app{info = Info} ->
			%% Included applications in rel shall overwrite included
			%% applications in .app. I.e. included applications in
			%% .app shall only be used if it is not defined in rel.
			IA = case RA#rel_app.incl_apps of
				 undefined ->
				     Info#app_info.incl_apps;
				 RelIA ->
				     RelIA
			     end,
			build_more_apps(Info, IA);
		    false ->
			reltool_utils:throw_error(
			  "Release ~tp uses non existing "
			  "application ~w",
			  [RelName,RA#rel_app.name])
		end,
	    not lists:keymember(AppName, 2, Explicit)],
    more_apps_in_rels(Deps, Apps, Explicit).

more_apps_in_rels([{RelName, AppName, Optional} | RelApps], Apps, Acc) ->
    case lists:member({RelName, AppName}, Acc) of
	true ->
	    more_apps_in_rels(RelApps, Apps, Acc);
	false ->
	    case lists:keyfind(AppName, #app.name, Apps) of
		#app{info = #app_info{incl_apps=IA} = Info} ->
		    Extra = [{RelName, ChildName, ChildOptional} ||
				{ChildName, ChildOptional} <- build_more_apps(Info, IA)],
		    Acc2 = more_apps_in_rels(Extra, Apps, [{RelName, AppName} | Acc]),
		    more_apps_in_rels(RelApps, Apps, Acc2);
		false when Optional ->
		    more_apps_in_rels(RelApps, Apps, Acc);
		false ->
		    reltool_utils:throw_error(
		      "Release ~tp uses non existing application ~w",
		      [RelName,AppName])
	    end
    end;
more_apps_in_rels([], _Apps, Acc) ->
    Acc.

build_more_apps(#app_info{applications = AA, opt_apps = OA}, IA) ->
    AAOpt = [{App, lists:member(App, OA)} || App <- AA],
    IAOpt = [{App, false} || App <- IA],
    AAOpt ++ IAOpt.

apps_init_is_included(S, Apps, RelApps, Status) ->
    lists:foldl(fun(App, AccStatus) ->
			app_init_is_included(S, App, RelApps, AccStatus)
		end,
		Status,
		Apps).

app_init_is_included(#state{app_tab = AppTab, mod_tab = ModTab, sys=Sys},
		     #app{name = AppName, mods = Mods} = A,
		     RelApps,
		     Status) ->
    AppCond =
        case A#app.incl_cond of
            undefined -> Sys#sys.incl_cond;
            _         -> A#app.incl_cond
        end,
    ModCond =
        case A#app.mod_cond of
            undefined -> Sys#sys.mod_cond;
            _         -> A#app.mod_cond
        end,
    Rels = [RelName || {RelName, AN} <- RelApps, AN =:= AppName],
    {Default, IsPreIncl, IsIncl, Status2} =
        case {AppCond, Rels} of
            {include, _} ->
		{undefined, true, true, Status};
            {exclude, []} ->
		{undefined, false, false, Status};
            {exclude, [RelName | _]} -> % App is included in at least one rel
		reltool_utils:throw_error(
		  "Application ~w is used in release ~tp and cannot be excluded",
		  [AppName,RelName]);
            {derived, []} ->
		{undefined, undefined, undefined, Status};
            {derived, [_ | _]} -> % App is included in at least one rel
		{true, undefined, true, Status}
        end,
    {Mods2,Status3} = lists:mapfoldl(fun(Mod,Acc) ->
					     mod_init_is_included(ModTab,
								  Mod,
								  ModCond,
								  AppCond,
								  Default,
								  Acc)
				     end,
				     Status2,
				     Mods),
    A2 = A#app{mods = Mods2,
	       is_pre_included = IsPreIncl,
	       is_included = IsIncl,
	       rels = Rels},
    ets:insert(AppTab, A2),
    Status3.

mod_init_is_included(ModTab, M, ModCond, AppCond, Default, Status) ->
    %% print(M#mod.name, hipe, "incl_cond -> ~w\n", [AppCond]),
    IsIncl =
        case AppCond of
            include ->
                case M#mod.incl_cond of
                    include ->
                        true;
                    exclude ->
                        false;
		    derived ->
			undefined;
                    undefined ->
                        %% print(M#mod.name, hipe, "mod_cond -> ~w\n",
			%%       [ModCond]),
                        case ModCond of
                            all     -> true;
                            app     -> false_to_undefined(M#mod.is_app_mod);
                            ebin    -> false_to_undefined(M#mod.is_ebin_mod);
                            derived -> Default;
                            none    -> false
                        end
                end;
            exclude ->
                false;
            derived ->
                case M#mod.incl_cond of
                    include ->
                        true;
                    exclude ->
                        false;
		    derived ->
			undefined;
                    undefined ->
                        Default
                end
        end,

    M2 = M#mod{is_pre_included = IsIncl, is_included = IsIncl},

    Status2 =
	case ets:lookup(ModTab,M#mod.name) of
	    [Existing] ->
		case {Existing#mod.is_included,IsIncl} of
		    {false,_} ->
			ets:insert(ModTab, M2),
			reltool_utils:add_warning(
			  "Module ~w exists in applications ~w and ~w. "
			  "Using module from application ~w.",
			  [M#mod.name, Existing#mod.app_name,
			   M#mod.app_name, M#mod.app_name],
			  Status);
		    {_,false} ->
			%% Don't insert in ModTab - using Existing
			reltool_utils:add_warning(
			  "Module ~w exists in applications ~w and ~w. "
			  "Using module from application ~w.",
			  [M#mod.name, Existing#mod.app_name,
			   M#mod.app_name,Existing#mod.app_name],
			  Status);
		    {_,_} ->
			reltool_utils:throw_error(
			  "Module ~w potentially included by two different "
			  "applications: ~w and ~w.",
			  [M#mod.name,Existing#mod.app_name,M#mod.app_name])
		end;
	    [] ->
		ets:insert(ModTab, M2),
		Status
	end,

    %% print(M#mod.name, hipe, "~p -> ~w\n", [M2, IsIncl]),
    {M2,Status2}.

false_to_undefined(Bool) ->
    case Bool of
        false -> undefined;
        _     -> Bool
    end.

get_no_rel_apps_and_dependencies(S) ->
    ets:select(S#state.app_tab, [{#app{name='$1',
				       is_included=true,
				       info=#app_info{applications='$2',
						      incl_apps='$3',
						      _='_'},
				       rels=[],
				       _='_'},
				  [],
				  [{{'$1','$2','$3'}}]}]).

app_propagate_is_included(S) ->
    lists:foreach(
      fun({AppName,DepNames1,DepNames2}) ->
	      app_mark_is_included(S,AppName,DepNames1++DepNames2)
      end,
      get_no_rel_apps_and_dependencies(S)).

app_mark_is_included(#state{app_tab=AppTab, mod_tab=ModTab, sys=Sys}=S,UsedByName,[AppName|AppNames]) ->
    case ets:lookup(AppTab, AppName) of
	[A] ->
	    case A#app.is_included of
		undefined ->
		    %% Not yet marked => mark and propagate
		    A2 =
			case A#app.incl_cond of
			    include ->
				A#app{is_pre_included = true,
				      is_included = true};
			    exclude ->
				A#app{is_pre_included = false,
				      is_included = false};
			    AppInclCond when AppInclCond==undefined;
					     AppInclCond==derived  ->
				A#app{is_included = true}
			end,
		    ets:insert(AppTab, A2),

		    ModCond =
			case A#app.mod_cond of
			    undefined -> Sys#sys.mod_cond;
			    _         -> A#app.mod_cond
			end,
		    Filter =
			fun(M) ->
				case ModCond of
				    all     -> true;
				    app     -> M#mod.is_app_mod;
				    ebin    -> M#mod.is_ebin_mod;
				    derived -> false;
				    none    -> false
				end
			end,
		    Mods = lists:filter(Filter, A#app.mods),
		    %% Mark the modules of this app, but no need to go
		    %% recursive on modules since this is done in
		    %% mod_mark_is_included.
		    [case M#mod.is_included of
			 undefined ->
			     M2 =
				 case M#mod.incl_cond of
				     include ->
					 M#mod{is_pre_included = true,
					       is_included = true};
				     exclude ->
					 M#mod{is_pre_included = false,
					       is_included = false};
				     ModInclCond when ModInclCond==undefined;
						      ModInclCond==derived  ->
					 M#mod{is_included = true}
				 end,
			     ets:insert(ModTab, M2);
			 _ ->
			     ok
		     end || M <- Mods],

		    %% Go recursive on dependencies
		    #app{info=#app_info{applications=DepNames1,
					incl_apps=DepNames2}} = A,
		    app_mark_is_included(S,AppName,DepNames1++DepNames2);
		_ ->
		    %% Already marked
		    ok
	    end;
	[] ->
	    %% Missing app
	    reltool_utils:throw_error(
	      "Application ~tp uses non existing application ~w",
	      [UsedByName,AppName])
    end,
    app_mark_is_included(S, UsedByName, AppNames);
app_mark_is_included(_S, _UsedByName, []) ->
    ok.

%% Return the list for {ModName, UsesModNames} for all modules where
%% #mod.is_included==true.
get_all_mods_and_dependencies(S) ->
    ets:select(S#state.mod_tab, [{#mod{name='$1',
				       uses_mods='$2',
				       is_included=true,
				       _='_'},
				  [],
				  [{{'$1','$2'}}]}]).

mod_propagate_is_included(S) ->
    case lists:flatmap(
	   fun({ModName,UsesModNames}) ->
		   mod_mark_is_included(S,ModName,UsesModNames,[])
	   end,
	   get_all_mods_and_dependencies(S)) of
	[] ->
	    ok;
	MissingMods ->
	    MissingApp = default_app(?MISSING_APP_NAME, "missing"),
	    MissingApp2 = MissingApp#app{label = ?MISSING_APP_TEXT,
					 info = missing_app_info(""),
					 mods = MissingMods,
					 status = missing,
					 uses_mods = []},
	    ets:insert(S#state.app_tab, MissingApp2),
	    ok
    end.

mod_mark_is_included(#state{app_tab=AppTab, mod_tab=ModTab, sys=Sys} = S,
		     UsedByName, [ModName | ModNames], Acc) ->
    Acc3 =
        case ets:lookup(ModTab, ModName) of
            [M] ->
                case M#mod.is_included of
                    undefined ->
                        %% Not yet marked => mark and propagate
                        M2 =
                            case M#mod.incl_cond of
                                include ->
                                    M#mod{is_pre_included = true,
					  is_included = true};
                                exclude ->
                                    M#mod{is_pre_included = false,
					  is_included = false};
				ModInclCond when ModInclCond==undefined;
						 ModInclCond==derived  ->
                                    M#mod{is_included = true}
                            end,
                        ets:insert(ModTab, M2),
                        [A] = ets:lookup(AppTab, M2#mod.app_name),
                        Acc2 =
                            case A#app.is_included of
                                undefined ->
                                    ModCond =
                                        case A#app.mod_cond of
                                            undefined -> Sys#sys.mod_cond;
                                            _         -> A#app.mod_cond
                                        end,
                                    Filter =
                                        fun(M3) ->
                                                case ModCond of
                                                    all     -> true;
                                                    app     -> M3#mod.is_app_mod;
                                                    ebin    -> M3#mod.is_ebin_mod;
                                                    derived -> false;
                                                    none    -> false
                                                end
                                        end,
                                    Mods = lists:filter(Filter, A#app.mods),
                                    A2 = A#app{is_included = true},
                                    ets:insert(AppTab, A2),
                                    mod_mark_is_included(S,
							 ModName,
							 [M3#mod.name ||
							     M3 <- Mods],
							 Acc);
				_ ->
				    %% Already marked true or false
                                    Acc
                            end,
                        mod_mark_is_included(S, ModName, M2#mod.uses_mods, Acc2);
		    _ ->
                        %% Already marked true or false
                        Acc
                end;
            [] ->
                M = missing_mod(ModName, ?MISSING_APP_NAME),
                M2 = M#mod{is_included = true},
                ets:insert(ModTab, M2),
                [M2 | Acc]
        end,
    mod_mark_is_included(S, UsedByName, ModNames, Acc3);
mod_mark_is_included(_S, _UsedByName, [], Acc) ->
    Acc.

propagate_is_used_by(S) ->
    lists:foreach(
      fun({Mod,UsesMods}) ->
	      lists:foreach(
		fun(UsedMod) ->
			ets:insert(S#state.mod_used_by_tab,{UsedMod,Mod})
		end,
		UsesMods)
      end,
      get_all_mods_and_dependencies(S)).


app_recap_dependencies(S) ->
    ets:foldl(fun(App,_) -> app_recap_dependencies(S,App) end,
	      ok, S#state.app_tab).

app_recap_dependencies(S, #app{mods = Mods, is_included = IsIncl} = A) ->
    {Mods2, IsIncl2} = mod_recap_dependencies(S, A, Mods, [], IsIncl),
    AppStatus =
        case lists:keymember(missing, #mod.status, Mods2) of
            true  -> missing;
            false -> ok
        end,
    UsesMods = [M#mod.uses_mods || M <- Mods2, M#mod.is_included =:= true],
    UsesMods2 = lists:usort(lists:flatten(UsesMods)),
    UsesApps = [M#mod.app_name || ModName <- UsesMods2,
				  M <- ets:lookup(S#state.mod_tab, ModName)],
    UsesApps2 = lists:usort(UsesApps),
    UsedByMods = [M#mod.used_by_mods || M <- Mods2, M#mod.is_included =:= true],
    UsedByMods2 = lists:usort(lists:flatten(UsedByMods)),
    UsedByApps = [M#mod.app_name || ModName <- UsedByMods2,
				    M <- ets:lookup(S#state.mod_tab, ModName)],
    UsedByApps2 = lists:usort(UsedByApps),

    A2 = A#app{mods = Mods2,
               status = AppStatus,
               uses_mods = UsesMods2,
               used_by_mods = UsedByMods2,
               uses_apps = UsesApps2,
               used_by_apps = UsedByApps2,
               is_included = IsIncl2},
    ets:insert(S#state.app_tab,A2),
    ok.

mod_recap_dependencies(S, A, [#mod{name = ModName}=M1 | Mods], Acc, IsIncl) ->
    case ets:lookup(S#state.mod_tab, ModName) of
	[M2] when M2#mod.app_name=:=A#app.name ->
	    ModStatus = do_get_status(M2),
	    %% print(M2#mod.name, hipe, "status -> ~w\n", [ModStatus]),
	    {IsIncl2, M3} =
		case M2#mod.is_included of
		    true ->
			UsedByMods =
			    [N || {_, N} <- ets:lookup(S#state.mod_used_by_tab,
						       ModName)],
			{true, M2#mod{status = ModStatus, used_by_mods = UsedByMods}};
		    _    ->
			{IsIncl, M2#mod{status = ModStatus, used_by_mods = []}}
		end,
	    ets:insert(S#state.mod_tab, M3),
	    mod_recap_dependencies(S, A, Mods, [M3 | Acc], IsIncl2);
	[_] when A#app.is_included==false; M1#mod.incl_cond==exclude ->
	    %% App is explicitly excluded so it is ok that the module
	    %% record does not exist for this module in this
	    %% application.
	    mod_recap_dependencies(S, A, Mods, [M1 | Acc], IsIncl);
	[M2] ->
	    %% A module is potensially included by multiple
	    %% applications. This is not allowed!
	    reltool_utils:throw_error(
	      "Module ~w potentially included by two different applications: "
	      "~w and ~w.", [ModName,A#app.name, M2#mod.app_name])
    end;
mod_recap_dependencies(_S, _A, [], Acc, IsIncl) ->
    {lists:reverse(Acc), IsIncl}.

do_get_status(M) ->
    if
        M#mod.exists =:= false, M#mod.is_included =/= false ->
            missing;
        true ->
            ok
    end.

verify_config(#state{app_tab=AppTab, sys=#sys{boot_rel = BootRel, rels = Rels}},
	      RelApps, Status) ->
    case lists:keymember(BootRel, #rel.name, Rels) of
        true ->
	    Status2 = lists:foldl(fun(RA, Acc) ->
					  check_app(AppTab, RA, Acc) end,
				  Status,
				  RelApps),
	    lists:foldl(fun(#rel{name = RelName}, Acc)->
				check_rel(RelName, RelApps, Acc)
			end,
			Status2,
			Rels);
        false ->
	    reltool_utils:throw_error(
	      "Release ~tp is mandatory (used as boot_rel)",[BootRel])
    end.

check_app(AppTab, {RelName, AppName}, Status) ->
    case ets:lookup(AppTab, AppName) of
	[#app{is_pre_included=IsPreIncl, is_included=IsIncl}]
	  when IsPreIncl; IsIncl ->
	    Status;
	_ ->
	    reltool_utils:throw_error(
	      "Release ~tp uses non included application ~w",[RelName,AppName])
    end.

check_rel(RelName, RelApps, Status) ->
    EnsureApp =
        fun(AppName, Acc) ->
                case lists:member({RelName, AppName}, RelApps) of
                    true ->
                        Acc;
                    false ->
			reltool_utils:throw_error(
			  "Mandatory application ~w is not included in "
			  "release ~tp", [AppName,RelName])
                end
        end,
    Mandatory = [kernel, stdlib],
    lists:foldl(EnsureApp, Status, Mandatory).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

refresh_app(#app{name = AppName,
                 is_escript = IsEscript,
                 active_dir = ActiveDir,
                 label = OptLabel,
                 mods = Mods,
                 status = AppStatus} = App,
            Force,
            Status) ->
    if
        Force; OptLabel =:= undefined ->
            {AppInfo, EbinMods, Status3} =
                case IsEscript of
                    false ->

                        %% Add info from .app file
                        Base = get_base(AppName, ActiveDir),
                        DefaultVsn = get_vsn_from_dir(AppName,Base),
                        Ebin = filename:join([ActiveDir, "ebin"]),
                        AppFile =
			    filename:join([Ebin,
					   atom_to_list(AppName) ++ ".app"]),
                        {AI, Status2} =
			    read_app_info(AppFile,
					  AppFile,
					  AppName,
                                          ActiveDir,
                                          AppStatus,
					  DefaultVsn,
					  Status),

			%% And read all modules from ebin and create
			%% #mod record with dependencies (uses_mods).
                        {AI, read_ebin_mods(Ebin, AppName), Status2};
                    _ ->
                        {App#app.info, Mods, Status}
                end,

            %% Add non-existing modules - i.e. create default #mod
            %% records for all modules that are listed in .app file
            %% but do not exist in ebin.
	    AppInfoMods = lists:usort(AppInfo#app_info.modules),
	    Status4 =
		case AppInfo#app_info.modules -- AppInfoMods of
		    [] ->
			Status3;
		    DuplicatedMods  ->
			lists:foldl(
			  fun(M,S) ->
				  reltool_utils:add_warning(
				    "Module ~w duplicated in app file for "
				    "application ~w.", [M, AppName], S)
			  end,
			  Status3,
			  DuplicatedMods)
		end,
	    AppModNames =
		case AppInfo#app_info.mod of
		    {StartModName, _} ->
			case lists:member(StartModName, AppInfoMods) of
			    true  -> AppInfoMods;
			    false -> [StartModName | AppInfoMods]
			end;
		    undefined ->
			AppInfoMods
		end,
	    MissingMods = add_missing_mods(AppName, EbinMods, AppModNames),

            %% Add optional user config for each module.
	    %% The #mod records that are already in the #app record at
	    %% this point do only contain user defined configuration
	    %% (set by parse_options/4). So here we merge with the
	    %% default records from above.
            Mods2 = add_mod_config(MissingMods ++ EbinMods, Mods),

            %% Set app flag for each module in app file, i.e. the flag
            %% which indicates if the module is listed in the .app
            %% file or not. The start module also get the flag set to true.
            Mods3 = set_mod_flags(Mods2, AppModNames),

	    %% Finally, set label and update the #app record
            AppVsn = AppInfo#app_info.vsn,
            AppLabel =
                case AppVsn of
                    "" -> atom_to_list(AppName);
                    _  -> atom_to_list(AppName) ++ "-" ++ AppVsn
                end,
            App2 = App#app{vsn = AppVsn,
                           label = AppLabel,
                           info = AppInfo,
                           mods = lists:keysort(#mod.name, Mods3)},
            {App2, Status4};
        true ->
            {App, Status}
    end.

missing_app_info(Vsn) ->
    #app_info{vsn = Vsn}.

read_app_info(_AppFileOrBin, _AppFile, erts, _ActiveDir, _AppStatus, DefaultVsn, Status) ->
    {missing_app_info(DefaultVsn), Status};
read_app_info(_AppFileOrBin, _AppFile, _AppName, undefined, missing, DefaultVsn, Status) ->
    {missing_app_info(DefaultVsn), Status};
read_app_info(AppFileOrBin, AppFile, AppName, _ActiveDir, _AppStatus, DefaultVsn, Status) ->
    EnoentText = file:format_error(enoent),
    case reltool_utils:prim_consult(AppFileOrBin) of
        {ok,  [{application, AppName, Info}]} ->
            AI = #app_info{vsn = DefaultVsn},
            parse_app_info(AppFile, Info, AI, Status);
        {ok, _BadApp} ->
            {missing_app_info(DefaultVsn),
	     reltool_utils:add_warning("~w: Illegal contents in app file ~tp, "
				       "application tuple with arity 3 expected.",
				       [AppName,AppFile],
				       Status)};
        {error, Text} when Text =:= EnoentText ->
	    {missing_app_info(DefaultVsn),
             reltool_utils:add_warning("~w: Missing app file ~tp.",
                                       [AppName,AppFile],
                                       Status)};
        {error, Text} ->
            {missing_app_info(DefaultVsn),
	     reltool_utils:add_warning("~w: Cannot parse app file ~tp (~tp).",
				       [AppName,AppFile,Text],
				       Status)}
    end.

parse_app_info(File, [{Key, Val} | KeyVals], AI, Status) ->
    case Key of
        description ->
	    parse_app_info(File, KeyVals, AI#app_info{description = Val},
			   Status);
        id ->
	    parse_app_info(File, KeyVals, AI#app_info{id = Val}, Status);
        vsn ->
	    parse_app_info(File, KeyVals, AI#app_info{vsn = Val}, Status);
        modules ->
	    parse_app_info(File, KeyVals, AI#app_info{modules = Val}, Status);
        maxP ->
	    parse_app_info(File, KeyVals, AI#app_info{maxP = Val}, Status);
        maxT ->
	    parse_app_info(File, KeyVals, AI#app_info{maxT = Val}, Status);
        registered ->
	    parse_app_info(File, KeyVals, AI#app_info{registered = Val},
			   Status);
	optional_applications ->
	    parse_app_info(File, KeyVals, AI#app_info{opt_apps = Val}, Status);
        included_applications ->
	    parse_app_info(File, KeyVals, AI#app_info{incl_apps = Val}, Status);
        applications ->
	    parse_app_info(File, KeyVals, AI#app_info{applications = Val},
			   Status);
        env ->
	    parse_app_info(File, KeyVals, AI#app_info{env = Val}, Status);
        mod ->
	    parse_app_info(File, KeyVals, AI#app_info{mod = Val}, Status);
        start_phases ->
	    parse_app_info(File, KeyVals, AI#app_info{start_phases = Val},
			   Status);
	runtime_dependencies ->
	    parse_app_info(File, KeyVals, AI#app_info{runtime_dependencies = Val},
			   Status);
        _ ->
	    Status2 =
		reltool_utils:add_warning("Unexpected item ~tp in app file ~tp.",
					  [Key,File],
					  Status),
	    parse_app_info(File, KeyVals, AI, Status2)
    end;
parse_app_info(_, [], AI, Status) ->
    {AI, Status}.

read_ebin_mods(Ebin, AppName) ->
    case erl_prim_loader:list_dir(Ebin) of
        {ok, Files} ->
            Ext = code:objfile_extension(),
            InitMod = fun(F) ->
                              File = filename:join([Ebin, F]),
                              init_mod(AppName, File, File, Ext)
                      end,
            Files2 = [F || F <- Files, filename:extension(F) =:= Ext],
            pmap(InitMod, Files2);
        error ->
            []
    end.

pmap(Fun, List) ->
    lists:map(Fun, List).
    %% N = erlang:system_info(schedulers) * 2,
    %% pmap(Fun, List, 0, N, 0, [], []).

%% -record(pmap_res, {count, ref, res}).
%% -record(pmap_wait, {count, ref, pid}).
%%
%% pmap(Fun, [H | T], N, Max, Count, WaitFor, Results) when N < Max ->
%%     Ref = make_ref(),
%%     Parent = self(),
%%     Count2 = Count + 1,
%%     Pid = spawn_link(fun() -> Parent ! #pmap_res{count = Count2, ref = Ref, res = Fun(H)}, unlink(Parent) end),
%%     PW = #pmap_wait{count = Count2, pid = Pid, ref = Ref},
%%     pmap(Fun, T, N + 1, Max, Count2, [PW | WaitFor], Results);
%% pmap(_Fun, [], _N, _Max, _Count, [], Results) ->
%%     %% Sort results and return them in the same orderas the original list
%%     [PR#pmap_res.res || PR <- lists:keysort(#pmap_res.count, Results)];
%% pmap(Fun, List, N, Max, Count, WaitFor, Results) ->
%%     receive
%%      #pmap_res{ref = Ref} = PR ->
%%          WaitFor2 = lists:keydelete(Ref, #pmap_wait.ref, WaitFor),
%%          pmap(Fun, List, N - 1, Max, Count, WaitFor2, [PR | Results]);
%%      {'EXIT', Reason} ->
%%          exit(Reason)
%%     end.

init_mod(AppName, File, FileOrBin, Ext) ->
    UsesMods = xref_mod(FileOrBin),
    Base = filename:basename(File, Ext),
    ModName = list_to_atom(Base),
    #mod{name = ModName,
         app_name = AppName,
         incl_cond = undefined,
         is_ebin_mod = true,
         uses_mods = UsesMods,
         exists = true}.

xref_mod({Base, Bin}) when is_binary(Bin) ->
    Dir = filename:absname("reltool_server.tmp"),
    ok = reltool_utils:recursive_delete(Dir),
    ok = file:make_dir(Dir),
    File = filename:join([Dir, Base]),
    ok = file:write_file(File, Bin),
    Res = xref_mod(File),
    ok = reltool_utils:recursive_delete(Dir),
    Res;
xref_mod(File) when is_list(File) ->
    {ok, Pid} = xref:start([{xref_mode, modules}]),
    link(Pid),
    ok = xref:set_default(Pid, [{verbose,false}, {warnings, false}]),
    ok = xref:set_library_path(Pid, []),
    {ok, _} = xref:add_module(Pid, File, []),
    {ok, UnknownMods} = xref:q(Pid, "UM", []),
    %% {ok, ExportedFuns} = xref:q(Pid, "X", []),
    %% io:format("Unres: ~p\n", [xref:variables(Pid, [predefined])]),
    %% io:format("Q: ~p\n", [xref:q(Pid, "XU", [])]),
    Ref = erlang:monitor(process, Pid),
    unlink(Pid),
    xref:stop(Pid),
    wait_for_processto_die(Ref, Pid, File),
    UnknownMods.

wait_for_processto_die(Ref, Pid, File) ->
    receive
	{'DOWN', Ref, _Type, _Object, _Info} ->
	    ok
    after timer:seconds(30) ->
	    error_logger:error_msg("~w(~w): Waiting for process ~w to die ~tp\n",
				   [?MODULE, ?LINE, Pid, File]),
	    wait_for_processto_die(Ref, Pid, File)
    end.

add_missing_mods(AppName, EbinMods, AppModNames) ->
    EbinModNames = [M#mod.name || M <- EbinMods],
    MissingModNames = AppModNames -- EbinModNames,
    [missing_mod(ModName, AppName) || ModName <- MissingModNames].

missing_mod(ModName, AppName) ->
    %% io:format("Missing: ~w -> ~w\n", [AppName, ModName]),
    #mod{name = ModName,
         app_name = AppName,
         incl_cond = undefined,
         is_ebin_mod = false,
         exists = false,
         status = missing,
         uses_mods = []}.

add_mod_config(Mods, ModConfigs) ->
    AddConfig =
        fun(Config, Acc) ->
                case lists:keyfind(Config#mod.name, #mod.name, Mods) of
                    #mod{} = M ->
                        M2 = M#mod{incl_cond = Config#mod.incl_cond},
                        lists:keystore(Config#mod.name, #mod.name, Acc, M2);
                    false ->
                        Config2 = Config#mod{uses_mods = [], exists = false},
                        [Config2 | Acc]
                end
        end,
    lists:foldl(AddConfig, Mods, ModConfigs).

set_mod_flags(Mods, AppModNames) ->
    SetFlags =
        fun(#mod{name = N} = M) ->
                M#mod{is_app_mod = lists:member(N, AppModNames)}
        end,
    lists:map(SetFlags, Mods).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

do_get_config(S, InclDef, InclDeriv) ->
    AppTab = S#state.app_tab,
    Sys =
        case InclDeriv of
            false ->
		%% Only the apps that exist in #sys.apps shall be
		%% included,and they shall be minimized
		Apps = [shrink_app(App) ||
			   #app{name=Name} <- (S#state.sys)#sys.apps,
			   App <- ets:lookup(AppTab,Name)],
		(S#state.sys)#sys{apps=Apps};
            true  ->
		sys_all_apps(S)
        end,
    reltool_target:gen_config(Sys, InclDef).

shrink_app(A) ->
    Mods = [M#mod{is_app_mod = undefined,
                  is_ebin_mod = undefined,
                  uses_mods = undefined,
                  exists = false} ||
               M <- A#app.mods,
               M#mod.incl_cond =/= undefined],
    if
	A#app.is_escript ->
	    A#app{vsn = undefined,
		  label = undefined,
		  info = undefined,
		  mods = [],
		  uses_mods = undefined};
	true ->
            {Dir, Dirs, OptVsn} =
                case A#app.use_selected_vsn of
                    undefined ->
			{undefined, [], undefined};
                    vsn ->
			{undefined, [], A#app.vsn};
                    dir ->
			{A#app.active_dir, [A#app.active_dir], undefined}
                end,
            A#app{active_dir = Dir,
		  sorted_dirs = Dirs,
		  vsn = OptVsn,
		  label = undefined,
		  info = undefined,
		  mods = Mods,
		  uses_mods = undefined}
    end.


do_save_config(S, Filename, InclDef, InclDeriv) ->
    {ok, Config} = do_get_config(S, InclDef, InclDeriv),
    IoList = io_lib:format("%% ~s\n"
                           "%% config generated at ~w ~w\n"
                           "~tp.\n\n",
                           [epp:encoding_to_string(utf8),date(), time(), Config]),
    Bin = unicode:characters_to_binary(IoList),
    file:write_file(Filename, Bin).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

do_load_config(S, SysConfig) ->
    S#state{sys = read_config(default_sys(), SysConfig)}.

read_config(OldSys, Filename) when is_list(Filename) ->
    case file:consult(Filename) of
        {ok, [SysConfig | _]} ->
            read_config(OldSys, SysConfig);
        {ok, Content} ->
	    reltool_utils:throw_error("Illegal file content: ~p",[Content]);
        {error, Reason} ->
	    reltool_utils:throw_error("Illegal config file ~tp: ~ts",
				      [Filename,file:format_error(Reason)])
    end;
read_config(OldSys, {sys, KeyVals}) ->
    NewSys = decode(OldSys#sys{apps = [], rels = []}, KeyVals),
    Apps = [A#app{mods = lists:sort(A#app.mods)} || A <- NewSys#sys.apps],
    Rels =
	case NewSys#sys.rels of
	    []   -> reltool_utils:default_rels();
	    Rs -> Rs
	end,
    NewSys2 = NewSys#sys{apps = lists:sort(Apps),
			 rels = lists:sort(Rels)},
    case lists:keymember(NewSys2#sys.boot_rel, #rel.name, NewSys2#sys.rels) of
	true ->
	    NewSys2;
	false ->
	    reltool_utils:throw_error(
	      "Release ~tp is mandatory (used as boot_rel)",
	      [NewSys2#sys.boot_rel])
    end;
read_config(_OldSys, BadConfig) ->
    reltool_utils:throw_error("Illegal content: ~tp", [BadConfig]).

decode(#sys{apps = Apps} = Sys, [{erts = Name, AppKeyVals} | SysKeyVals])
  when is_atom(Name), is_list(AppKeyVals) ->
    App = default_app(Name),
    App2= decode(App, AppKeyVals),
    decode(Sys#sys{apps = [App2 | Apps]}, SysKeyVals);
decode(#sys{apps = Apps} = Sys, [{app, Name, AppKeyVals} | SysKeyVals])
  when is_atom(Name), is_list(AppKeyVals) ->
    App = default_app(Name),
    App2 = decode(App, AppKeyVals),
    decode(Sys#sys{apps = [App2 | Apps]}, SysKeyVals);
decode(#sys{apps = Apps, escripts = Escripts} = Sys,
       [{escript, File0, AppKeyVals} | SysKeyVals])
  when is_list(File0), is_list(AppKeyVals) ->
    File = filename:absname(File0),
    App = default_escript_app(File),
    App2 = decode(App, AppKeyVals),
    decode(Sys#sys{apps = [App2 | Apps], escripts = [File | Escripts]},
	   SysKeyVals);
decode(#sys{rels = Rels} = Sys, [{rel, Name, Vsn, RelApps} | SysKeyVals])
  when is_list(Name), is_list(Vsn), is_list(RelApps) ->
    Rel = #rel{name = Name, vsn = Vsn, rel_apps = []},
    Rel2 = decode(Rel, RelApps),
    decode(Sys#sys{rels = [Rel2 | Rels]}, SysKeyVals);
decode(#sys{rels = Rels} = Sys, [{rel, Name, Vsn, RelApps, Opts} | SysKeyVals])
  when is_list(Name), is_list(Vsn), is_list(RelApps), is_list(Opts) ->
    Rel1 = lists:foldl(fun(Opt, Rel0) ->
        case Opt of
            {load_dot_erlang, Value} when is_boolean(Value) ->
                Rel0#rel{load_dot_erlang = Value};
            _ ->
                reltool_utils:throw_error("Illegal rel option: ~tp", [Opt])
        end
    end, #rel{name = Name, vsn = Vsn, rel_apps = []}, Opts),
    Rel2 = decode(Rel1, RelApps),
    decode(Sys#sys{rels = [Rel2 | Rels]}, SysKeyVals);
decode(#sys{} = Sys, [{Key, Val} | KeyVals]) ->
    Sys3 =
        case Key of
            root_dir when is_list(Val) ->
                Sys#sys{root_dir = Val};
            lib_dirs when is_list(Val) ->
                Sys#sys{lib_dirs = Val};
            mod_cond when Val =:= all;
			  Val =:= app;
                          Val =:= ebin;
			  Val =:= derived;
                          Val =:= none ->
                Sys#sys{mod_cond = Val};
            incl_cond when Val =:= include;
			   Val =:= exclude;
                           Val =:= derived ->
                Sys#sys{incl_cond = Val};
            boot_rel when is_list(Val) ->
                Sys#sys{boot_rel = Val};
            emu_name when is_list(Val) ->
                Sys#sys{emu_name = Val};
	    profile when Val =:= development;
			 Val =:= embedded;
			 Val =:= standalone ->
		InclSys = reltool_utils:choose_default(incl_sys_filters, Val, false),
		ExclSys = reltool_utils:choose_default(excl_sys_filters, Val, false),
		InclApp = reltool_utils:choose_default(incl_app_filters, Val, false),
		ExclApp = reltool_utils:choose_default(excl_app_filters, Val, false),
		AppType = reltool_utils:choose_default(embedded_app_type, Val, false),
		Sys#sys{profile = Val,
			incl_sys_filters = dec_re(incl_sys_filters,
						  InclSys,
						  Sys#sys.incl_sys_filters),
			excl_sys_filters = dec_re(excl_sys_filters,
						  ExclSys,
						  Sys#sys.excl_sys_filters),
			incl_app_filters = dec_re(incl_app_filters,
						  InclApp,
						  Sys#sys.incl_app_filters),
			excl_app_filters = dec_re(excl_app_filters,
						  ExclApp,
						  Sys#sys.excl_app_filters),
			embedded_app_type = AppType};
	    excl_lib when Val =:= otp_root ->
		Sys#sys{excl_lib=Val};
            incl_sys_filters ->
                Sys#sys{incl_sys_filters =
			    dec_re(Key, Val, Sys#sys.incl_sys_filters)};
            excl_sys_filters ->
                Sys#sys{excl_sys_filters =
			    dec_re(Key, Val, Sys#sys.excl_sys_filters)};
            incl_app_filters ->
                Sys#sys{incl_app_filters =
			    dec_re(Key, Val, Sys#sys.incl_app_filters)};
            excl_app_filters ->
                Sys#sys{excl_app_filters =
			    dec_re(Key, Val, Sys#sys.excl_app_filters)};
            incl_archive_filters ->
                io:format("incl_archive_filters is no longer supported in reltool");
            excl_archive_filters ->
                io:format("excl_archive_filters is no longer supported in reltool");
            archive_opts when is_list(Val) ->
                io:format("archive_opts is no longer supported in reltool");
            relocatable when Val =:= true; Val =:= false ->
                Sys#sys{relocatable = Val};
            rel_app_type when Val =:= permanent;
			      Val =:= transient;
			      Val =:= temporary;
			      Val =:= load;
			      Val =:= none ->
                Sys#sys{rel_app_type = Val};
	    embedded_app_type when Val =:= permanent;
				   Val =:= transient;
				   Val =:= temporary;
				   Val =:= load;
				   Val =:= none;
				   Val =:= undefined ->
                Sys#sys{embedded_app_type = Val};
            app_file when Val =:= keep; Val =:= strip; Val =:= all ->
                Sys#sys{app_file = Val};
            debug_info when Val =:= keep; Val =:= strip; is_list(Val) ->
                Sys#sys{debug_info = Val};
            _ ->
		reltool_utils:throw_error("Illegal option: ~tp", [{Key, Val}])
        end,
    decode(Sys3, KeyVals);
decode(#app{} = App, [{Key, Val} | KeyVals]) ->
    App2 =
        case Key of
            mod_cond when Val =:= all;
			  Val =:= app;
			  Val =:= ebin;
			  Val =:= derived;
			  Val =:= none ->
                App#app{mod_cond = Val};
            incl_cond when Val =:= include;
			   Val =:= exclude;
			   Val =:= derived ->
                App#app{incl_cond = Val};

            debug_info when Val =:= keep;
			    Val =:= strip;
                            is_list(Val) ->
                App#app{debug_info = Val};
            app_file when Val =:= keep;
			  Val =:= strip;
			  Val =:= all ->
                App#app{app_file = Val};
            app_type when Val =:= permanent;
			  Val =:= transient;
			  Val =:= temporary;
                          Val =:= load;
			  Val =:= none;
			  Val =:= undefined ->
                App#app{app_type = Val};
            incl_app_filters ->
                App#app{incl_app_filters =
			    dec_re(Key, Val, App#app.incl_app_filters)};
            excl_app_filters ->
                App#app{excl_app_filters =
			    dec_re(Key, Val, App#app.excl_app_filters)};
            incl_archive_filters ->
                io:format("incl_archive_filters is no longer supported in reltool");
            excl_archive_filters ->
                io:format("excl_archive_filters is no longer supported in reltool");
            archive_opts when is_list(Val) ->
                io:format("archive_opts is no longer supported in reltool");
            vsn when is_list(Val), App#app.use_selected_vsn=:=undefined ->
		App#app{use_selected_vsn = vsn, vsn = Val};
	    lib_dir when is_list(Val), App#app.use_selected_vsn=:=undefined ->
		case filelib:is_dir(Val) of
		    true ->
			Dir = reltool_utils:normalize_dir(Val),
			App#app{use_selected_vsn = dir,
				active_dir = Dir,
				sorted_dirs = [Dir]};
		    false ->
			reltool_utils:throw_error("Illegal lib dir for ~w: ~tp",
						  [App#app.name, Val])
		end;
	    SelectVsn when SelectVsn=:=vsn; SelectVsn=:=lib_dir ->
		reltool_utils:throw_error("Mutual exclusive options "
					  "'vsn' and 'lib_dir'",[]);
            _ ->
		reltool_utils:throw_error("Illegal option: ~tp", [{Key, Val}])
        end,
    decode(App2, KeyVals);
decode(#app{mods = Mods} = App, [{mod, Name, ModKeyVals} | AppKeyVals]) ->
    Mod = decode(#mod{name = Name}, ModKeyVals),
    decode(App#app{mods = [Mod | Mods]}, AppKeyVals);
decode(#mod{} = Mod, [{Key, Val} | KeyVals]) ->
    Mod2 =
        case Key of
            incl_cond when Val =:= include; Val =:= exclude; Val =:= derived ->
                Mod#mod{incl_cond = Val};
            debug_info when Val =:= keep; Val =:= strip; is_list(Val) ->
                Mod#mod{debug_info = Val};
            _ ->
		reltool_utils:throw_error("Illegal option: ~tp", [{Key, Val}])
        end,
    decode(Mod2, KeyVals);
decode(#rel{rel_apps = RelApps} = Rel, [RelApp | KeyVals]) ->
    {ValidTypesAssigned, RA} =
        case RelApp of
            Name when is_atom(Name) ->
                {true, #rel_app{name = Name}};
            {Name, InclApps} when is_atom(Name), is_list(InclApps) ->
		VI = lists:all(fun erlang:is_atom/1, InclApps),
                {VI, #rel_app{name = Name, incl_apps = InclApps}};
            {Name, Type} when is_atom(Name) ->
                {is_type(Type), #rel_app{name = Name, app_type = Type}};
            {Name, Type, InclApps} when is_atom(Name), is_list(InclApps) ->
		VT = is_type(Type),
		VI = lists:all(fun erlang:is_atom/1, InclApps),
		{VT andalso VI,
                 #rel_app{name = Name, app_type = Type, incl_apps = InclApps}};
            _ ->
                {false, #rel_app{}}
        end,
    case ValidTypesAssigned of
	true ->
            decode(Rel#rel{rel_apps = RelApps ++ [RA]}, KeyVals);
        false ->
	    reltool_utils:throw_error("Illegal option: ~tp", [RelApp])
    end;
decode(Acc, []) ->
    Acc;
decode(_Acc, KeyVal) ->
    reltool_utils:throw_error("Illegal option: ~tp", [KeyVal]).

is_type(Type) ->
    case Type of
        undefined -> true;
        permanent -> true;
        transient -> true;
        temporary -> true;
        load      -> true;
        none      -> true;
        _         -> false
    end.

split_escript_name(File) when is_list(File) ->
    Label = filename:basename(File, ".escript"),
    {list_to_atom("*escript* " ++ Label), Label}.

default_escript_app(File) ->
    {Name, Label} = split_escript_name(File),
    App = default_app(Name, File),
    App#app{is_escript = true,
	    label = Label,
	    info = missing_app_info("")}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Apps is a list of #app records - sorted on #app.name - containing
%% only the apps that have specific configuration (e.g. in the config
%% file)
refresh(#state{sys=Sys} = S) ->
    RootDir = filename:absname(Sys#sys.root_dir),
    LibDirs = [filename:absname(D) || D <- Sys#sys.lib_dirs],
    Escripts = [filename:absname(E) || E <- Sys#sys.escripts],

    %% Read all lib dirs and return sorted [{AppName,Dir}]
    SourceDirs = libs_to_dirs(RootDir, LibDirs),

    %% Create #app records for all apps in SourceDirs, and merge with
    %% list of apps from config.
    MergedApps = merge_app_dirs(SourceDirs, Sys#sys.apps),

    %% For each escript, find all related files and convert to #app
    %% and #mod records
    {AllApps, Status2} = escripts_to_apps(Escripts, MergedApps, {ok,[]}),

    %% Make sure correct version of each application is used according
    %% to the user configuration.
    %% Then find all modules and their dependencies and set user
    %% configuration per module if it exists.
    {RefreshedApps, Status3} = refresh_apps(Sys#sys.apps, AllApps, [],
					    true, Status2),

    %% Make sure erts exists in app list and has a version (or warn)
    {PatchedApps, Status4} = patch_erts_version(RootDir, RefreshedApps, Status3),

    %% Update #sys and return
    Escripts2 = [A#app.active_dir || A <- PatchedApps, A#app.is_escript],
    Sys2 = Sys#sys{root_dir = RootDir,
		   lib_dirs = LibDirs,
		   escripts = Escripts2},
    {S#state{sys=Sys2}, PatchedApps, Status4}.

patch_erts_version(RootDir, Apps, Status) ->
    AppName = erts,
    case lists:keyfind(AppName, #app.name, Apps) of
        #app{vsn = Vsn} = Erts ->
            LocalRoot = code:root_dir(),
            if
                LocalRoot =:= RootDir, Vsn =:= "" ->
                    Vsn2 = erlang:system_info(version),
                    Erts2 = Erts#app{vsn = Vsn2, label = "erts-" ++ Vsn2},
                    Apps2 = lists:keystore(AppName, #app.name, Apps, Erts2),
                    {Apps2, Status};
                Vsn =:= "" ->
                    {Apps, reltool_utils:add_warning("erts has no version",[],
						     Status)};
                true ->
                    {Apps, Status}
            end;
        false ->
	    reltool_utils:throw_error(
	      "erts cannot be found in the root directory ~tp", [RootDir])
    end.

libs_to_dirs(RootDir, LibDirs) ->
    case file:list_dir(RootDir) of
        {ok, RootFiles} ->
            RootLibDir = filename:join([RootDir, "lib"]),
            AllLibDirs = [RootLibDir | LibDirs],
            case AllLibDirs -- lists:usort(AllLibDirs) of
                [] ->
                    Fun = fun(Base) ->
                                  AppDir = filename:join([RootLibDir, Base]),
                                  true = filelib:is_dir(
                                           filename:join([AppDir, "ebin"]),
                                           erl_prim_loader),
                                  AppDir
                          end,
                    ErtsFiles = [{erts, Fun(F)} || F <- RootFiles,
						   lists:prefix("erts", F)],
                    app_dirs2(AllLibDirs, [ErtsFiles]);
                [Duplicate | _] ->
		    reltool_utils:throw_error("Duplicate library: ~tp",[Duplicate])
            end;
        {error, Reason} ->
	    reltool_utils:throw_error("Missing root library ~tp: ~ts",
				      [RootDir,file:format_error(Reason)])
    end.

app_dirs2([Lib | Libs], Acc) ->
    case file:list_dir(Lib) of
        {ok, Files} ->
            Filter =
                fun(Base) ->
                        AppDir = filename:join([Lib, Base]),
                        EbinDir = filename:join([AppDir, "ebin"]),
                        case filelib:is_dir(EbinDir, erl_prim_loader) of
                            true ->
				Name = find_app_name(Base,EbinDir),
                                case Name of
                                    erts -> false;
                                    _    -> {true, {Name, AppDir}}
                                end;
                            false ->
                                false
                        end
                end,
            Files2 = lists:zf(Filter, Files),
            app_dirs2(Libs, [Files2 | Acc]);
        {error, Reason} ->
	    reltool_utils:throw_error("Illegal library ~tp: ~ts",
				      [Lib, file:format_error(Reason)])
    end;
app_dirs2([], Acc) ->
    lists:sort(lists:append(Acc)).

find_app_name(Base,EbinDir) ->
    {ok,EbinFiles} = erl_prim_loader:list_dir(EbinDir),
    AppFile =
	case [F || F <- EbinFiles, filename:extension(F)=:=".app"] of
	    [AF] ->
		AF;
	    _ ->
		undefined
	end,
    find_app_name1(Base,AppFile).

find_app_name1(Base,undefined) ->
    {Name,_} = reltool_utils:split_app_name(Base),
    Name;
find_app_name1(_Base,AppFile) ->
    list_to_atom(filename:rootname(AppFile)).

get_vsn_from_dir(AppName,Base) ->
    Prefix = atom_to_list(AppName) ++ "-",
    case lists:prefix(Prefix,Base) of
	true ->
	    lists:nthtail(length(Prefix),Base);
	false ->
	    ""
    end.


escripts_to_apps([Escript | Escripts], Apps, Status) ->
    {EscriptAppName, _Label} = split_escript_name(Escript),
    Ext = code:objfile_extension(),

    %% First find all .app files and associate the app name to the app
    %% label - this is in order to now which application a module
    %% belongs to in the next round.
    AppFun = fun(FullName, _GetInfo, _GetBin, AppFiles) ->
		     Components = filename:split(FullName),
		     case Components of
			 [AppLabel, "ebin", File] ->
			     case filename:extension(File) of
				 ".app" ->
				     [{AppLabel,File}|AppFiles];
				 _ ->
				     AppFiles
			     end;
			 _ ->
			     AppFiles
		     end
	     end,
    AppFiles =
	case reltool_utils:escript_foldl(AppFun, [], Escript) of
	    {ok, AF} ->
		AF;
	    {error, Reason1} ->
		reltool_utils:throw_error("Illegal escript ~tp: ~tp",
					  [Escript,Reason1])
	end,

    %% Next, traverse all files...
    Fun = fun(FullName, _GetInfo, GetBin, {FileAcc, StatusAcc}) ->
                  Components = filename:split(FullName),
                  case Components of
                      [AppLabel, "ebin", File] ->
                          case filename:extension(File) of
                              ".app" ->
				  AppName =
				      list_to_atom(filename:rootname(File)),
                                  DefaultVsn =
				      get_vsn_from_dir(AppName,AppLabel),
                                  AppFileName =
				      filename:join([Escript, FullName]),
                                  Dir = filename:join([Escript, AppName]),
                                  {Info, StatusAcc2} =
                                      read_app_info(GetBin(),
						    AppFileName,
						    AppName,
                                                    Dir,
                                                    ok,
						    DefaultVsn,
						    Status),
                                  {[{AppName, app, Dir, Info} | FileAcc],
				   StatusAcc2};
                              E when E =:= Ext ->
				  AppFile =
				      proplists:get_value(AppLabel,AppFiles),
				  AppName = find_app_name1(AppLabel,AppFile),
                                  Mod = init_mod(AppName,
						 File,
						 {File, GetBin()},
						 Ext),
                                  Dir = filename:join([Escript, AppName]),
                                  {[{AppName, mod, Dir, Mod} | FileAcc],
				   StatusAcc};
                              _ ->
                                  {FileAcc, StatusAcc}
                          end;
                      ["."] ->
                          Bin = GetBin(),
                          {ok, {ModName, _}} = beam_lib:version(Bin),
                          ModStr = atom_to_list(ModName) ++ Ext,
                          Mod = init_mod(EscriptAppName,
					 ModStr,
					 {ModStr, GetBin()},
					 Ext),
                          {[{EscriptAppName, mod, Escript, Mod} | FileAcc],
			   StatusAcc};
                      [File] ->
                          case filename:extension(File) of
                              E when E =:= Ext ->
                                  Mod = init_mod(EscriptAppName,
						 File,
						 {File, GetBin()},
						 Ext),
                                  {[{EscriptAppName, mod, File, Mod} | FileAcc],
				   StatusAcc};
                              _ ->
                                  {FileAcc, StatusAcc}
                          end;
                      _ ->
                          {FileAcc, StatusAcc}
                  end
          end,

    case reltool_utils:escript_foldl(Fun, {[], Status}, Escript) of
	{ok, {Files, Status2}} ->
	    EscriptApp =
		case lists:keyfind(EscriptAppName,#app.name,Apps) of
		    false -> default_escript_app(Escript);
		    EA    -> EA
		end,
	    {Apps2, Status3} =
		escript_files_to_apps(EscriptAppName,
				      lists:sort(Files),
				      [EscriptApp],
				      Apps,
				      Status2),
	    escripts_to_apps(Escripts, Apps2, Status3);
	{error, Reason2} ->
	    reltool_utils:throw_error("Illegal escript ~tp: ~tp",
				      [Escript,Reason2])
    end;
escripts_to_apps([], Apps, Status) ->
    {Apps, Status}.

%% Assume that all files for an app are in consecutive order
%% Assume the app info is before the mods
escript_files_to_apps(EscriptAppName,
		      [{AppName, Type, Dir, ModOrInfo} | Files],
		      Acc,
		      Apps,
		      Status) ->
    {NewAcc,Status3} =
	case Type of
	    mod ->
		case Acc of
		    [App | Acc2] when App#app.name =:= ModOrInfo#mod.app_name ->
			Mods = lists:ukeymerge(#mod.name,
					       [ModOrInfo],
					       App#app.mods),
			{[App#app{mods = Mods} | Acc2], Status};
		    Acc ->
			{NewApp, Status2} = init_escript_app(AppName,
							     EscriptAppName,
							     Dir,
							     missing_app_info(""),
							     [ModOrInfo],
							     Apps,
							     Status),
			{[NewApp | Acc], Status2}
		end;
           app ->
		{App, Status2} = init_escript_app(AppName,
						  EscriptAppName,
						  Dir,
						  ModOrInfo,
						  [],
						  Apps,
						  Status),
		{[App | Acc], Status2}
	end,
    escript_files_to_apps(EscriptAppName, Files, NewAcc, Apps, Status3);
escript_files_to_apps(_EscriptAppName, [], Acc, Apps, Status) ->
    {lists:ukeymerge(#app.name, lists:reverse(Acc), Apps), Status}.

init_escript_app(AppName, EscriptAppName, Dir, Info, Mods, Apps, Status) ->
    App1 = default_app(AppName, Dir),
    IsEscript =
	if AppName=:=EscriptAppName -> true;
	   true -> {inlined, EscriptAppName}
	end,
    InclCond = (lists:keyfind(EscriptAppName,#app.name,Apps))#app.incl_cond,
    App2 = App1#app{is_escript = IsEscript,
		    label = filename:basename(Dir, ".escript"),
		    info = Info,
		    mods = Mods,
		    active_dir = Dir,
		    sorted_dirs = [Dir],
		    incl_cond = InclCond},% inlined apps inherit incl from escript
    case lists:keymember(AppName, #app.name, Apps) of
        true ->
	    reltool_utils:throw_error(
	      "~w: Application name clash. Escript ~tp contains application ~w.",
	      [AppName,Dir,AppName]);
        false ->
            {App2, Status}
    end.

merge_app_dirs([{Name, Dir} | Rest], Apps) ->
    App =
        case lists:keyfind(Name, #app.name, Apps) of
            false ->
		default_app(Name, Dir);
            OldApp ->
		SortedDirs = lists:umerge(fun reltool_utils:app_dir_test/2,
					  [Dir], OldApp#app.sorted_dirs),
                OldApp#app{sorted_dirs = SortedDirs}
        end,
    Apps2 = lists:ukeymerge(#app.name, [App], Apps),
    merge_app_dirs(Rest, Apps2);
merge_app_dirs([], Apps) ->
    set_active_dirs(Apps).

%% First dir, i.e. the one with highest version, is set to active dir,
%% unless a specific dir is given in config
set_active_dirs([#app{use_selected_vsn = dir} = App | Apps]) ->
    [App | set_active_dirs(Apps)];
set_active_dirs([#app{sorted_dirs = [ActiveDir|_]} = App | Apps]) ->
    [App#app{active_dir = ActiveDir} | set_active_dirs(Apps)];
set_active_dirs([#app{sorted_dirs = []} = App | Apps]) ->
    [App#app{active_dir = undefined} | set_active_dirs(Apps)];
set_active_dirs([]) ->
    [].


default_app(Name, Dir) ->
    App = default_app(Name),
    App#app{active_dir = Dir,
            sorted_dirs = [Dir]}.

default_app(Name) ->
    #app{name = Name,
         is_escript = false,
         sorted_dirs = [],
         mods = [],
         status = missing}.



refresh_apps(ConfigApps, [New | NewApps], Acc, Force, Status) ->
    {New2, Status3} =
	case lists:keymember(New#app.name,#app.name,ConfigApps) of
	    true ->
		%% There is user defined config for this application, make
		%% sure that the application exists and that correct
		%% version is used. Set active directory.
		{Info, ActiveDir, Status2} = ensure_app_info(New, Status),
		OptLabel =
		    case Info#app_info.vsn =:= New#app.vsn of
			true -> New#app.label;
			false -> undefined % Cause refresh
		    end,
		refresh_app(New#app{label = OptLabel,
				    active_dir = ActiveDir,
				    vsn = Info#app_info.vsn,
				    info = Info},
			    Force,
			    Status2);
	    false ->
		%% There is no user defined config for this
		%% application. This means that the app is found in the
		%% lib dirs, and that the highest version shall be
		%% used. I.e. the active_dir and vsn are already correct
		%% from merge_app_dirs.
		refresh_app(New, Force, Status)
	end,
    refresh_apps(ConfigApps, NewApps, [New2 | Acc], Force, Status3);
refresh_apps(_ConfigApps, [], Acc, _Force, Status) ->
    {lists:reverse(Acc), Status}.

ensure_app_info(#app{is_escript = IsEscript, active_dir = Dir, info = Info},
		Status)
  when IsEscript=/=false ->
    %% Escript or application which is inlined in an escript
    {Info, Dir, Status};
ensure_app_info(#app{name = Name, sorted_dirs = []} = App, Status) ->
    Reason = "~w: Missing application directory.",
    case App of
        #app{incl_cond = exclude, status = missing, active_dir = Dir} ->
            Status2 = reltool_utils:add_warning(Reason, [Name], Status),
            {missing_app_info(""), Dir, Status2};
        _ ->
            reltool_utils:throw_error(Reason, [Name])
    end;
ensure_app_info(#app{name = Name,
		     vsn = Vsn,
		     use_selected_vsn = UseSelectedVsn,
		     active_dir = ActiveDir,
		     sorted_dirs = Dirs,
                     info = undefined,
                     status = AppStatus},
		Status) ->
    ReadInfo =
        fun(Dir, StatusAcc) ->
                Base = get_base(Name, Dir),
                Ebin = filename:join([Dir, "ebin"]),
                DefaultVsn = get_vsn_from_dir(Name,Base),
                AppFile = filename:join([Ebin, atom_to_list(Name) ++ ".app"]),
                read_app_info(AppFile, AppFile, Name, ActiveDir,
                              AppStatus, DefaultVsn, StatusAcc)
        end,
    {AllInfo, Status2} = lists:mapfoldl(ReadInfo, Status, Dirs),
    AllVsns = [I#app_info.vsn || I <- AllInfo],
    Status3 =
        case AllVsns -- lists:usort(AllVsns) of
            [] ->
                %% No redundant info
                Status2;
            [BadVsn | _] ->
		reltool_utils:throw_error(
		  "~w: Application version clash. "
		  "Multiple directories contain version ~tp.",
		  [Name,BadVsn])
        end,
    FirstInfo = hd(AllInfo),
    FirstDir = hd(Dirs),
    if
        UseSelectedVsn =:= dir ->
	    if ActiveDir =:= FirstDir ->
		    {FirstInfo, FirstDir, Status3};
	       true ->
		    Info = find_dir(ActiveDir, AllInfo, Dirs),
		    {Info, ActiveDir, Status3}
	    end;
        UseSelectedVsn =:= vsn ->
	    if Vsn =:= FirstInfo#app_info.vsn ->
		    {FirstInfo, FirstDir, Status3};
	       true ->
		    case find_vsn(Vsn, AllInfo, Dirs) of
			{Info, VsnDir} ->
			    {Info, VsnDir, Status3};
			false ->
			    reltool_utils:throw_error(
			      "~w: No application directory contains "
			      "selected version ~tp", [Name,Vsn])
		    end
	    end;
	true ->
            {FirstInfo, FirstDir, Status3}
    end;
ensure_app_info(#app{active_dir = Dir, info = Info}, Status) ->
    {Info, Dir, Status}.

find_vsn(Vsn, [#app_info{vsn = Vsn} = Info | _], [Dir | _]) ->
    {Info, Dir};
find_vsn(Vsn, [_ | MoreInfo], [_ | MoreDirs]) ->
    find_vsn(Vsn, MoreInfo, MoreDirs);
find_vsn(_, [], []) ->
    false.

find_dir(Dir, [Info | _], [Dir | _]) ->
    Info;
find_dir(Dir, [_ | MoreInfo], [_ | MoreDirs]) ->
    find_dir(Dir, MoreInfo, MoreDirs).

get_base(_Name, Dir) ->
    filename:basename(Dir).

sys_all_apps(#state{app_tab=AppTab, sys=Sys}) ->
    Sys#sys{apps = ets:match_object(AppTab,'_')}.

config_and_refresh(OldS, Fun) ->
    try
	S = Fun(),
	{S2, Apps, Status2} = refresh(S),
	%% Analyse will write to app_tab and mod_tab, so we first
	%% backup these tables and clear them
	Backup = backup(OldS),
	try
	    Status3 = analyse(S2, Apps, Status2),
	    S3 = save_old(OldS, S2, Backup, Status3),
	    {S3, Status3}
	catch throw:{error,_} = Error1 ->
		restore(Backup,OldS),
		throw(Error1)
	end
    catch throw:{error,_} = Error2 ->
	    {OldS, Error2}
    end.


backup(S) ->
    Apps = ets:tab2list(S#state.app_tab),
    Mods = ets:tab2list(S#state.mod_tab),
    ets:delete_all_objects(S#state.app_tab),
    ets:delete_all_objects(S#state.mod_tab),
    ets:delete_all_objects(S#state.mod_used_by_tab), %tmp tab, no backup needed
    {Apps,Mods}.

restore({Apps,Mods}, S) ->
    insert_all(S#state.app_tab,Apps),
    insert_all(S#state.mod_tab,Mods).

save_old(#state{status=OldStatus,sys=OldSys},NewS,{OldApps,OldMods},NewStatus) ->
    ets:delete_all_objects(NewS#state.old_app_tab),
    ets:delete_all_objects(NewS#state.old_mod_tab),
    insert_all(NewS#state.old_app_tab,OldApps),
    insert_all(NewS#state.old_mod_tab,OldMods),
    NewS#state{old_sys=OldSys,
	       old_status=OldStatus,
	       status=NewStatus}.

insert_all(Tab,Items) ->
    lists:foreach(fun(Item) -> ets:insert(Tab,Item) end, Items).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% sys callbacks

system_continue(_Parent, _Debug, S) ->
    ?MODULE:loop(S).

system_terminate(Reason, _Parent, _Debug, _S) ->
    exit(Reason).

system_code_change(S,_Module,_OldVsn,_Extra) ->
    {ok, S}.
