FDBReleasesBaseUrl = "https://api.github.com/repos/apple/foundationdb/releases".
IncludeDir = "/usr/local/include".

% Detect rebar3 or mix
RebarApi = {error, nofile} =/= code:ensure_loaded(rebar_api).

% Look for fdbcli on PATH, but also check /usr/local/bin explicitly since that is the common location
FdbCli =
    case os:find_executable("fdbcli") of
        false ->
            case os:find_executable("fdbcli", "/usr/local/bin") of
                false -> "fdbcli";
                Exec -> Exec
            end;
        Exec ->
            Exec
    end.

% Logs to stdout/stderr from either rebar3 or mix
Log = fun(Level, Fmt, Args) ->
    if
        RebarApi ->
            rebar_api:Level("[erlfdb] " ++ Fmt, Args);
        true ->
            Message = iolist_to_binary(io_lib:format(Fmt, Args)),
            Level2 =
                if
                    Level =:= abort -> error;
                    true -> Level
                end,
            ('Elixir.Mix':shell()):Level2(io_lib:format("===> [erlfdb] ~s", [Message])),
            if
                Level =:= abort ->
                    'Elixir.Mix':raise(Message);
                true ->
                    ok
            end
    end
end.

% Check architecture against regex
IsArch = fun(ArchRe) ->
    match =:= re:run(erlang:system_info(system_architecture), ArchRe, [{capture, none}])
end.

% Hacky means to extract API version from fdbcli protocol version output
% See https://github.com/apple/foundationdb/blob/master/flow/ProtocolVersion.h
MaxAPIVersion =
    begin
        VsnInfo = os:cmd(FdbCli ++ " --version"),
        case re:run(VsnInfo, "protocol ([a-f0-9]*)", [{capture, [1], list}]) of
            {match, [ProtocolStr]} ->
                ProtocolVsn = list_to_integer(ProtocolStr, 16),
                APIVersionBytes = (ProtocolVsn band 16#0000000FFF00000) bsr 20,
                list_to_integer(integer_to_list(APIVersionBytes, 16));
            nomatch ->
                undefined
        end
    end.

% Make an HTTP GET request, expect JSON body
GetJson = fun(Url) ->
    case code:ensure_loaded(json) of
        {module, _} ->
            Log(info, "  GET ~s", [Url]),
            case httpc:request(get, {Url, [{"User-Agent", "rebar3 erlfdb"}]}, [], []) of
                {ok, {{"HTTP/1.1", 200, "OK"}, _, Body}} ->
                    {ok, json:decode(iolist_to_binary(Body))};
                _ ->
                    {error, "HTTP GET Failed"}
            end;
        {error, _} ->
            {error, "Minimum OTP 27 required for release auto-detection"}
    end
end.

% Gets the latest release version from the official FoundationDB GitHub Releases
GetLatestRelease = fun() ->
    case GetJson(FDBReleasesBaseUrl ++ "/latest") of
        {ok, #{<<"tag_name">> := LatestTag}} ->
            case
                re:run(LatestTag, "(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)", [
                    {capture, [major, minor, patch], list}
                ])
            of
                {match, Matches} ->
                    {ok, [list_to_integer(X) || X <- Matches]};
                _ ->
                    {error, "Failed to parse FoundationDB version"}
            end;
        Error ->
            Error
    end
end.

% From the list of assets in the FDB Release, get those that match the regex
GetAssetByNameRe = fun(NameRe, Assets) ->
    case
        lists:filter(
            fun(#{<<"name">> := Name}) ->
                match =:= re:run(Name, NameRe, [{capture, none}])
            end,
            Assets
        )
    of
        [Asset | _] -> Asset;
        _ -> undefined
    end
end.

% Filter the list of assets in the FDB Release by the known supported architectures
GetSupportedAssets = fun(Assets) ->
    [
        {"(aarch64-apple-darwin)", #{
            server => GetAssetByNameRe("^FoundationDB.*arm64\\.pkg$", Assets),
            clients => undefined
        }},
        {"(x86_64-.*-linux)", #{
            server => GetAssetByNameRe("^foundationdb-server.*amd64\\.deb$", Assets),
            clients => GetAssetByNameRe("^foundationdb-clients.*amd64\\.deb$", Assets)
        }}
    ]
end.

% Gets the asset map for the latest non-AVX FDB Release on the current architecture
GetLatestNonAVXAssets = fun() ->
    % AVX releases have odd patch numbers, so we make sure we have the even one
    % If you want the AVX release, please install it manually.
    RVsn =
        case GetLatestRelease() of
            {ok, [Major, Minor, Patch]} when Patch rem 2 == 1 ->
                {ok, [Major, Minor, Patch - 1]};
            Res ->
                Res
        end,

    case RVsn of
        {ok, Vsn = [_, _, _]} ->
            Url = lists:flatten([FDBReleasesBaseUrl ++ "/tags/", io_lib:format("~p.~p.~p", Vsn)]),
            case GetJson(Url) of
                {ok, #{<<"assets">> := Assets}} ->
                    SupportedAssets = GetSupportedAssets(Assets),
                    case
                        lists:filter(fun({ArchRe, Assets}) -> IsArch(ArchRe) end, SupportedAssets)
                    of
                        [{_, AssetsMap} | _] ->
                            {ok, AssetsMap};
                        [] ->
                            {error, "Architecture may not have an official FoundationDB release."}
                    end;
                Error ->
                    Error
            end;
        Error ->
            Error
    end
end.

% Logs the list of assets to stdout
LogAssetsMap = fun
    LL(#{server := ServerAsset, clients := undefined}) ->
        Log(
            info,
            "For both the foundationdb-server and foundationdb-clients packages, download and install~n~n        ~ts~n",
            [maps:get(<<"browser_download_url">>, ServerAsset)]
        );
    LL(#{server := ServerAsset, clients := ClientsAsset}) ->
        Log(
            info,
            "For the foundationdb-server package, download and install~n~n        ~ts~n",
            [
                maps:get(<<"browser_download_url">>, ServerAsset)
            ]
        ),
        Log(
            info,
            "For the foundationdb-clients package, download and install~n~n        ~ts~n",
            [
                maps:get(<<"browser_download_url">>, ClientsAsset)
            ]
        )
end.

% If MaxAPIVersion undetected, print helpful informatiom about where to download the latest FDB Release
case MaxAPIVersion of
    undefined ->
        Log(info, "Checking for latest FoundationDB release...", []),
        {ok, _} = application:ensure_all_started(inets),
        Message =
            case application:ensure_all_started(ssl) of
                {ok, _} ->
                    case GetLatestNonAVXAssets() of
                        {ok, AssetsMap} ->
                            LogAssetsMap(AssetsMap),
                            undefined;
                        {error, _Reason} ->
                            "The foundationdb-clients package is required to compile erlfdb. Please visit~n~n        https://github.com/apple/foundationdb/releases~n"
                    end;
                {error, _Reason} ->
                    "The foundationdb-clients package is required to compile erlfdb. Please visit~n~n        https://github.com/apple/foundationdb/releases~n"
            end,
        [Log(info, Message, []) || Message =/= undefined],
        AssertFdbCli = "0" =/= os:getenv("ERLFDB_ASSERT_FDBCLI"),
        if
            AssertFdbCli ->
                Log(abort, "Error: fdbcli not found on PATH.", []);
            true ->
                Log(error, "fdbcli not found on PATH. Continuing anyway...", [])
        end;
    _ ->
        ok
end.

ErlOpts = proplists:get_value(erl_opts, CONFIG, []).
CONFIG1 = proplists:delete(erl_opts, CONFIG).

DynamicLibrary = case IsArch("darwin") of
    true ->
        "/usr/local/lib/libfdb_c.dylib";
    false ->
        "/usr/lib/libfdb_c.so"
end.
DynamicLibraryDir = filename:dirname(DynamicLibrary).

[
    {erl_opts, ErlOpts ++ [
        {d, erlfdb_api_version, MaxAPIVersion},
        {d, erlfdb_compile_time_external_client_library, DynamicLibrary}
    ]},
    {port_env, [
        {
            "(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)",
            "CFLAGS",
            "$CFLAGS -I"++IncludeDir++" -Ic_src/ -g -Wall -Werror " ++
                (if
                    MaxAPIVersion < 730 ->
                        "-DFDB_API_VERSION=" ++ integer_to_list(MaxAPIVersion);
                    true ->
                        "-DFDB_USE_LATEST_API_VERSION=1"
                end)
        },
        {
            "(linux|solaris|freebsd|netbsd|openbsd|dragonfly|gnu)",
            "LDFLAGS",
            "$LDFLAGS -L"++DynamicLibraryDir++" -lfdb_c"
        },
        {
            "(darwin)",
            "LDFLAGS",
            "$LDFLAGS -rpath "++DynamicLibraryDir++" -L"++DynamicLibraryDir++" -lfdb_c"
        }
    ]}
] ++ CONFIG1.
