-module(eSync).

-behaviour(es_gen_ipc).

-include_lib("kernel/include/file.hrl").

-compile(inline).
-compile({inline_size, 128}).

-define(IIF(Cond, Ret1, Ret2), (case Cond of true -> Ret1; _ -> Ret2 end)).
-define(LOG_ON(Val), Val == true; Val == all; Val == skip_success; is_list(Val), Val =/= []).

-define(Log, log).
-define(compileCmd, compileCmd).
-define(extraDirs, extraDirs).
-define(descendant, descendant).
-define(onMSyncFun, onMSyncFun).
-define(onCSyncFun, onCSyncFun).
-define(swSyncNode, swSyncNode).
-define(isJustMem, isJustMem).
-define(debugInfoKeyFun, debugInfoKeyFun).

-define(DefCfgList, [{?Log, all}, {?compileCmd, undefined}, {?extraDirs, undefined}, {?descendant, fix}, {?onMSyncFun, undefined}, {?onCSyncFun, undefined}, {?swSyncNode, false}, {?isJustMem, false}, {?debugInfoKeyFun, undefined}]).

-define(esCfgSync, esCfgSync).
-define(rootSrcDir, <<"src">>).

-define(logSuccess(Format), canLog(success) andalso error_logger:info_msg("eSync[~p:~p|~p] " ++ Format, [?MODULE, ?FUNCTION_NAME, ?LINE])).
-define(logSuccess(Format, Args), canLog(success) andalso error_logger:info_msg("eSync[~p:~p|~p] " ++ Format, [?MODULE, ?FUNCTION_NAME, ?LINE] ++ Args)).

-define(logErrors(Format), canLog(errors) andalso error_logger:info_msg("eSync[~p:~p|~p] " ++ Format, [?MODULE, ?FUNCTION_NAME, ?LINE])).
-define(logErrors(Format, Args), canLog(errors) andalso error_logger:info_msg("eSync[~p:~p|~p] " ++ Format, [?MODULE, ?FUNCTION_NAME, ?LINE] ++ Args)).

-define(logWarnings(Format), canLog(warnings) andalso error_logger:info_msg("eSync[~p:~p|~p] " ++ Format, [?MODULE, ?FUNCTION_NAME, ?LINE])).
-define(logWarnings(Format, Args), canLog(warnings) andalso error_logger:info_msg("eSync[~p:~p|~p] " ++ Format, [?MODULE, ?FUNCTION_NAME, ?LINE] ++ Args)).

-export([
   start/2,
   start/0,
   stop/0,
   run/0
]).

%% API
-export([
   start_link/0,
   rescan/0,
   pause/0,
   unpause/0,
   setLog/1,
   getLog/0,
   curInfo/0,
   getOnMSync/0,
   setOnMSync/1,
   getOnCSync/0,
   setOnCSync/1,
   swSyncNode/1
]).

%% es_gen_ipc callbacks
-export([
   init/1,
   handleCall/4,
   handleAfter/3,
   handleCast/3,
   handleInfo/3,
   handleOnevent/4,
   terminate/3
]).

start(_StartType, _StartArgs) ->
   start_link().

start() ->
   application:ensure_all_started(eSync).

stop() ->
   application:stop(eSync).

run() ->
   case start() of
      {ok, _Started} ->
         unpause(),
         ok;
      {error, Reason} ->
         ?logErrors("start eSync error:~p~n", [Reason])
   end.

-define(SERVER, ?MODULE).
-define(None, 0).

-record(state, {
   port = undefined
   , onMSyncFun = undefined
   , onCSyncFun = undefined
   , swSyncNode = false
   , srcFiles = #{} :: map()
   , hrlFiles = #{} :: map()
   , configs = #{} :: map()
   , beams = #{} :: map()
}).

%% ************************************  API start ***************************
rescan() ->
   es_gen_ipc:cast(?SERVER, miRescan),
   ?logSuccess("start rescaning source files..."),
   ok.

unpause() ->
   es_gen_ipc:cast(?SERVER, miUnpause),
   ok.

pause() ->
   es_gen_ipc:cast(?SERVER, miPause),
   ?logSuccess("Pausing eSync. Call eSync:run() to restart"),
   ok.

curInfo() ->
   es_gen_ipc:call(?SERVER, miCurInfo).

setLog(T) when ?LOG_ON(T) ->
   setEnv(log, T),
   loadCfg(),
   ?logSuccess("Console Notifications Enabled"),
   ok;
setLog(_) ->
   setEnv(log, none),
   loadCfg(),
   ?logSuccess("Console Notifications Disabled"),
   ok.

getLog() ->
   ?esCfgSync:getv(log).

swSyncNode(IsSync) ->
   es_gen_ipc:cast(?SERVER, {miSyncNode, IsSync}),
   ok.

getOnMSync() ->
   es_gen_ipc:call(?SERVER, miGetOnMSync).

setOnMSync(Fun) ->
   es_gen_ipc:call(?SERVER, {miSetOnMSync, Fun}).

getOnCSync() ->
   es_gen_ipc:call(?SERVER, miGetOnCSync).

setOnCSync(Fun) ->
   es_gen_ipc:call(?SERVER, {miSetOnCSync, Fun}).

%% ************************************  API end   ***************************
start_link() ->
   es_gen_ipc:start_link({local, ?SERVER}, ?MODULE, ?None, []).

%% status :: waiting | running | pause
init(_Args) ->
   erlang:process_flag(trap_exit, true),
   loadCfg(),
   {ok, waiting, #state{onMSyncFun = ?esCfgSync:getv(?onMSyncFun), onCSyncFun = ?esCfgSync:getv(?onCSyncFun), swSyncNode = ?esCfgSync:getv(?swSyncNode)}, {doAfter, ?None}}.

handleAfter(?None, waiting, State) ->
   %% 启动port 发送监听目录信息
   PortName = fileSyncPath("fileSync"),
   Opts = [{packet, 4}, binary, exit_status, use_stdio],
   Port = erlang:open_port({spawn_executable, PortName}, Opts),
   {kpS, State#state{port = Port}, {sTimeout, 4000, waitConnOver}}.

handleCall(miGetOnMSync, _, #state{onMSyncFun = OnMSyncFun} = State, _From) ->
   {reply, OnMSyncFun, State};
handleCall({miSetOnMSync, Fun}, _, State, _From) ->
   {reply, ok, State#state{onMSyncFun = Fun}};
handleCall(miGetOnCSync, _, #state{onCSyncFun = OnCSyncFun} = State, _From) ->
   {reply, OnCSyncFun, State};
handleCall({miSetOnCSync, Fun}, _, State, _From) ->
   {reply, ok, State#state{onCSyncFun = Fun}};
handleCall(miCurInfo, Status, State, _Form) ->
   {reply, {Status, erlang:get(), State}, State};
handleCall(_Request, _, _State, _From) ->
   kpS_S.

handleCast(miPause, running, State) ->
   {nextS, pause, State};
handleCast(miUnpause, pause, State) ->
   {nextS, running, State};
handleCast({miSyncNode, IsSync}, _, State) ->
   case IsSync of
      true ->
         {kpS, State#state{swSyncNode = true}};
      _ ->
         {kpS, State#state{swSyncNode = false}}
   end;
handleCast(miRescan, _, State) ->
   {Srcs, Hrls, Configs, Beams} = collSrcFiles(false),
   {noreply, State#state{srcFiles = Srcs, hrlFiles = Hrls, configs = Configs, beams = Beams}, hibernate};
handleCast(_Msg, _, _State) ->
   kpS_S.

handleInfo({Port, {data, Data}}, Status, #state{srcFiles = Srcs, hrlFiles = Hrls, configs = Configs, beams = Beams, onMSyncFun = OnMSyncFun, onCSyncFun = OnCSyncFun, swSyncNode = SwSyncNode} = State) ->
   ?logSuccess("eSync data... ~p ~p~n", [Status, Data]),
   case Status of
      running ->
         FileList = binary:split(Data, <<"\r\n">>, [global]),
         %% 收集改动了beam hrl src 文件 然后执行相应的逻辑
         {CBeams, CConfigs, CHrls, CSrcs, NewSrcs, NewHrls, NewConfigs, NewBeams} = classifyChangeFile(FileList, [], [], #{}, #{}, Srcs, Hrls, Configs, Beams),
         fireOnSync(OnCSyncFun, CConfigs),
         reloadChangedMod(CBeams, SwSyncNode, OnMSyncFun, []),
         case ?esCfgSync:getv(?compileCmd) of
            undefined ->
               LastCHrls = collIncludeCHrls(maps:keys(CHrls), NewHrls, CHrls, #{}),
               NReSrcs =  collIncludeCErls(maps:keys(LastCHrls), NewSrcs, CSrcs, #{}),
               recompileChangeSrcFile(maps:iterator(NReSrcs), SwSyncNode),
               {kpS, State#state{srcFiles = NewSrcs, hrlFiles = NewHrls, configs = NewConfigs, beams = NewBeams}};
            CmdStr ->
               case maps:size(CSrcs) > 0 orelse CHrls =/= [] of
                  true ->
                     RetStr = os:cmd(CmdStr),
                     RetList = string:split(RetStr, "\n", all),
                     ?logSuccess("compile cmd:~p ~n", [CmdStr]),
                     ?logSuccess("the result: ~n ", []),
                     [
                        begin
                           ?logSuccess("~p ~n", [OneRet])
                        end || OneRet <- RetList, OneRet =/= []
                     ],
                     ok;
                  _ ->
                     ignore
               end,
               kpS_S
         end;
      _ ->
         case Data of
            <<"init">> ->
               %% port启动成功 先发送监听目录配置
               {AddExtraSrcDirs, AddOnlySrcDirs, OnlySrcDirs, DelSrcDirs} = mergeExtraDirs(false),
               AddExtraStr = string:join([filename:nativename(OneDir) || OneDir <- AddExtraSrcDirs], "|"),
               AddOnlyStr = string:join([filename:nativename(OneDir) || OneDir <- AddOnlySrcDirs], "|"),
               OnlyStr = string:join([filename:nativename(OneDir) || OneDir <- OnlySrcDirs], "|"),
               DelStr = string:join([filename:nativename(OneDir) || OneDir <- DelSrcDirs], "|"),
               AllStr = string:join([AddExtraStr, AddOnlyStr, OnlyStr, DelStr], "\r\n"),
               erlang:port_command(Port, AllStr),
               ?logSuccess("eSync connect fileSync success..."),
               %% 然后收集一下监听目录下的src文件
               {BSrcs, BHrls, BConfigs, BBeams} = collSrcFiles(true),
               ?logSuccess("eSync coll src files over..."),
               {nextS, running, State#state{srcFiles = BSrcs, hrlFiles = BHrls, configs = BConfigs, beams = BBeams}, {isHib, true}};
            _ ->
               ?logErrors("error, receive unexpect port msg ~p~n", [Data]),
               kpS_S
         end
   end;
handleInfo({Port, closed}, running, #state{port = Port} = _State) ->
   ?logErrors("receive port closed ~n"),
   {nextS, port_close, _State};
handleInfo({'EXIT', Port, Reason}, running, #state{port = Port} = _State) ->
   ?logErrors("receive port exit Reason:~p ~n", [Reason]),
   {nextS, {port_EXIT, Reason}, _State};
handleInfo({Port, {exit_status, Status}}, running, #state{port = Port} = _State) ->
   ?logErrors("receive port exit_status Status:~p ~p ~n", [Status, Port]),
   {nextS, {port_exit_status, Status}, _State};
handleInfo({'EXIT', _Pid, _Reason}, running, _State) ->
   kpS_S;
handleInfo(_Msg, _, _State) ->
   ?logErrors("receive unexpect msg:~p ~n", [_Msg]),
   kpS_S.

handleOnevent(sTimeout, waitConnOver, Status, State) ->
   ?logErrors("failed to connect the fileSync to stop stauts:~p state:~p ~n", [Status, State]),
   stop;
handleOnevent(_EventType, _EventContent, _Status, _State) ->
   kpS_S.

terminate(_Reason, _Status, _State) ->
   ok.

%% ************************************************* utils start *******************************************************
getModSrcDir(Module) ->
   case code:is_loaded(Module) of
      {file, _} ->
         try
            %% Get some module info...
            Props = Module:module_info(compile),
            Source = proplists:get_value(source, Props, ""),
            %% Ensure that the file exists, is a decendent of the tree, and how to deal with that
            IsFile = filelib:is_regular(Source),
            IsDescendant = isDescendent(Source),
            Descendant = ?esCfgSync:getv(?descendant),
            LastSource =
               case {IsFile, IsDescendant, Descendant} of
                  %% is file and descendant, we're good to go
                  {true, true, _} -> Source;
                  %% is not a descendant, but we allow them, so good to go
                  {true, false, allow} -> Source;
                  %% is not a descendant, and we fix non-descendants, so let's fix it
                  {_, false, fix} -> fixDescendantSource(Source, IsFile);
                  %% Anything else, and we don't know what to do, so let's just bail.
                  _ -> undefined
               end,
            case LastSource of
               undefined ->
                  undefined;
               _ ->
                  %% Get the source dir...
                  Dir = filename:dirname(LastSource),
                  getSrcDir(Dir)
            end
         catch _ : _ ->
            undefined
         end;
      _ ->
         undefined
   end.

getModOpts(Module) ->
   case code:is_loaded(Module) of
      {file, _} ->
         try
            Props = Module:module_info(compile),
            BeamDir = filename:dirname(code:which(Module)),
            Options1 = proplists:get_value(options, Props, []),
            %% transform `outdir'
            Options2 = transformOutdir(BeamDir, Options1),
            Options3 = ensureInclude(Options2),
            %% transform the include directories
            Options4 = transformAllIncludes(Module, BeamDir, Options3),
            %% maybe_add_compile_info
            Options5 = maybeAddCompileInfo(Options4),
            %% add filetype to options (DTL, LFE, erl, etc)
            Options6 = addFileType(Module, Options5),
            Options7 = lists:keyreplace(debug_info_key, 1, Options6, debugInfoKeyFun()),
            {ok, Options7}
         catch ExType:Error ->
            ?logWarnings("~p:0: ~p looking for options: ~p. ~n", [Module, ExType, Error]),
            undefined
         end;
      _ ->
         undefined
   end.

tryGetModOpts(Module) ->
   try
      Props = Module:module_info(compile),
      BeamDir = filename:dirname(code:which(Module)),
      Options1 = proplists:get_value(options, Props, []),
      %% transform `outdir'
      Options2 = transformOutdir(BeamDir, Options1),
      Options3 = ensureInclude(Options2),
      %% transform the include directories
      Options4 = transformAllIncludes(Module, BeamDir, Options3),
      %% maybe_add_compile_info
      Options5 = maybeAddCompileInfo(Options4),
      %% add filetype to options (DTL, LFE, erl, etc)
      Options6 = addFileType(Module, Options5),
      Options7 = lists:keyreplace(debug_info_key, 1, Options6, debugInfoKeyFun()),
      {ok, Options7}
   catch _ExType:_Error ->
      undefiend
   end.

tryGetSrcOpts(SrcDir) ->
   %% Then we dig back through the parent directories until we find our include directory
   NewDirName = filename:dirname(SrcDir),
   case getOptions(NewDirName) of
      {ok, _Options} = Opts ->
         Opts;
      _ ->
         BaseName = filename:basename(SrcDir),
         IsBaseSrcDir = BaseName == ?rootSrcDir,
         case NewDirName =/= SrcDir andalso not IsBaseSrcDir of
            true ->
               tryGetSrcOpts(NewDirName);
            _ when IsBaseSrcDir ->
               try filelib:fold_files(SrcDir, ".*\\.(erl|dtl|lfe|ex)$", true,
                  fun(OneFile, Acc) ->
                     Mod = binary_to_atom(filename:basename(OneFile, filename:extension(OneFile))),
                     case tryGetModOpts(Mod) of
                        {ok, _Options} = Opts ->
                           throw(Opts);
                        _ ->
                           Acc
                     end
                  end, undefined)
               catch
                  {ok, _Options} = Opts ->
                     Opts;
                  _ExType:_Error ->
                     ?logWarnings("looking src options error ~p:~p. ~n", [_ExType, _Error]),
                     undefined
               end;
            _ ->
               undefined
         end
   end.

transformOutdir(BeamDir, Options) ->
   [{outdir, BeamDir} | proplists:delete(outdir, Options)].

ensureInclude(Options) ->
   case proplists:get_value(i, Options) of
      undefined -> [{i, "include"} | Options];
      _ -> Options
   end.

transformAllIncludes(Module, BeamDir, Options) ->
   [begin
       case Opt of
          {i, IncludeDir} ->
             {ok, SrcDir} = getModSrcDir(Module),
             {ok, IncludeDir2} = determineIncludeDir(IncludeDir, BeamDir, SrcDir),
             {i, IncludeDir2};
          _ ->
             Opt
       end
    end || Opt <- Options].

maybeAddCompileInfo(Options) ->
   case lists:member(compile_info, Options) of
      true -> Options;
      false -> addCompileInfo(Options)
   end.

addCompileInfo(Options) ->
   CompInfo = [{K, V} || {K, V} <- Options, lists:member(K, [outdir, i])],
   [{compile_info, CompInfo} | Options].

addFileType(Module, Options) ->
   Type = getFileType(Module),
   [{type, Type} | Options].

%% This will check if the given module or source file is an ErlyDTL template.
%% Currently, this is done by checking if its reported source path ends with
%% ".dtl.erl".
getFileType(Module) when is_atom(Module) ->
   Props = Module:module_info(compile),
   Source = proplists:get_value(source, Props, ""),
   getFileType(Source);

getFileType(Source) ->
   Ext = filename:extension(Source),
   Root = filename:rootname(Source),
   SecondExt = filename:extension(Root),
   case Ext of
      <<".erl">> when SecondExt =:= <<".dtl">> -> dtl;
      <<".dtl">> -> dtl;
      <<".erl">> -> erl;
      <<".lfe">> -> lfe;
      <<".ex">> -> elixir;
      ".erl" when SecondExt =:= ".dtl" -> dtl;
      ".dtl" -> dtl;
      ".erl" -> erl;
      ".lfe" -> lfe;
      ".ex" -> elixir
   end.

%% This will search back to find an appropriate include directory, by
%% searching further back than "..". Instead, it will extract the basename
%% (probably "include" from the include pathfile, and then search backwards in
%% the directory tree until it finds a directory with the same basename found
%% above.
determineIncludeDir(IncludeDir, BeamDir, SrcDir) ->
   IncludeBase = filename:basename(IncludeDir),
   case determineIncludeDirFromBeamDir(IncludeBase, IncludeDir, BeamDir) of
      {ok, _Dir} = RetD -> RetD;
      undefined ->
         {ok, Cwd} = file:get_cwd(),
         % Cwd2 = normalizeCaseWindowsDir(Cwd),
         % SrcDir2 = normalizeCaseWindowsDir(SrcDir),
         % IncludeBase2 = normalizeCaseWindowsDir(IncludeBase),
         case findIncludeDirFromAncestors(SrcDir, Cwd, IncludeBase) of
            {ok, _Dir} = RetD -> RetD;
            undefined -> {ok, IncludeDir} %% Failed, just stick with original
         end
   end.

%% First try to see if we have an include file alongside our ebin directory, which is typically the case
determineIncludeDirFromBeamDir(IncludeBase, IncludeDir, BeamDir) ->
   BeamBasedIncDir = filename:join(filename:dirname(BeamDir), IncludeBase),
   case filelib:is_dir(BeamBasedIncDir) of
      true -> {ok, BeamBasedIncDir};
      false ->
         BeamBasedIncDir2 = filename:join(filename:dirname(BeamDir), IncludeDir),
         case filelib:is_dir(BeamBasedIncDir2) of
            true -> {ok, BeamBasedIncDir2};
            _ ->
               undefined
         end
   end.

%% get the src dir
getRootSrcDirFromSrcDir(SrcDir) ->
   NewDirName = filename:dirname(SrcDir),
   BaseName = filename:basename(SrcDir),
   case BaseName of
      ?rootSrcDir ->
         NewDirName;
      _ ->
         case NewDirName =/= SrcDir of
            true ->
               getRootSrcDirFromSrcDir(NewDirName);
            _ ->
               undefined
         end
   end.

%% Then we dig back through the parent directories until we find our include directory
findIncludeDirFromAncestors(Cwd, Cwd, _) -> undefined;
findIncludeDirFromAncestors("/", _, _) -> undefined;
findIncludeDirFromAncestors(".", _, _) -> undefined;
findIncludeDirFromAncestors("", _, _) -> undefined;
findIncludeDirFromAncestors(Dir, Cwd, IncludeBase) ->
   NewDirName = filename:dirname(Dir),
   AttemptDir = filename:join(NewDirName, IncludeBase),
   case filelib:is_dir(AttemptDir) of
      true ->
         {ok, AttemptDir};
      false ->
         case NewDirName =/= Dir of
            true ->
               findIncludeDirFromAncestors(NewDirName, Cwd, IncludeBase);
            _ ->
               undefined
         end
   end.

% normalizeCaseWindowsDir(Dir) ->
%    case os:type() of
%       {win32, _} -> Dir; %string:to_lower(Dir);
%       {unix, _} -> Dir
%    end.

%% This is an attempt to intelligently fix paths in modules when a
%% release is moved.  Essentially, it takes a module name and its original path
%% from Module:module_info(compile), say
%% "/some/original/path/site/src/pages/somepage.erl", and then breaks down the
%% path one by one prefixing it with the current working directory until it
%% either finds a match, or fails.  If it succeeds, it returns the Path to the
%% new Source file.
fixDescendantSource([], _IsFile) ->
   undefined;
fixDescendantSource(Path, IsFile) ->
   {ok, Cwd} = file:get_cwd(),
   PathParts = filename:split(Path),
   case makeDescendantSource(PathParts, Cwd) of
      undefined -> case IsFile of true -> Path; _ -> undefined end;
      FoundPath -> FoundPath
   end.

makeDescendantSource([], _Cwd) ->
   undefined;
makeDescendantSource([_ | T], Cwd) ->
   PathAttempt = filename:join([Cwd | T]),
   case filelib:is_regular(PathAttempt) of
      true -> PathAttempt;
      false -> makeDescendantSource(T, Cwd)
   end.

isDescendent(Path) ->
   {ok, Cwd} = file:get_cwd(),
   lists:sublist(Path, length(Cwd)) == Cwd.

%% @private Find the src directory for the specified Directory; max 15 iterations
getSrcDir(Dir) ->
   getSrcDir(Dir, 15).

getSrcDir(_Dir, 0) ->
   undefined;
getSrcDir(Dir, Ctr) ->
   HasCode = filelib:wildcard("*.erl", Dir) /= [] orelse
      filelib:wildcard("*.hrl", Dir) /= [] orelse
      filelib:wildcard("*.dtl", Dir) /= [] orelse
      filelib:wildcard("*.lfe", Dir) /= [] orelse
      filelib:wildcard("*.ex", Dir) /= [],
   if
      HasCode -> {ok, Dir};
      true -> getSrcDir(filename:dirname(Dir), Ctr - 1)
   end.

mergeExtraDirs(IsAddPath) ->
   case ?esCfgSync:getv(?extraDirs) of
      undefined ->
         {[], [], [], []};
      ExtraList ->
         FunMerge =
            fun(OneExtra, {AddExtraDirs, AddOnlyDirs, OnlyDirs, DelDirs} = AllAcc) ->
               case OneExtra of
                  {addExtra, DirsAndOpts} ->
                     Adds =
                        [
                           begin
                              case IsAddPath of
                                 true ->
                                    case proplists:get_value(outdir, Opts) of
                                       undefined ->
                                          true;
                                       Path ->
                                          ok = filelib:ensure_dir(Path),
                                          true = code:add_pathz(Path)
                                    end;
                                 _ ->
                                    ignore
                              end,
                              filename:absname(Dir)
                           end || {Dir, Opts} <- DirsAndOpts
                        ],
                     setelement(1, AllAcc, Adds ++ AddExtraDirs);
                  {addOnly, DirsAndOpts} ->
                     Adds =
                        [
                           begin
                              case IsAddPath of
                                 true ->
                                    case proplists:get_value(outdir, Opts) of
                                       undefined ->
                                          true;
                                       Path ->
                                          ok = filelib:ensure_dir(Path),
                                          true = code:add_pathz(Path)
                                    end;
                                 _ ->
                                    ignore
                              end,
                              filename:absname(Dir)
                           end || {Dir, Opts} <- DirsAndOpts
                        ],
                     setelement(2, AllAcc, Adds ++ AddOnlyDirs);
                  {only, DirsAndOpts} ->
                     Onlys =
                        [
                           begin
                              case IsAddPath of
                                 true ->
                                    case proplists:get_value(outdir, Opts) of
                                       undefined ->
                                          true;
                                       Path ->
                                          ok = filelib:ensure_dir(Path),
                                          true = code:add_pathz(Path)
                                    end;
                                 _ ->
                                    ignore
                              end,
                              filename:absname(Dir)
                           end || {Dir, Opts} <- DirsAndOpts
                        ],
                     setelement(3, AllAcc, Onlys ++ OnlyDirs);
                  {del, DirsAndOpts} ->
                     Dels =
                        [
                           begin
                              filename:absname(Dir)
                           end || {Dir, _Opts} <- DirsAndOpts
                        ],
                     setelement(4, AllAcc, Dels ++ DelDirs)
               end
            end,
         lists:foldl(FunMerge, {[], [], [], []}, ExtraList)
   end.

-define(RegExp, <<".*\\.(erl|hrl|beam|config|dtl|lfe|ex)$">>).
collSrcFiles(IsAddPath) ->
   {AddExtraSrcDirs, AddOnlySrcDirs, OnlySrcDirs, DelSrcDirs} = mergeExtraDirs(IsAddPath),
   CollFiles = filelib:fold_files(filename:absname(<<"./">>), ?RegExp, true,
      fun(OneFile, {Srcs, Hrls, Configs, Beams} = Acc) ->
         case isOnlyDir(OnlySrcDirs, OneFile) andalso (not isDelDir(DelSrcDirs, OneFile)) of
            true ->
               MTimeSec = case file:read_file_info(OneFile, [raw]) of
                  {ok, FileInfo} ->
                     dateTimeToSec(FileInfo#file_info.mtime);
                  _ ->
                     0
               end,
               %MTimeSec = dateTimeToSec(filelib:last_modified(OneFile)),
               case filename:extension(OneFile) of
                  <<".beam">> ->
                     BeamMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)),
                     setelement(4, Acc, Beams#{BeamMod => MTimeSec});
                  <<".config">> ->
                     setelement(3, Acc, Configs#{OneFile => MTimeSec});
                  <<".hrl">> ->
                     setelement(2, Acc, Hrls#{OneFile => MTimeSec});
                  <<>> ->
                     Acc;
                  _ ->
                     RootSrcDir =
                        case getRootSrcDirFromSrcDir(OneFile) of
                           undefined ->
                              filename:dirname(OneFile);
                           RetSrcDir ->
                              RetSrcDir
                        end,
                     case getOptions(RootSrcDir) of
                        undefined ->
                           Mod = binary_to_atom(filename:basename(OneFile, filename:extension(OneFile))),
                           case getModOpts(Mod) of
                              {ok, Options} ->
                                 setOptions(RootSrcDir, Options);
                              _ ->
                                 ignore
                           end;
                        _ ->
                           ignore
                     end,
                     setelement(1, Acc, Srcs#{OneFile => MTimeSec})
               end;
            _ ->
               Acc
         end
      end, {#{}, #{}, #{}, #{}}),

   FunCollAddExtra =
      fun(OneDir, FilesAcc) ->
         filelib:fold_files(?IIF(is_list(OneDir), list_to_binary(OneDir), OneDir), ?RegExp, true,
            fun(OneFile, {Srcs, Hrls, Configs, Beams} = Acc) ->
               MTimeSec = dateTimeToSec(filelib:last_modified(OneFile)),
               case filename:extension(OneFile) of
                  <<".beam">> ->
                     BeamMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)),
                     setelement(4, Acc, Beams#{BeamMod => MTimeSec});
                  <<".config">> ->
                     setelement(3, Acc, Configs#{OneFile => MTimeSec});
                  <<".hrl">> ->
                     setelement(2, Acc, Hrls#{OneFile => MTimeSec});
                  <<>> ->
                     Acc;
                  _ ->
                     setelement(1, Acc, Srcs#{OneFile => MTimeSec})
               end
            end, FilesAcc)
      end,
   AddExtraCollFiles = lists:foldl(FunCollAddExtra, CollFiles, AddExtraSrcDirs),

   FunCollAddOnly =
      fun(OneDir, FilesAcc) ->
         filelib:fold_files(?IIF(is_list(OneDir), list_to_binary(OneDir), OneDir), ?RegExp, false,
            fun(OneFile, {Srcs, Hrls, Configs, Beams} = Acc) ->
               MTimeSec = dateTimeToSec(filelib:last_modified(OneFile)),
               case filename:extension(OneFile) of
                  <<".beam">> ->
                     BeamMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)),
                     setelement(4, Acc, Beams#{BeamMod => MTimeSec});
                  <<".config">> ->
                     setelement(3, Acc, Configs#{OneFile => MTimeSec});
                  <<".hrl">> ->
                     setelement(2, Acc, Hrls#{OneFile => MTimeSec});
                  <<>> ->
                     Acc;
                  _ ->
                     setelement(1, Acc, Srcs#{OneFile => MTimeSec})
               end
            end, FilesAcc)
      end,
   lists:foldl(FunCollAddOnly, AddExtraCollFiles, AddOnlySrcDirs).


isOnlyDir([], _) ->
   true;
isOnlyDir(ReplaceDirs, SrcDir) ->
   isMatchDir(ReplaceDirs, SrcDir).

isDelDir([], _) ->
   false;
isDelDir(ReplaceDirs, SrcDir) ->
   isMatchDir(ReplaceDirs, SrcDir).

isMatchDir([], _SrcDir) ->
   false;
isMatchDir([SrcDir | _ReplaceDirs], SrcDir) ->
   true;
isMatchDir([OneDir | ReplaceDirs], SrcDir) ->
   case re:run(SrcDir, OneDir) of
      nomatch -> isMatchDir(ReplaceDirs, SrcDir);
      _ -> true
   end.

getEnv(Var, Default) ->
   case application:get_env(eSync, Var) of
      {ok, Value} ->
         Value;
      _ ->
         Default
   end.

setEnv(Var, Val) ->
   ok = application:set_env(eSync, Var, Val).

canLog(MsgType) ->
   case eSync:getLog() of
      true -> true;
      all -> true;
      none -> false;
      false -> false;
      skip_success -> MsgType == errors orelse MsgType == warnings;
      L when is_list(L) -> lists:member(MsgType, L);
      _ -> false
   end.

%% 注意 map类型的数据不能当做key
-type key() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple().
-type value() :: atom() | binary() | bitstring() | float() | integer() | list() | tuple() | map().

-spec load(term(), [{key(), value()}]) -> ok.
load(Module, KVs) ->
   Forms = forms(Module, KVs),
   {ok, Module, Bin} = compile:forms(Forms),
   code:soft_purge(Module),
   {module, Module} = code:load_binary(Module, atom_to_list(Module), Bin),
   ok.

forms(Module, KVs) ->
   %% -module(Module).
   Mod = erl_syntax:attribute(erl_syntax:atom(module), [erl_syntax:atom(Module)]),
   %% -export([getv/0]).
   ExportList = [erl_syntax:arity_qualifier(erl_syntax:atom(getv), erl_syntax:integer(1))],
   Export = erl_syntax:attribute(erl_syntax:atom(export), [erl_syntax:list(ExportList)]),
   %% getv(K) -> V
   Function = erl_syntax:function(erl_syntax:atom(getv), lookup_clauses(KVs, [])),
   [erl_syntax:revert(X) || X <- [Mod, Export, Function]].

lookup_clause(Key, Value) ->
   Var = erl_syntax:abstract(Key),
   Body = erl_syntax:abstract(Value),
   erl_syntax:clause([Var], [], [Body]).

lookup_clause_anon() ->
   Var = erl_syntax:variable("_"),
   Body = erl_syntax:atom(undefined),
   erl_syntax:clause([Var], [], [Body]).

lookup_clauses([], Acc) ->
   lists:reverse(lists:flatten([lookup_clause_anon() | Acc]));
lookup_clauses([{Key, Value} | T], Acc) ->
   lookup_clauses(T, [lookup_clause(Key, Value) | Acc]).

getOptions(SrcDir) ->
   case erlang:get(SrcDir) of
      undefined ->
         undefined;
      Options ->
         {ok, Options}
   end.

setOptions(SrcDir, Options) ->
   case erlang:get(SrcDir) of
      undefined ->
         erlang:put(SrcDir, Options);
      OldOptions ->
         NewOptions =
            case lists:keytake(compile_info, 1, Options) of
               {value, {compile_info, ValList1}, Options1} ->
                  case lists:keytake(compile_info, 1, OldOptions) of
                     {value, {compile_info, ValList2}, Options2} ->
                        [{compile_info, lists:usort(ValList1 ++ ValList2)} | lists:usort(Options1 ++ Options2)];
                     _ ->
                        lists:usort(Options ++ OldOptions)
                  end;
               _ ->
                  lists:usort(Options ++ OldOptions)
            end,
         erlang:put(SrcDir, NewOptions)
   end.

loadCfg() ->
   KVs = [{Key, getEnv(Key, DefVal)} || {Key, DefVal} <- ?DefCfgList],
   load(?esCfgSync, KVs).

%% *******************************  加载与编译相关 **********************************************************************
printResults(_Module, SrcFile, [], []) ->
   ?logSuccess("~s Recompiled", [SrcFile]);
printResults(_Module, SrcFile, [], Warnings) ->
   formatErrors(logWarnings, SrcFile, [], Warnings);
printResults(_Module, SrcFile, Errors, Warnings) ->
   formatErrors(logErrors, SrcFile, Errors, Warnings).

%% @private Print error messages in a pretty and user readable way.
formatErrors(LogFun, File, Errors, Warnings) ->
   AllErrors1 = lists:sort(lists:flatten([X || {_, X} <- Errors])),
   AllErrors2 = [{Line, "Error", Module, Description} || {Line, Module, Description} <- AllErrors1],
   AllWarnings1 = lists:sort(lists:flatten([X || {_, X} <- Warnings])),
   AllWarnings2 = [{Line, "Warning", Module, Description} || {Line, Module, Description} <- AllWarnings1],
   Everything = lists:sort(AllErrors2 ++ AllWarnings2),
   FPck =
      fun({Line, Prefix, Module, ErrorDescription}) ->
         Msg = formatError(Module, ErrorDescription),
         case LogFun of
            logWarnings ->
               ?logWarnings("~s: ~p: ~s: ~s", [File, Line, Prefix, Msg]);
            logErrors ->
               ?logErrors("~s: ~p: ~s: ~s", [File, Line, Prefix, Msg])
         end
      end,
   [FPck(X) || X <- Everything],
   ok.

formatError(Module, ErrorDescription) ->
   case erlang:function_exported(Module, format_error, 1) of
      true -> Module:format_error(ErrorDescription);
      false -> io_lib:format("~s", [ErrorDescription])
   end.

fireOnSync(OnSyncFun, Modules) ->
   case OnSyncFun of
      undefined -> ok;
      Funs when is_list(Funs) -> onSyncApplyList(Funs, Modules);
      Fun -> onSyncApply(Fun, Modules)
   end.

onSyncApplyList(Funs, Modules) ->
   [onSyncApply(Fun, Modules) || Fun <- Funs].

onSyncApply({M, F}, Modules) ->
   try erlang:apply(M, F, [Modules])
   catch
      C:R:S ->
         ?logErrors("apply sync fun ~p:~p(~p)  error ~p", [M, F, Modules, {C, R, S}])
   end;
onSyncApply(Fun, Modules) when is_function(Fun) ->
   try Fun(Modules)
   catch
      C:R:S ->
         ?logErrors("apply sync fun ~p(~p)  error ~p", [Fun, Modules, {C, R, S}])
   end.

reloadChangedMod([], _SwSyncNode, OnSyncFun, Acc) ->
   fireOnSync(OnSyncFun, Acc);
reloadChangedMod([Module | LeftMod], SwSyncNode, OnSyncFun, Acc) ->
   case Module == es_gen_ipc orelse code:get_object_code(Module) of
      true ->
         ignore;
      error ->
         ?logErrors("Error loading object code for ~p", [Module]),
         reloadChangedMod(LeftMod, SwSyncNode, OnSyncFun, Acc);
      {Module, Binary, Filename} ->
         case code:load_binary(Module, Filename, Binary) of
            {module, Module} ->
               ?logSuccess("Reloaded(Beam changed) Mod: ~s Success", [Module]),
               syncLoadModOnAllNodes(SwSyncNode, Module, Binary, changed);
            {error, What} ->
               ?logErrors("Reloaded(Beam changed) Mod: ~s Errors Reason:~p", [Module, What])
         end,
         reloadChangedMod(LeftMod, SwSyncNode, OnSyncFun, [Module | Acc])
   end.

getNodes() ->
   lists:usort(lists:flatten(nodes() ++ [erpc:call(X, erlang, nodes, []) || X <- nodes()])) -- [node()].

syncLoadModOnAllNodes(false, _Module, _Binary, _Reason) ->
   ignore;
syncLoadModOnAllNodes(true, Module, Binary, Reason) ->
   %% Get a list of nodes known by this node, plus all attached nodes.
   Nodes = getNodes(),
   NumNodes = length(Nodes),
   [
      begin
         ?logSuccess("Do Reloading '~s' on ~p", [Module, Node]),
         erpc:call(Node, code, ensure_loaded, [Module]),
         case erpc:call(Node, code, which, [Module]) of
            Filename when is_binary(Filename) orelse is_list(Filename) ->
               %% File exists, overwrite and load into VM.
               ok = erpc:call(Node, file, write_file, [Filename, Binary]),
               erpc:call(Node, code, purge, [Module]),

               case erpc:call(Node, code, load_file, [Module]) of
                  {module, Module} ->
                     ?logSuccess("Reloaded(Beam ~p) Mod:~s and write Success on node:~p", [Reason, Module, Node]);
                  {error, What} ->
                     ?logErrors("Reloaded(Beam ~p) Mod:~s and write Errors on node:~p Reason:~p", [Module, Node, What])
               end;
            _ ->
               %% File doesn't exist, just load into VM.
               case erpc:call(Node, code, load_binary, [Module, undefined, Binary]) of
                  {module, Module} ->
                     ?logSuccess("Reloaded(Beam ~p) Mod:~s Success on node:~p", [Reason, Module, Node]);
                  {error, What} ->
                     ?logErrors("Reloaded(Beam ~p) Mod:~s Errors on node:~p Reason:~p", [Reason, Module, Node, What])
               end
         end
      end || Node <- Nodes
   ],
   ?logSuccess("Reloaded(Beam changed) Mod: ~s on ~p nodes:~p", [Module, NumNodes, Nodes]).

recompileChangeSrcFile(Iterator, SwSyncNode) ->
   case maps:next(Iterator) of
      {File, _V, NextIterator} ->
         recompileSrcFile(File, SwSyncNode),
         recompileChangeSrcFile(NextIterator, SwSyncNode);
      _ ->
         ok
   end.

erlydtlCompile(SrcFile, Options) ->
   F =
      fun({outdir, OutDir}, Acc) -> [{out_dir, OutDir} | Acc];
         (OtherOption, Acc) -> [OtherOption | Acc]
      end,
   DtlOptions = lists:foldl(F, [], Options),
   Module = binary_to_atom(lists:flatten(filename:basename(SrcFile, ".dtl") ++ "_dtl")),
   Compiler = erlydtl,
   Compiler:compile(SrcFile, Module, DtlOptions).

elixir_compile(SrcFile, Options) ->
   Outdir = proplists:get_value(outdir, Options),
   Compiler = ':Elixir.Kernel.ParallelCompiler',
   Modules = Compiler:files_to_path([SrcFile], Outdir),
   Loader =
      fun(Module) ->
         Outfile = code:which(Module),
         Binary = file:read_file(Outfile),
         {Module, Binary}
      end,
   Results = lists:map(Loader, Modules),
   {ok, multiple, Results, []}.

lfe_compile(SrcFile, Options) ->
   Compiler = lfe_comp,
   Compiler:file(SrcFile, Options).

getCompileFunAndModuleName(SrcFile) ->
   case getFileType(SrcFile) of
      erl ->
         {fun compile:file/2, binary_to_atom(filename:basename(SrcFile, <<".erl">>))};
      dtl ->
         {fun erlydtlCompile/2, list_to_atom(lists:flatten(binary_to_list(filename:basename(SrcFile, <<".dtl">>)) ++ "_dtl"))};
      lfe ->
         {fun lfe_compile/2, binary_to_atom(filename:basename(SrcFile, <<".lfe">>))};
      elixir ->
         {fun elixir_compile/2, binary_to_atom(filename:basename(SrcFile, <<".ex">>))}
   end.

getObjectCode(Module) ->
   case code:get_object_code(Module) of
      {Module, B, Filename} -> {B, Filename};
      _ -> {undefined, undefined}
   end.

reloadIfNecessary(Module, OldBinary, Binary, Filename, SwSyncNode) ->
   case Binary =/= OldBinary of
      true ->
         %% Try to load the module...
         case code:ensure_loaded(Module) of
            {module, Module} ->
               case code:load_binary(Module, Filename, Binary) of
                  {module, Module} ->
                     ?logSuccess("Reloaded(Beam recompiled) Mod:~s Success", [Module]),
                     syncLoadModOnAllNodes(SwSyncNode, Module, Binary, recompiled);
                  {error, What} ->
                     ?logErrors("Reloaded(Beam recompiled) Mod:~s Errors Reason:~p", [Module, What])
               end;
            {error, nofile} ->
               case code:load_binary(Module, Filename, Binary) of
                  {module, Module} ->
                     ?logSuccess("Reloaded(Beam recompiled) Mod:~s Success", [Module]),
                     syncLoadModOnAllNodes(SwSyncNode, Module, Binary, recompiled);
                  {error, What} ->
                     ?logErrors("Reloaded(Beam recompiled) Mod:~s Errors Reason:~p", [Module, What])
               end;
            {error, embedded} ->
               case code:load_file(Module) of                  %% Module is not yet loaded, load it.
                  {module, Module} -> ok;
                  {error, nofile} ->
                     case code:load_binary(Module, Filename, Binary) of
                        {module, Module} ->
                           ?logSuccess("Reloaded(Beam recompiled) Mod:~s Success", [Module]),
                           syncLoadModOnAllNodes(SwSyncNode, Module, Binary, recompiled);
                        {error, What} ->
                           ?logErrors("Reloaded(Beam recompiled) Mod:~s Errors Reason:~p", [Module, What])
                     end
               end
         end;
      _ ->
         ignore
   end.

recompileSrcFile(SrcFile, SwSyncNode) ->
   %% Get the module, src dir, and options...
   RootSrcDir =
      case getRootSrcDirFromSrcDir(SrcFile) of
         undefined ->
            filename:dirname(SrcFile);
         RetSrcDir ->
            RetSrcDir
      end,
   CurSrcDir = filename:dirname(SrcFile),
   {CompileFun, Module} = getCompileFunAndModuleName(SrcFile),
   {OldBinary, Filename} = getObjectCode(Module),
   case Module == es_gen_ipc orelse getOptions(RootSrcDir) of
      true ->
         ignore;
      {ok, Options} ->
         RightFileDir = binary_to_list(filename:join(CurSrcDir, filename:basename(SrcFile))),
         LastOptions = ?IIF(?esCfgSync:getv(?isJustMem), [binary, return | Options], [return | Options]),
         case CompileFun(RightFileDir, LastOptions) of
            {ok, Module, Binary, Warnings} ->
               printResults(Module, RightFileDir, [], Warnings),
               reloadIfNecessary(Module, OldBinary, Binary, Filename, SwSyncNode),
               {ok, [], Warnings};
            {ok, [{ok, Module, Binary, Warnings}], Warnings2} ->
               printResults(Module, RightFileDir, [], Warnings ++ Warnings2),
               reloadIfNecessary(Module, OldBinary, Binary, Filename, SwSyncNode),
               {ok, [], Warnings ++ Warnings2};
            {ok, multiple, Results, Warnings} ->
               printResults(Module, RightFileDir, [], Warnings),
               [reloadIfNecessary(CompiledModule, OldBinary, Binary, Filename, SwSyncNode) || {CompiledModule, Binary} <- Results],
               {ok, [], Warnings};
            {ok, OtherModule, _Binary, Warnings} ->
               Desc = io_lib:format("Module definition (~p) differs from expected (~s)", [OtherModule, filename:rootname(filename:basename(RightFileDir))]),
               Errors = [{RightFileDir, {0, Module, Desc}}],
               printResults(Module, RightFileDir, Errors, Warnings),
               {ok, Errors, Warnings};
            {error, Errors, Warnings} ->
               printResults(Module, RightFileDir, Errors, Warnings),
               {ok, Errors, Warnings};
            {ok, Module, Warnings} ->
               printResults(Module, RightFileDir, [], Warnings),
               {ok, [], Warnings};
            _Err ->
               ?logErrors("compile Mod:~s Errors Reason:~p", [Module, _Err])
         end;
      undefined ->
         case tryGetModOpts(Module) of
            {ok, Options} ->
               setOptions(RootSrcDir, Options),
               recompileSrcFile(SrcFile, SwSyncNode);
            _ ->
               case tryGetSrcOpts(CurSrcDir) of
                  {ok, Options} ->
                     setOptions(RootSrcDir, Options),
                     recompileSrcFile(SrcFile, SwSyncNode);
                  _ ->
                     ?logErrors("Unable to determine options for ~s", [SrcFile])
               end
         end
   end.

collIncludeCHrls([], AllHrls, CHrls, NewAddMap) ->
   case maps:size(NewAddMap) > 0 of
      true ->
         collIncludeCHrls(maps:keys(NewAddMap), AllHrls, CHrls, #{});
      _ ->
         CHrls
   end;
collIncludeCHrls([OneHrl | LeftCHrls], AllHrls, CHrls, NewAddMap) ->
   {NewCHrls, NNewAddMap} = whoInclude(OneHrl, AllHrls, CHrls, NewAddMap),
   collIncludeCHrls(LeftCHrls, AllHrls, NewCHrls, NNewAddMap).

collIncludeCErls([], _SrcFiles, CSrcs, _NewAddMap) ->
   CSrcs;
collIncludeCErls([Hrl | LeftHrl], SrcFiles, CSrcs, NewAddMap) ->
   {NewCSrcs, NNewAddMap} = whoInclude(Hrl, SrcFiles, CSrcs, NewAddMap),
   collIncludeCErls(LeftHrl, SrcFiles, NewCSrcs, NNewAddMap).

whoInclude(HrlFile, AllFiles, CFiles, NewAddMap) ->
   HrlFileBaseName = filename:basename(HrlFile),
   QuoteHrlFileBaseName = <<"\"", HrlFileBaseName/binary, "\"">>,
   doMathEveryFile(maps:iterator(AllFiles), QuoteHrlFileBaseName, CFiles, NewAddMap).

doMathEveryFile(Iterator, HrlFileBaseName, CFiles, NewAddMap) ->
   case maps:next(Iterator) of
      {OneFile, _V, NextIterator} ->
         case file:open(OneFile, [read, binary]) of
            {ok, IoDevice} ->
               IsInclude = doMathEveryLine(IoDevice, HrlFileBaseName),
               file:close(IoDevice),
               case IsInclude of
                  true ->
                     case maps:is_key(OneFile, CFiles) of
                        true ->
                           doMathEveryFile(NextIterator, HrlFileBaseName, CFiles, NewAddMap);
                        _ ->
                           doMathEveryFile(NextIterator, HrlFileBaseName, CFiles#{OneFile => 1}, NewAddMap#{OneFile => 1})
                     end;
                  _ ->
                     doMathEveryFile(NextIterator, HrlFileBaseName, CFiles, NewAddMap)
               end;
            _ ->
               doMathEveryFile(NextIterator, HrlFileBaseName, CFiles, NewAddMap)
         end;
      _ ->
         {CFiles, NewAddMap}
   end.

%% 注释
%% whoInclude(HrlFile, SrcFiles) ->
%%    HrlFileBaseName = filename:basename(HrlFile),
%%    Pred =
%%       fun(SrcFile, _) ->
%%          {ok, Forms} = epp_dodger:parse_file(SrcFile),
%%          isInclude(binary_to_list(HrlFileBaseName), Forms)
%%       end,
%%    maps:filter(Pred, SrcFiles).
%% isInclude(_HrlFile, []) ->
%%    false;
%% isInclude(HrlFile, [{tree, attribute, _, {attribute, _, [{_, _, IncludeFile}]}} | Forms]) when is_list(IncludeFile) ->
%%    IncludeFileBaseName = filename:basename(IncludeFile),
%%    case IncludeFileBaseName of
%%       HrlFile -> true;
%%       _ -> isInclude(HrlFile, Forms)
%%    end;
%% isInclude(HrlFile, [_SomeForm | Forms]) ->
%%    isInclude(HrlFile, Forms).

doMathEveryLine(IoDevice, HrlFileBaseName) ->
   case file:read_line(IoDevice) of
      {ok, Data} ->
         case re:run(Data, HrlFileBaseName) of
            nomatch ->
               case re:run(Data, <<"->">>) of
                  nomatch ->
                     doMathEveryLine(IoDevice, HrlFileBaseName);
                  _ ->
                     false
               end;
            _ ->
               true
         end;
      _ ->
         false
   end.

classifyChangeFile([], Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams) ->
   {Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams};
classifyChangeFile([OneFile | LeftFile], Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams) ->
   CurMTimeSec = dateTimeToSec(filelib:last_modified(OneFile)),
   case filename:extension(OneFile) of
      <<".beam">> ->
         BinMod = binary_to_atom(filename:basename(OneFile, <<".beam">>)),
         case ColBeams of
            #{BinMod := OldMTimeSec} ->
               case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of
                  true ->
                     classifyChangeFile(LeftFile, [BinMod | Beams], Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams#{BinMod := CurMTimeSec});
                  _ ->
                     classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams)
               end;
            _ ->
               classifyChangeFile(LeftFile, [BinMod | Beams], Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams#{BinMod => CurMTimeSec})
         end;
      <<".config">> ->
         AbsFile = filename:absname(OneFile),
         case ColConfigs of
            #{AbsFile := OldMTimeSec} ->
               case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of
                  true ->
                     CfgMod = erlang:binary_to_atom(filename:basename(AbsFile, <<".config">>), utf8),
                     classifyChangeFile(LeftFile, Beams, [CfgMod | Configs], Hrls, Srcs, ColSrcs, ColHrls, ColConfigs#{AbsFile := CurMTimeSec}, ColBeams);
                  _ ->
                     classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams)
               end;
            _ ->
               CfgMod = erlang:binary_to_atom(filename:basename(AbsFile, <<".config">>), utf8),
               classifyChangeFile(LeftFile, Beams, [CfgMod | Configs], Hrls, Srcs, ColSrcs, ColHrls, ColConfigs#{AbsFile => CurMTimeSec}, ColBeams)
         end;
      <<".hrl">> ->
         AbsFile = filename:absname(OneFile),
         case ColHrls of
            #{AbsFile := OldMTimeSec} ->
               case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of
                  true ->
                     classifyChangeFile(LeftFile, Beams, Configs, Hrls#{AbsFile => 1}, Srcs, ColSrcs, ColHrls#{AbsFile := CurMTimeSec}, ColConfigs, ColBeams);
                  _ ->
                     classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams)
               end;
            _ ->
               classifyChangeFile(LeftFile, Beams, Configs, Hrls#{AbsFile => 1}, Srcs, ColSrcs, ColHrls#{AbsFile => CurMTimeSec}, ColConfigs, ColBeams)
         end;
      <<>> ->
         classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams);
      _ ->
         AbsFile = filename:absname(OneFile),
         case ColSrcs of
            #{AbsFile := OldMTimeSec} ->
               case CurMTimeSec =/= OldMTimeSec andalso CurMTimeSec =/= 0 of
                  true ->
                     classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs#{AbsFile => 1}, ColSrcs#{AbsFile := CurMTimeSec}, ColHrls, ColConfigs, ColBeams);
                  _ ->
                     classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs, ColSrcs, ColHrls, ColConfigs, ColBeams)
               end;
            _ ->
               classifyChangeFile(LeftFile, Beams, Configs, Hrls, Srcs#{AbsFile => 1}, ColSrcs#{AbsFile => CurMTimeSec}, ColHrls, ColConfigs, ColBeams)
         end
   end.

fileSyncPath(ExecName) ->
   case code:priv_dir(?MODULE) of
      {error, _} ->
         case code:which(?MODULE) of
            Filename when is_list(Filename) ->
               filename:join([filename:dirname(filename:dirname(Filename)), "priv", ExecName]);
            _ ->
               filename:join("../priv", ExecName)
         end;
      Dir ->
         filename:join(Dir, ExecName)
   end.

dateTimeToSec(DateTime) ->
   if
      DateTime == 0 ->
         0;
      true ->
         erlang:universaltime_to_posixtime(DateTime)
   end.

debugInfoKeyFun() ->
   case ?esCfgSync:getv(?debugInfoKeyFun) of
      undefined ->
         {debug_info_key, undefined};
      {Mod, Fun} ->
         Mod:Fun()
   end.
%% ************************************************* utils end   *******************************************************