%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ts=4 sw=4 ft=erlang et
%%
%% Copyright 2016-2019 Tuncer Ayaz
%%
%% 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.

LocalIncDir = "-Ic_src/re2".

%% Find RE2 include dir on system.
Re2IncDir = fun() ->
                    UsrLocalH = "/usr/local/include/re2/re2.h",
                    UsrH = "/usr/include/re2/re2.h",
                    %% Get include dir which contains the re2/ dir.
                    Hs = [filename:dirname(filename:dirname(F)) ||
                             F <- filelib:wildcard(UsrLocalH)
                                 ++ filelib:wildcard(UsrH)],
                    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 a local RE2 copy.
                  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.

LibName = case os:type() of
              {win32,_} -> "re2.lib";
              _ -> "libre2.a"
          end.

CLANG_GCC_EXTRA_CFLAGS="-Wall -Wextra -Wpedantic".
ExtraCFLAGS = case os:type() of
                  {unix,_} -> CLANG_GCC_EXTRA_CFLAGS;
                  _ -> ""
              end.

GetFlagsAndLocalArchive =
    fun() ->
            WinRelFlags = "/O2 /MD",
            WinDbgFlags = "/MDd",
            RelFlags = "-O3",
            DbgFlags = "-g",
            ObjDir = filename:join(["c_src", "re2", "obj"]),
            RelArchive = filename:join(ObjDir, LibName),
            DbgArchive = filename:join([ObjDir, "dbg", LibName]),
            case {os:type(), os:getenv("RE2_DEBUG")} of
                {{unix,_}, false}  -> {RelFlags, RelArchive};
                {{unix,_}, "1"}    -> {DbgFlags, DbgArchive};
                {{win32,_}, false} -> {WinRelFlags, RelArchive};
                {{win32,_}, "1"}   -> {WinDbgFlags, RelArchive}
            end
    end.
{Flags, LocalRe2Archive} = GetFlagsAndLocalArchive().

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.
                   case os:getenv("SYSTEM_RE2") of
                       false ->
                           {LocalIncDir, LocalRe2Lib};
                       _ ->
                           FindRe2(SysRe2Lib, LocalRe2Lib)
                   end
           end.

PortOpts = fun() ->
                   PortSpecs = {port_specs,
                                [
                                 {"priv/re2_nif.so", ["c_src/re2_nif.cc"]}
                                ]},
                   BaseCFLAGS = "$DRV_CFLAGS -std=c++11 " ++
                        ExtraCFLAGS ++ " " ++ Flags ++ " ",
                   Hooks = [ {pre_hooks,
                              [{{pc, compile}, "sh -c c_src/build_deps.sh"}]}
                           , {post_hooks,
                              [{{pc, clean}, "sh -c 'c_src/build_deps.sh clean'"}]}
                           ],
                   case WhichRe2() of
                       {Inc, {LibDir, {sys, Lib}}} ->
                           %% Use existing (system) RE2
                           [ PortSpecs
                           , {port_env,
                              [ {"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,
                                  [ {"DRV_CFLAGS", BaseCFLAGS ++ Inc}
                                  , {"DRV_LDFLAGS",
                                     "$DRV_LDFLAGS " ++ Lib}
                                  ]}
                               ],
                           PortCompilerSettings ++ Hooks
                   end
           end.

PCDep = {pc, {git, "https://github.com/blt/port_compiler", {tag, "v1.10.2"}}}.
%% PCDep = pc.
%% TestDeps = [{triq, ".*",
%%              {git, "https://gitlab.com/triq/triq.git", {branch, "master"}}}].
TestDeps = [triq].

DebugWarns = [warnings_as_errors].
Artifact = case os:type() of
               {win32,_} -> "priv/re2_nif.dll";
               _ -> "priv/re2_nif.so"
           end.
GeneralOpts = [ {plugins, [PCDep]}
              , {artifacts, [Artifact]}
              , {pc_clang_db, true}
              , {provider_hooks,
                 [ {pre,
                    [ {compile, {pc, compile}}
                    , {clean, {pc, clean}}
                    ]}
                 ]}
              , {profiles
                , [ {test
                    , [ {deps, TestDeps}
                      , {erl_opts, DebugWarns}
                      ]}
                  , {debug
                    , [ {port_env, [{"DRV_CFLAGS", "$DRV_CFLAGS -DDEBUG"}]}
                      , {erl_opts, DebugWarns ++ [{d, 'DEV'}, {d, 'DEBUG'}]}
                      ]}
                  ]}
              ].

Config = GeneralOpts ++ PortOpts().
case os:getenv("DEBUG_CONFIG") of
    false ->
        Config;
    _ ->
        io:format(standard_error, "rebar config:~n~p~n", [Config]),
        Config
end.
