%%----------------------------------------------------
%% Erlang模块热更新到所有线路（包括server的回调函数，如果对state有影响时慎用）
%%
%% 检查：u:c()                %% 列出前5分钟内编译过的文件
%%       u:c(N)               %% 列出前N分钟内编译过的文件
%%
%% 更新：u:u()                %% 更新前5分钟内编译过的文件
%%       u:u(N)               %% 更新前N分钟内编译过的文件
%%       u:u([mod_xx, ...])   %% 指定模块（不带后缀名）
%%       u:u(m)               %% 编译并加载文件
%%
%% Tips: u - update, c - check
%%----------------------------------------------------
-module(u).
-compile(export_all).
-include_lib("kernel/include/file.hrl").
-include("def_cache.hrl").
-include("common.hrl").
-include("node.hrl").

c() ->
    c(5).
c(S) when is_integer(S) ->
    case file:list_dir(".") of
        {ok, FileList} ->
            Files = get_new_file(FileList, S * 60),
            info("---------check modules---------~n~w~n=========check modules=========", [Files]);
        Any -> info("Error Dir: ~w", [Any])
    end;
c(_) -> info("ERROR======> Badarg", []).

admin()->
    spawn(fun() -> u(m) end),
    ok.

u() ->
    u(5).
u(m) ->
    StartTime = utime:unixtime(),
    info("----------makes----------", []),
    c:cd("../"),
    % Files = make:all(),
    make:all(),
    c:cd("ebin"),
    EndTime = utime:unixtime(),
    Time = EndTime - StartTime,
    % info("Recompile: ~ts\n",[Files]),
    info("Make Time : ~w s", [Time]),
    u(Time / 60);
u(S) when is_number(S) ->
    c:cd("../ebin"),
    case file:list_dir(".") of
        {ok, FileList} ->
            Files = get_new_file(FileList, umath:ceil(S * 60) + 3),
            info("---------modules---------~n~w~n", [Files]),
            load(Files),
            node_mgr:apply_cast_other_nodes(u, load, [Files]),
            ok;
        Any -> info("Error Dir: ~w", [Any])
    end;
u(Files) when is_list(Files) ->
    info("---------modules---------~n~p~n", [Files]),
    load(Files),
    node_mgr:apply_cast_other_nodes(u, load, [Files]),
    ok;
u(md5) ->
    ModuleList = find_hot_modules(),
    u(ModuleList);
u(_) -> info("ERROR======> Badarg", []).

h() ->
    u(md5).

%% m(['src/data/*','src/lib/lib_goods.erl'])
m(Files) when is_list(Files) ->
    StartTime = utime:unixtime(),
    info("----------makes----------~n~p~n", [Files]),
    c:cd("../"),
    Res = make:files(Files, [debug_info,{i, "include"},{outdir, "ebin"}]),
    c:cd("ebin"),
    EndTime = utime:unixtime(),
    Time = EndTime - StartTime,
    info("Make Time : ~w s", [Time]),
    Res.

info(V) ->
    info(V, []).
info(V, P) ->
    io:format(V ++ "~n", P).

get_new_file(Files, S) ->
    get_new_file(Files, S, []).
get_new_file([], _S, Result) -> Result;
get_new_file([H | T], S, Result) ->
    NewResult = case string:tokens(H, ".") of
        [Left, Right] when Right =:= "beam" ->
            case file:read_file_info(H) of
                {ok, FileInfo} ->
                    Now = calendar:local_time(),
                    case calendar:time_difference(FileInfo#file_info.mtime, Now) of
                        {Days, Times} ->
                            Seconds = calendar:time_to_seconds(Times),
                            case Days =:= 0 andalso Seconds < S of
                                true ->
                                    FileName = list_to_atom(Left),
                                    [FileName | Result];
                                false -> Result
                            end;
                        _ -> Result
                    end;
                _ -> Result
            end;
        _ -> Result
    end,
    get_new_file(T, S, NewResult).

load(Files) ->
    io:format("[node : ~ts]~n", [node()]),
    load(Files, []).

load([], Result) -> logfile(lists:reverse(Result)), ok;
load([FileName | T], Result) ->
    FR = case c:l(FileName) of
        {error, Error} ->
            info("load ~w fail reason=~p", [FileName, Error]),
            {fail, FileName, Error};
        {module, _} ->
            info("loaded: ~w", [FileName]),
            mod_cache:refresh(?CACHE_REFRESH_HOT, FileName),
            {success, FileName}
    end,
    load(T, [FR|Result]).

logfile(FileUpResult) ->
    {{Y, M, D},{H, I, S}} = calendar:local_time(),
    {ok, Path} = file:get_cwd(),
    file:set_cwd(filename:join(filename:dirname(Path), "../config")),
    LogPath = config:get_log_path(),
    file:set_cwd(Path),
    File = lists:concat([LogPath, "/", "u_", Y, "_", M, "_", D, ".txt"]),
    {ok, IoDevice} = file:open(File, [write, append]),
    io:format(IoDevice, "~w-~w-~w ~w:~w:~w node(~w) load file result:~w~n", [Y,M,D,H,I,S,node(),FileUpResult]),
    file:close(IoDevice).



%% ---------------------------------------------------------------------------
%% @doc 查找需要热更的模块
-spec find_hot_modules() -> ModuleList when
    ModuleList      :: [Module :: atom()].
%% ---------------------------------------------------------------------------
find_hot_modules() ->
    c:cd("../ebin"),
    case file:list_dir(".") of
        {ok, FileList} ->
            find_hot_modules(FileList, []);
        Any ->
            info("Error Dir: ~w", [Any]),
            []
    end.

find_hot_modules([], ModuleList) ->
    ModuleList;
find_hot_modules([File | T], ModuleList) ->
    case filename:extension(File) =:= ".beam" of
        true ->
            NewModuleList = case need_to_hot_or_not(File) of
                {true, Module} -> [Module | ModuleList];
                _ -> ModuleList
            end,
            find_hot_modules(T, NewModuleList);
        false ->
            find_hot_modules(T, ModuleList)
    end.

%% 判断文件是否需要热更
need_to_hot_or_not(BeamFile) ->
    Module = list_to_atom(filename:basename(BeamFile, ".beam")),
    BeamPath = "../ebin/" ++ BeamFile,
    MD5 = Module:module_info(md5),
    case beam_lib:md5(BeamPath) of
        {ok, {Module, NewMD5}} when NewMD5 =/= MD5 -> %% MD5不一致才热更
            {true, Module};
        {error, beam_lib, Reason} -> %% 报错不热更
            info("Error beam_lib: ~w", [Reason]),
            false;
        _ -> %% 其他情况一概不热更
            false
    end.
