%% vim: set ft=erlang : -*- erlang -*- % Magic lines for code editors

%% Licensed under the Apache License, Version 2.0 (the "License"); you may not
%% use this file except in compliance with the License. You may obtain a copy of
%% the License at
%%
%%   http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
%% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
%% License for the specific language governing permissions and limitations under
%% the License.

CopyIfDifferent = fun(Path, Contents) ->
    case filelib:is_file(Path) of
        true ->
            case file:read_file(Path) of
                {ok, Contents} ->
                    ok;
                _ ->
                    file:write_file(Path, Contents)
            end;
        false ->
            file:write_file(Path, Contents)
    end
end.


CouchJSName = case os:type() of
    {win32, _} ->
        "couchjs.exe";
    _ ->
        "couchjs"
end.
CouchJSPath = filename:join(["priv", CouchJSName]).
Version = case os:getenv("COUCHDB_VERSION") of
    false ->
        string:trim(os:cmd("git describe --always"));
    Version0 ->
        string:trim(Version0)
end.

GitSha = case os:getenv("COUCHDB_GIT_SHA") of
    false ->
        ""; % release builds won\'t get a fallback
    GitSha0 ->
        string:trim(GitSha0)
end.

CouchConfig = case filelib:is_file(os:getenv("COUCHDB_CONFIG")) of
    true ->
        {ok, Result} = file:consult(os:getenv("COUCHDB_CONFIG")),
        Result;
    false ->
        []
end.

JsEngine = case lists:keyfind(js_engine, 1, CouchConfig) of
    {_, "spidermonkey"} ->
        "spidermonkey";
    {_, "quickjs"} ->
        "quickjs";
    {_, InvalidJsEngine} ->
        io:format(standard_error, "Unsupported default JS engine ~p~n", [InvalidJsEngine]),
        erlang:halt(1);
    false ->
        "spidermonkey"
end.

WithSpidermonkey = case {lists:keyfind(with_spidermonkey, 1, CouchConfig), JsEngine} of
    {{_, false}, "spidermonkey"} ->
         io:format(standard_error, "Spidermonkey is disabled, select another default js_engine ~n", []),
         erlang:halt(1);
    {{_, false}, _} ->
         false;
    {_, _} ->
         true
end.

SMVsn = case lists:keyfind(spidermonkey_version, 1, CouchConfig) of
    {_, "1.8.5"} ->
        "1.8.5";
    {_, "60"} ->
        "60";
    {_, "68"} ->
        "68";
    {_, "78"} ->
        "78";
    {_, "86"} ->
        "86";
    {_, "91"} ->
        "91";
    {_, "102"} ->
        "102";
    {_, "115"} ->
        "115";
    {_, "128"} ->
        "128";
    undefined ->
        "128";
    {_, Unsupported} ->
        io:format(standard_error, "Unsupported SpiderMonkey version: ~s~n", [Unsupported]),
        erlang:halt(1);
    false ->
        "128"
end.

ConfigH = [
    {"SM185", ""},
    {"HAVE_JS_GET_STRING_CHARS_AND_LENGTH", "1"},
    {"JSSCRIPT_TYPE", "JSObject*"},
    {"COUCHJS_NAME", "\"" ++ CouchJSName++ "\""},
    {"PACKAGE", "\"apache-couchdb\""},
    {"PACKAGE_BUGREPORT", "\"https://github.com/apache/couchdb/issues\""},
    {"PACKAGE_NAME", "\"Apache CouchDB\""},
    {"PACKAGE_STRING", "\"Apache CouchDB " ++ Version ++ "\""},
    {"PACKAGE_VERSION", "\"" ++ Version ++ "\""}
].

CouchJSConfig = case SMVsn of
    "78" ->
        "priv/couch_js/86/config.h";
    "91" ->
        "priv/couch_js/86/config.h";
    "102" ->
        "priv/couch_js/102/config.h";
    "115" ->
        "priv/couch_js/102/config.h";
    "128" ->
        "priv/couch_js/102/config.h";
    _ ->
        "priv/couch_js/" ++ SMVsn ++ "/config.h"
end.
ConfigSrc = [["#define ", K, " ", V, $\n] || {K, V} <- ConfigH].
ConfigBin = iolist_to_binary(ConfigSrc).
case WithSpidermonkey of
    true -> ok = CopyIfDifferent(CouchJSConfig, ConfigBin);
    false -> ok
end.

MD5Config = case lists:keyfind(erlang_md5, 1, CouchConfig) of
    {erlang_md5, true} ->
        [{d, 'ERLANG_MD5', true}];
    _ ->
        []
end.

ProperConfig = case code:lib_dir(proper) of
    {error, bad_name} -> [];
    _ -> [{d, 'WITH_PROPER'}]
end.

DpkgArchitectureCmd = "dpkg-architecture -q DEB_HOST_MULTIARCH".
GenericMozJSIncludePaths = "-I/usr/include/mozjs-" ++ SMVsn ++ " -I/usr/local/include/mozjs-" ++ SMVsn.
GenericMozJSLibPaths = "-L/usr/local/lib".

WithDpkgArchitecture = os:find_executable("dpkg-architecture") /= false.
WithHomebrew = os:find_executable("brew") /= false.

MozJSIncludePath = case {WithDpkgArchitecture, WithHomebrew} of
    {false, false} -> GenericMozJSIncludePaths;
    {true, false} -> GenericMozJSIncludePaths ++ " -I/usr/include/" ++ string:trim(os:cmd(DpkgArchitectureCmd)) ++ "/mozjs-" ++ SMVsn;
    {false, true} -> GenericMozJSIncludePaths ++ " -I/opt/homebrew/include/mozjs-" ++ SMVsn
end.

MozJSLibPath = case {WithDpkgArchitecture, WithHomebrew} of
    {false, false} -> GenericMozJSLibPaths;
    {true, false} -> GenericMozJSLibPaths ++ " -L/usr/lib/" ++ string:trim(os:cmd(DpkgArchitectureCmd));
    {false, true} -> GenericMozJSLibPaths ++ " -L/opt/homebrew/lib"
end.

% The include directories (parameters for the `-I` C compiler flag) are
% considered in the `configure` script as a pre-check for their existence.
% Please keep them in sync.
{JS_CFLAGS, JS_LDFLAGS} = case os:type() of
    {win32, _} when SMVsn == "1.8.5" ->
        {
            "/DXP_WIN",
            "mozjs185-1.0.lib"
        };
    {unix, _} when SMVsn == "1.8.5" ->
        {
            "-DXP_UNIX -I/usr/include/js -I/usr/local/include/js",
            "-L/usr/local/lib -lmozjs185 -lm"
        };
    {win32, _} when SMVsn == "60" ->
        {
            "/DXP_WIN",
            "mozjs-60.lib"
        };
    {unix, darwin} when SMVsn == "60" ->
        {
            "-DXP_UNIX -I/usr/include/mozjs-60 -I/usr/local/include/mozjs-60 -std=c++14",
            "-L/usr/local/lib -lmozjs-60 -lm -std=c++14 -lc++"
        };
    {unix, _} when SMVsn == "60" ->
        {
            "-DXP_UNIX -I/usr/include/mozjs-60 -I/usr/local/include/mozjs-60 -std=c++14 -Wno-invalid-offsetof",
            "-L/usr/local/lib -std=c++14 -lmozjs-60 -lm"
        };
    {unix, _} when SMVsn == "68" ->
        {
            "-DXP_UNIX -I/usr/include/mozjs-68 -I/usr/local/include/mozjs-68 -std=c++14 -Wno-invalid-offsetof",
            "-L/usr/local/lib -std=c++14 -lmozjs-68 -lm"
        };
    {unix, _} when SMVsn == "78" ->
        {
            "-DXP_UNIX -I/usr/include/mozjs-78 -I/usr/local/include/mozjs-78 -std=c++17 -Wno-invalid-offsetof",
            "-L/usr/local/lib -std=c++17 -lmozjs-78 -lm"
        };
    {unix, _} when SMVsn == "86" ->
        {
            "-DXP_UNIX -I/usr/include/mozjs-86 -I/usr/local/include/mozjs-86 -I/opt/homebrew/include/mozjs-86/ -std=c++17 -Wno-invalid-offsetof",
            "-L/usr/local/lib -L /opt/homebrew/lib/ -std=c++17 -lmozjs-86 -lm"
        };
    {unix, _} when SMVsn == "91"; SMVsn == "102"; SMVsn == "115"; SMVsn == "128" ->
        {
            "$CFLAGS -DXP_UNIX " ++ MozJSIncludePath ++ " -std=c++17 -Wno-invalid-offsetof",
            "$LDFLAGS " ++ MozJSLibPath ++ " -std=c++17 -lm -lmozjs-" ++ SMVsn
        };
    {win32, _} when SMVsn == "91" ->
        {
            "/std:c++17 /DXP_WIN",
            "$LDFLAGS mozjs-91.lib"
        };
    {win32, _} when SMVsn == "102"; SMVsn == "115"; SMVsn == "128" ->
        {
            "/std:c++17 /DXP_WIN /Zc:preprocessor /utf-8",
            "$LDFLAGS mozjs-" ++ SMVsn ++ ".lib"
        }
end.

CouchJSSrc = case SMVsn of
    "1.8.5" -> ["priv/couch_js/1.8.5/*.c"];
    "60" -> ["priv/couch_js/60/*.cpp"];
    "68" -> ["priv/couch_js/68/*.cpp"];
    "78" -> ["priv/couch_js/86/*.cpp"];
    "86" -> ["priv/couch_js/86/*.cpp"];
    "91" -> ["priv/couch_js/86/*.cpp"];
    "102" -> ["priv/couch_js/102/*.cpp"];
    "115" -> ["priv/couch_js/102/*.cpp"];
    "128" -> ["priv/couch_js/102/*.cpp"]
end.

CouchJSEnv = case SMVsn of
    "1.8.5" ->
        [
            {"CFLAGS", JS_CFLAGS},
            {"LDFLAGS", JS_LDFLAGS}
        ];
    _ ->
        [
            {"CXXFLAGS", JS_CFLAGS},
            {"LDFLAGS", JS_LDFLAGS}
        ]
end.

BrewIcuPrefixCmd = "brew --prefix icu4c 2> /dev/null".
GenericIcuIncludePaths = "-I/usr/local/opt/icu4c/include -I/opt/homebrew/opt/icu4c/include".
GenericIcuLibPaths = "-L/usr/local/opt/icu4c/lib -L/opt/homebrew/opt/icu4c/lib".

WithBrew = case os:find_executable("brew") of
    false -> false;
    _ -> true
end.

IcuIncludePath = case WithBrew of
    false -> GenericIcuIncludePaths;
    true -> "-I" ++ string:trim(os:cmd(BrewIcuPrefixCmd)) ++ "/include"
end.

IcuLibPath = case WithBrew of
    false -> GenericIcuLibPaths;
    true -> "-L" ++ string:trim(os:cmd(BrewIcuPrefixCmd)) ++ "/lib"
end.

IcuEnv = [{"DRV_CFLAGS",  "$DRV_CFLAGS -DPIC -O2 -fno-common -fvisibility=hidden"},
          {"DRV_LDFLAGS", "$DRV_LDFLAGS -lm -licuuc -licudata -licui18n -lpthread"},
          {"LDFLAGS", "$LDFLAGS"},
          {"CFLAGS", "$CFLAGS"}].
IcuDarwinEnv = [{"CFLAGS", "-DXP_UNIX " ++ IcuIncludePath},
                {"LDFLAGS", IcuLibPath}].
IcuBsdEnv = [{"CFLAGS", "-DXP_UNIX -I/usr/local/include"},
             {"LDFLAGS", "-L/usr/local/lib"}].
IcuWinEnv = [{"CFLAGS", "$DRV_CFLAGS /DXP_WIN"},
             {"LDFLAGS", "$LDFLAGS icuin.lib icudt.lib icuuc.lib"}].

ComparePath = "priv/couch_ejson_compare.so".
CompareSrc = ["priv/couch_ejson_compare/*.c"].

CouchCFileEnv = [{"CFLAGS", "$CFLAGS -Wall -Werror -DNDEBUG -O3 -fvisibility=hidden"}].
CouchCFilePath = "priv/couch_cfile.so".
CouchCFileSrc = ["priv/couch_cfile/*.c"].

SpidermonkeySpecs = case WithSpidermonkey of
    true -> [{".*", CouchJSPath, CouchJSSrc, [{env, CouchJSEnv}]}];
    false -> []
end.

PortSpecs = SpidermonkeySpecs ++ [
        % ejson_compare
        {"darwin", ComparePath, CompareSrc, [{env, IcuEnv ++ IcuDarwinEnv}]},
        {"linux",  ComparePath, CompareSrc, [{env, IcuEnv}]},
        {"bsd",   ComparePath, CompareSrc, [{env, IcuEnv ++ IcuBsdEnv}]},
        {"win32",  ComparePath, CompareSrc, [{env, IcuWinEnv}]},
        {"(linux|bsd|darwin)", CouchCFilePath, CouchCFileSrc, [{env, CouchCFileEnv}]},
        {"win32",  CouchCFilePath, CouchCFileSrc, []}
].

%% hack required until switch to enc/rebar3
PortEnvOverrides = [
    {"win32", "EXE_LINK_CXX_TEMPLATE",
    "$LINKER $PORT_IN_FILES $LDFLAGS $EXE_LDFLAGS /OUT:$PORT_OUT_FILE"}
].

PlatformDefines = [
   {platform_define, "win32", 'WINDOWS'}
].
AddConfig = [
    {port_specs, PortSpecs},
    {erl_opts, PlatformDefines ++ [
        {d, 'COUCHDB_VERSION', Version},
        {d, 'COUCHDB_GIT_SHA', GitSha},
        {d, 'COUCHDB_JS_ENGINE', JsEngine},
        {d, 'COUCHDB_WITH_SPIDERMONKEY', WithSpidermonkey},
        {d, 'COUCHDB_SPIDERMONKEY_VERSION', SMVsn},
        {i, "../"}
    ] ++ MD5Config ++ ProperConfig},
    {port_env, PortEnvOverrides},
    {eunit_compile_opts, PlatformDefines}
].

lists:foldl(fun({K, V}, CfgAcc) ->
    case lists:keyfind(K, 1, CfgAcc) of
        {K, Existent} when is_list(Existent) andalso is_list(V) ->
            lists:keystore(K, 1, CfgAcc, {K, Existent ++ V});
        false ->
            lists:keystore(K, 1, CfgAcc, {K, V})
    end
end, CONFIG, AddConfig).
