anchor.diff:      ok.
   
   plugin_test() ->
  -	{ok, _} = application:ensure_all_started(hooks),
  -
  -	Hooks = [{a, [{?MODULE, hook1, 0},
  -				  {?MODULE, hook2, 0}]},
  -		     {b, [{?MODULE, hook1, 2},
  -		          {?MODULE, hook2, 2}]}],
  +    {ok, _} = application:ensure_all_started(hooks),
  +
  +    Hooks = [{a, [{?MODULE, hook1, 0},
  +                  {?MODULE, hook2, 0}]},
  +             {b, [{?MODULE, hook1, 2},
  +                  {?MODULE, hook2, 2}]}],
   
       application:set_env(hooks, hooks, Hooks),
  -	hooks:enable_plugin(hooks),
  +    hooks:enable_plugin(hooks),
   
       FoundA = hooks_list:find(a),
  -	?assertEqual({ok, [{?MODULE, hook1}, {?MODULE, hook2}]}, FoundA),
  -	FoundB = hooks_list:find(b),
  -	?assertEqual({ok, [{?MODULE, hook1}, {?MODULE, hook2}]}, FoundB),
  -	?assertEqual(ok, hooks:disable_plugin(hooks)),
  -	?assertEqual(hook_not_found, hooks_list:find(a)),
  -	?assertEqual(hook_not_found, hooks_list:find(b)),
  -	?assertEqual(ok, hooks:disable_plugin(hooks)),
  +    ?assertEqual({ok, [{?MODULE, hook1}, {?MODULE, hook2}]}, FoundA),
  +    FoundB = hooks_list:find(b),
  +    ?assertEqual({ok, [{?MODULE, hook1}, {?MODULE, hook2}]}, FoundB),
  +    ?assertEqual(ok, hooks:disable_plugin(hooks)),
  +    ?assertEqual(hook_not_found, hooks_list:find(a)),
  +    ?assertEqual(hook_not_found, hooks_list:find(b)),
  +    ?assertEqual(ok, hooks:disable_plugin(hooks)),
       ok.
   
   init_test_wait_loop(Server) ->
  
positive.code: all_test() ->
      {ok, _} = application:ensure_all_started(hooks),
  
  	Hooks = [{a, [{?MODULE, hook1, 0},
  				  {?MODULE, hook2, 0}]},
  		     {b, [{?MODULE, hook1, 2},
  		          {?MODULE, hook2, 2}]}],
  	ok = hooks:mreg(Hooks),
  
      ?assertEqual([[], []], hooks:all(a, [])),
      ?assertEqual([[1 | 1], [1 | 1]], hooks:all(b, [1, 1])),
  
negative.code:     end.
  
  do_mreg([]) ->
      ok;
  do_mreg([{HookName, Hooks0}| Rest]) ->
      case check_hooks(Hooks0, []) of
          {ok, Hooks} ->
              update_hooks(HookName, Hooks),
              do_mreg(Rest);
          Error ->
              Error
      end.
  
  
  do_reg(HookName, {_Priority, MFA}=Hook) ->
      case check_mfa(MFA) of
          ok ->
              update_hooks(HookName, [Hook]),
              ok;
          Error ->
              Error
      end.
  
  do_munreg([]) ->
      ok;
  do_munreg([{HookName, Hooks0} | Rest]) ->
      Hooks = lists:foldl(fun
                              ({_, _, _}=MFA, Acc) ->
                                  [{0, MFA} |Acc];
          ({_, {_, _, _}}=Hook, Acc) ->
                                  [Hook | Acc]
                          end, [], Hooks0),
      remove_hooks(HookName, lists:reverse(Hooks)),
      do_munreg(Rest).
  
  do_unreg(HookName, Hook) ->
      remove_hooks(HookName, [Hook]),
      ok.
  
  update_hooks(HookName, HookFuns) ->
      case ets:lookup(?TAB, {h, HookName}) of
          [] ->
              true = ets:insert(?TAB, {{h, HookName}, HookFuns});
          [{_, Funs}] ->
              Funs2 = lists:keysort(1, Funs ++ HookFuns),
              true = ets:insert(?TAB, {{h, HookName}, Funs2})
      end.
  
  remove_hooks(HookName, HookFuns) ->
      case ets:lookup(?TAB, {h, HookName}) of
          [] ->
              ok;
          [{_, Funs}] ->
              Funs2 = Funs -- HookFuns,
              case Funs2 of
                  [] ->
                      ets:delete(?TAB, {h, HookName});
                  _ ->
                      ets:insert(?TAB, {{h, HookName}, Funs2})
              end
      end.
  
  
  do_enable_plugin(Application, Paths) ->
      ok = add_paths(Paths),
      case ets:lookup(?TAB, {p, Application}) of
          [] -> ok;
          [{_, PluginHooks}] ->
              unregister_plugin_hooks(PluginHooks, Application)
      end,
  
      case load_plugin(Application, Paths) of
          ok ->
              ok;
          Error ->
              Error
      end.
  
  do_disable_plugin(Application) ->
      case lists:keyfind(Application, 1, application:which_applications()) of
          true ->  application:stop(Application);
          _ -> ok
      end,
  
      case ets:lookup(?TAB, {p, Application}) of
          [] -> ok;
          [{_, PluginHooks}] -> unregister_plugin_hooks(PluginHooks, Application)
      end,
      ok.
  
  add_paths([]) ->
      ok;
  add_paths([Path | Rest]) ->
      _ = code:add_path(Path),
  
      EbinDir = filelib:wildcard(filename:join(Path, "ebin")),
      ok = code:add_paths(EbinDir),
  
      %% rebar2 deps
      DepsEbinDir = filelib:wildcard(filename:join(Path, "deps/*/ebin")),
      ok = code:add_paths(DepsEbinDir),
      %% rebar3 deps
      LibEbinDir = filelib:wildcard(filename:join(Path, "lib/*/ebin")),
      ok = code:add_paths(LibEbinDir),
      add_paths(Rest).
  
  
  maybe_start_plugin(Application) ->
      case lists:keyfind(Application, 1, application:which_applications()) of
          false ->
              Res = application:ensure_all_started(Application),
              case Res of
                  {ok, _} -> ok;
                  Error -> Error
              end;
          _Else ->
              already_started
      end.
  
  extract_hooks(Application) ->
      application:get_env(Application, hooks, []).
  
  check_hooks([], Hooks) ->
      {ok, lists:reverse(Hooks)};
  check_hooks([{_, _, _}=MFA | Rest], Hooks) ->
      check_hooks([{0, MFA} | Rest], Hooks);
  check_hooks([{_Priority, MFA}=Hook | Rest], Hooks) ->
      case check_mfa(MFA) of
          ok -> check_hooks(Rest, [Hook | Hooks]);
          Error -> Error
      end.
  
  check_mfa({Mod, Fun, Arity}) ->
      _ = code:ensure_loaded(Mod),
      case erlang:function_exported(Mod, Fun, Arity) of
          true -> ok;
          false -> {error, hooks_not_exported}
      end.
  
  maybe_build_hooks(#state{ready=false}) -> ok;
  maybe_build_hooks(#state{ready=true}) -> build_hooks().
  
  build_hooks() ->
      Hooks = ets:select(?TAB, [{{{h,'$1'},'$2'},[],[{{'$1','$2'}}]}]),
      build_hooks(Hooks).
  
  build_hooks(Hooks) ->
      %% hooks:find_hook/1 function
      Cs0 = lists:foldl(fun({Name, FunsByPriority}, Acc) ->
                                Funs = [{M, F} || {_P, {M, F, _A}} <- FunsByPriority],
                                [?Q(["(_@Name@) ->",
                                     "{ok, _@Funs@}"]) | Acc]
                        end, [], Hooks),
      Cs = lists:reverse([?Q("(_) -> hook_not_found") | Cs0]),
      Find = erl_syntax:function(merl:term('find'), Cs),
  
      %% build module
      Module = ?Q("-module('hooks_list')."),
      Exported = ?Q("-export(['find'/1])."),
      Functions = [ ?Q("'@_F'() -> [].") || F <- [Find]],
      Forms = lists:flatten([Module, Exported, Functions]),
      merl:compile_and_load(Forms, [verbose]),
      %% run build hooks
      hooks:run(build_hooks, [Hooks]),
      ok.
  
  
  unregister_plugin_hooks({Hooks, _Path}, Application) ->
      lists:foreach(fun({HookName, Specs}) ->
                            case ets:lookup(?TAB, {h, HookName}) of
                                [] -> ok;
                                [{_, OldSpecs}] ->
                                    ToRem = OldSpecs -- Specs,
                                    NewSpecs = OldSpecs -- ToRem,
                                    if
                                        NewSpecs /= [] ->
                                            ets:insert(?TAB, {{h, HookName}, NewSpecs});
                                        true ->
                                            ets:delete(?TAB, {h, HookName})
                                    end
                            end
                    end, Hooks),
      ets:delete(?TAB, {p, Application}),
      ok.
  
  load_plugin(Application, Paths) ->
      ok = add_paths(Paths),
      Start = maybe_start_plugin(Application),
      if
          Start =:= ok; Start =:= already_started ->
              AppHooks = extract_hooks(Application),
  
              lists:foreach(fun({HookName, Specs}) ->
                                    case check_hooks(Specs, []) of
                                        {ok, Specs2} ->
                                            case ets:lookup(?TAB, {h, HookName})  of
                                                [] ->
                                                    ets:insert(?TAB, {{h, HookName}, Specs2});
                                                [{_, OldSpecs}] ->
                                                    ToRem = OldSpecs -- Specs2,
                                                    NewSpecs = lists:merge(OldSpecs -- ToRem, Specs2),
                                                    ets:insert(?TAB, {{h, HookName}, NewSpecs})
                                            end;
                                        Error ->
                                            error_logger:error_msg("~p~n error registering: ~p~n", [HookName, Error]),
                                            ok
                                    end
                            end, AppHooks),
              ets:insert(?TAB, {{p, Application}, {AppHooks, Paths}}),
              ok;
          true ->
              error_logger:error_msg("~p~n error loading: ~p~n", [Application, Start]),
              Start
      end.
  
  -ifdef(TEST).
  -compile(export_all).
  