%% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ft=erlang ts=4 sw=4 et

%% 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.
%%
%%

%% Utility script to compile query server .js files into C arrays. Main JS and
%% Coffeescript files are treated separately. First each is bundled into a
%% single .js file. Then the file is passed to qjsc to produce the bytecode
%% array.
%%

-export([main/1]).

-mode(compile).

main(["compile"]) ->
    concat_js_files("bundle_mainjs.js", "bundle_coffee.js"),
    Changed1 = compile_bytecode("bundle_mainjs.js", "couchjs_mainjs_bytecode.c"),
    Changed2 = compile_bytecode("bundle_coffee.js", "couchjs_coffee_bytecode.c"),
    case Changed1 orelse Changed2 of
        true ->
            % A stupid hack. The compile step is often too quick and
            % generates .o timestamps with the same 1 second timestamp
            % as the .c file. During dev work, it means it will
            % re-compile and re-link everything uncessarily at least
            % one more time as the port compiler compares timestamps
            % with the >= operator.
            timer:sleep(1000),
            ok;
        false ->
            ok
    end;
main(["clean"]) ->
    file:del_dir_r("priv"),
    rm("c_src/couchjs_*_bytecode.c");
main(Arg) ->
    io:format(standard_error, "Expected a 'compile' or 'clean' arg. Got:~p", [Arg]),
    halt(1).

concat_js_files(JsScript, CoffeeScript) ->
    Prefix = "../../share/server/",
    JsFiles = [
        "rewrite_fun.js",
        "dreyfus.js",
        "nouveau.js",
        "filter.js",
        "mimeparse.js",
        "render.js",
        "state.js",
        "util.js",
        "validate.js",
        "views.js"
    ],
    Main = JsFiles ++ ["dispatch-quickjs.js"],
    Coffee = JsFiles ++ ["coffee-script.js", "dispatch-quickjs.js"],
    concat([Prefix ++ File || File <- Main], "priv/" ++ JsScript),
    concat([Prefix ++ File || File <- Coffee], "priv/" ++ CoffeeScript),
    ok.

compile_bytecode(Js, CBytecode) ->
    % cp_if_different/2 is used to to avoid triggering a re-compile if nothing changed
    Tmp = CBytecode ++ ".tmp",
    {ok, Cwd} = file:get_cwd(),
    CompileCmd = Cwd ++ "/quickjs/qjsc -c -N bytecode -o c_src/" ++ Tmp ++ " priv/" ++ Js,
    os:cmd(CompileCmd, #{exception_on_failure => true}),
    Changed = cp_if_different("c_src/" ++ Tmp, "c_src/" ++ CBytecode),
    rm("c_src/" ++ Tmp),
    Changed.

cp_if_different(From, To) ->
    Bin = fread(From),
    case filelib:is_file(To) of
        true ->
            case fread(To) of
                Bin ->
                    false;
                <<_/binary>> ->
                    ok = fwrite(To, Bin),
                    true
            end;
        false ->
            ok = fwrite(To, Bin),
            true
    end.

concat(Sources, Target) ->
    SourceBins = [fread(P) || P <- Sources],
    TargetBin =  iolist_to_binary(["(function () {\n"] ++ SourceBins ++ ["})();\n"]),
    ok = filelib:ensure_dir(Target),
    fwrite(Target, TargetBin).

fread(Path) ->
    {ok, Bin} = file:read_file(Path),
    Bin.

fwrite(Path, Bin) when is_binary(Bin) ->
    ok = file:write_file(Path, Bin).

rm(Path) ->
    Fun = fun(F) ->
        case filelib:is_file(F) of
            true -> ok = file:delete(F);
            false -> ok
        end
    end,
    lists:foreach(Fun, filelib:wildcard(Path)).
