%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ts=4 sw=4 ft=erlang et
%%
%% Copyright (c) 2016-2018 Tuncer Ayaz. All Rights Reserved.
%%
%% Permission to use, copy, modify, and distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

%% If library matching wildcard can be found in common lib dirs, return Opt1,
%% otherwise Opt2.
FindLibByWC = fun(WC, Opt1, Opt2) ->
                      Dirs = [ "/usr/local/lib/"
                             , "/usr/lib64/"
                             , "/usr/lib/"
                             , "/lib64/"
                             , "/lib/"
                             ],
                      case lists:flatmap(fun(D) ->
                                                   filelib:wildcard(D ++ WC)
                                         end,
                                         Dirs)
                      of
                          [] -> Opt2;
                          [LibDir|_] -> {filename:dirname(LibDir), Opt1}
                      end
              end.

%% If "c++ --version" identifies itself as clang, and there is libc++, use
%% that, otherwise default to libstdc++.

IsClang = fun() ->
                  Vsn = os:cmd("c++ --version"),
                  case re:run(Vsn, "clang", [{capture, none}]) of
                      match -> clang;
                      nomatch -> other_compiler
                  end
          end.

LibCPP = "c++".
LibStdCPP = "stdc++".

MaybeUseLibCPP = fun() ->
                         case FindLibByWC("libc++*.{so,dylib}",
                                          LibCPP,
                                          LibStdCPP) of
                             {_LibDir, LibCPP} -> LibCPP;
                             LibStdCPP -> LibStdCPP
                         end
                 end.

WhichCPPLib = fun() ->
                      case IsClang() of
                          clang ->
                              MaybeUseLibCPP();
                          _ ->
                              LibStdCPP
                      end
              end.

%% Only look for libc++ if rebar is older than 2.6.4. If it's new
%% enough, we can omit the extra LDFLAGS, because CXX will be used in
%% the link command.
LDFLAGS = case application:get_key(rebar, vsn) of
              {ok, Rebar2Vsn} when Rebar2Vsn < "2.6.4" ->
                  [{"(linux|freebsd|solaris)",
                    "LDFLAGS", "$LDFLAGS -l" ++ WhichCPPLib()}];
              _ ->
                  %% If undefined, Elixir's mix is evaluating
                  %% rebar.config.script, and since it defaults to using
                  %% rebar3, the port_compiler plugin will be new enough. If
                  %% defined, Rebar2Vsn will be newer than 2.6.4 and also good
                  %% enough.
                  []
          end.

LocalIncDir = "-Ic_src/re2".

%% Find re2 include dir on system.
Re2IncDir = fun() ->
                    %% Get include dir which contains the re2/ dir.
                    Hs = [filename:dirname(filename:dirname(F)) ||
                          F <- filelib:wildcard("/usr/local/include/re2/re2.h")
                          ++ filelib:wildcard("/usr/include/re2/re2.h")],
                    case Hs of
                        [] -> not_found;
                        [IncDir|_] -> "-I" ++ IncDir
                    end
            end.

FindRe2 = fun(SysLib, LocalLib) ->
                  %% If there is libre2.so use it, otherwise enable fetching
                  %% and building of a local re2 copy.
                  %% We could try to test compile a simple program which makes
                  %% use of re2, but that would needlessly complicate the build
                  %% process due to the requirement of caching configure
                  %% results.
                  Lib = FindLibByWC("libre2.{so,dylib}", SysLib, LocalLib),
                  IncDir = Re2IncDir(),
                  case {IncDir, Lib} of
                      %% re2.h not found, and even though libre2 might
                      %% exist, we need the headers. Therefore, fall back to
                      %% local re2.
                      {not_found, {_LibDir, SysLib}} ->
                          {LocalIncDir, LocalLib};

                      %% Neither re2.h nor libre2.so found, use local lib.
                      {not_found, not_found} ->
                          {LocalIncDir, LocalLib};

                      %% If re2.h is found, but there is no libre2.so, which
                      %% should never be the case, fall back to local re2.
                      {IncDir, LocalLib} ->
                          {LocalIncDir, LocalLib};

                      %% If re2.h _and_ libre2.so are found, we can use
                      %% system re2.
                      {IncDir, {LibDir, SysLib}=Sys} ->
                          {IncDir, Sys}
                  end
          end.

{DebugFlags, LocalRe2Archive} = case os:getenv("DEBUG") of
                                    false ->
                                        {[], "c_src/re2/obj/libre2.a"};
                                    _ ->
                                        {"-g", "c_src/re2/obj/dbg/libre2.a"}
                                end,

WhichRe2 = fun() ->
                   SysRe2Lib = {sys, "-lre2"},
                   LocalRe2Lib = {local, LocalRe2Archive},
                   %% If env var SYSTEM_RE2 is set, try to find system re2 and
                   %% only fall back to local re2, if it cannot be found in the
                   %% system. Ideally, the default should be the other way
                   %% around, but it's likely this would disrupt too many
                   %% users' environments due to the requirement of having
                   %% libre2.so available. Eventually, the default should be
                   %% changed to give an option to skip detection of a system
                   %% re2.
                   case os:getenv("SYSTEM_RE2") of
                       false ->
                           {LocalIncDir, LocalRe2Lib};
                       _ ->
                           FindRe2(SysRe2Lib, LocalRe2Lib)
                   end
           end.

IsRebar3 = erlang:function_exported(rebar3, main, 1),

Hooks = fun() ->
                case IsRebar3 of
                    true ->
                        [ {pre_hooks,
                           [{{pc, compile}, "c_src/build_deps.sh"}]}
                        , {post_hooks,
                           [{{pc, clean}, "c_src/build_deps.sh clean"}]}
                        ];
                    false ->
                        [ {pre_hooks, [{compile, "c_src/build_deps.sh"}]}
                        , {post_hooks, [{clean, "c_src/build_deps.sh clean"}]}
                        ]
                end
        end,

CommonOpts = fun() ->
                     PortSpecs = {port_specs,
                                  [
                                   {"priv/re2_nif.so", ["c_src/re2_nif.cpp"]}
                                  ]},
                     BaseCFLAGS = "$DRV_CFLAGS -O3 -Wall -Wextra -std=c++11 "
                         ++ DebugFlags ++ " ",
                     case WhichRe2() of
                         {Inc, {LibDir, {sys, Lib}}} ->
                             %% Use already existing (system) re2
                             [ PortSpecs
                             , {port_env,
                                LDFLAGS ++
                                    [ {"DRV_CFLAGS",  BaseCFLAGS ++ Inc}
                                    , {"DRV_LDFLAGS",
                                       "$DRV_LDFLAGS -L" ++ LibDir
                                       ++ " " ++ Lib}
                                    ]}
                             ];
                         {Inc, {local, Lib}} ->
                             %% Use local re2, which is fetched and built by a
                             %% shell hook.
                             PortCompilerSettings =
                                 [ PortSpecs
                                 , {port_env,
                                    LDFLAGS ++
                                        [ {"DRV_CFLAGS",  BaseCFLAGS ++ Inc}
                                        , {"DRV_LDFLAGS",
                                           "$DRV_LDFLAGS " ++ Lib}
                                        ]}
                                 ],
                             PortCompilerSettings ++ Hooks()
                     end
             end.

Rebar2ExclusiveOpts = fun() -> [{qc_opts, [{qc_mod, triq}]}] end.

Rebar3ExclusiveOpts = fun() ->
                              [ {plugins, [pc]}
                              , {artifacts, ["priv/re2_nif.so"]}
                              , {provider_hooks,
                                 [ {pre,
                                    [ {compile, {pc, compile}}
                                    , {clean, {pc, clean}}
                                    ]}
                                 ]}
                              ]
                      end.

Config0 = case IsRebar3 of
              true ->
                  CommonOpts() ++ Rebar3ExclusiveOpts();
              false ->
                  CommonOpts() ++ Rebar2ExclusiveOpts()
          end.

TriqDep = [{triq, ".*", {git, "https://gitlab.com/triq/triq.git"}}].
Config = case os:getenv("RE2_TEST_DEPS") of
             false ->
                 Config0;
             _ ->
                 case lists:keysearch(deps, 1, Config0) of
                     {value, {deps, Deps}} ->
                         NDeps = Deps ++ TriqDep,
                         lists:keyreplace(deps, 1, Config0, {deps, NDeps});
                     false ->
                         Config0 ++ [{deps, TriqDep}]
                 end
         end.

%% If env var DEBUG_CONFIG is set, log config term.
case os:getenv("DEBUG_CONFIG") of
    false ->
        Config;
    _ ->
        C = Config,
        io:format(standard_error, "rebar config:~n~p~n", [C]),
        C
end.
