#!/usr/bin/env escript
%% -*- erlang -*-
-include_lib("kernel/include/file.hrl").


is_up_to_date(Target, Deps) ->
    case file:read_file_info(Target) of
        {ok, #file_info{mtime = TargetMTime}} ->
            lists:all(fun(Dep) ->
                              case file:read_file_info(Dep) of
                                  {ok, #file_info{mtime = MTime}} ->
                                      MTime =< TargetMTime;
                                  _ ->
                                      false
                              end
                      end, Deps);
        _ ->
            false
    end.

make_target(Target, Deps, CmdFun) ->
    case is_up_to_date(Target, Deps) of
        true ->
            ok;
        _ ->
            io:format("Compiling ~s...~n", [filename:basename(hd(Deps))]),
            CmdFun()
    end.

make(Makescript) ->
    lists:foreach(
      fun({Target, Deps, CmdFun}) -> make_target(Target, Deps, CmdFun) end,
      Makescript).

gpb_compile_file_cmd(Proto, Opts) ->
    fun() ->
            check_ok_or_halt(gpb_compile:file(Proto, Opts))
    end.

compile_file_cmd(Erl, Opts) ->
    fun() ->
            check_ok_or_halt(compile:file(Erl, Opts))
    end.

check_ok_or_halt(Res) ->
    if Res =:= ok -> ok;
       element(1, Res) =:= ok -> ok;
       true -> halt(1)
    end.

deps_dir() ->
    case os:getenv("REBAR_DEPS_DIR") of
        false ->
            %% Elixir 1.12+
            os:getenv("REBAR_BARE_COMPILER_OUTPUT_DIR");
        DepsDir ->
            DepsDir
    end.

checkouts_out_dir() ->
    os:getenv("REBAR_CHECKOUTS_OUT_DIR").

gpb_exists_via_env(DirEnvValue) ->
    if DirEnvValue == false  ->
            false;
       is_list(DirEnvValue) ->
            filelib:is_dir(filename:join(DirEnvValue, "gpb"))
    end.

main(_) ->
    REBAR_CHECKOUTS_OUT_DIR = checkouts_out_dir(),
    REBAR_DEPS_DIR = deps_dir(),
    GpbExistsInCheckoutsOutDir = gpb_exists_via_env(REBAR_CHECKOUTS_OUT_DIR),
    GpbExistsInDepsDir = gpb_exists_via_env(REBAR_DEPS_DIR),

    {EbinPath, TestPath} =
        if GpbExistsInCheckoutsOutDir ->
                %% With _checkouts and rebar3 3.14 or later
                {filename:join([REBAR_CHECKOUTS_OUT_DIR, "gpb", "ebin"]),
                 filename:join([REBAR_CHECKOUTS_OUT_DIR, "gpb", "test"])};
           GpbExistsInDepsDir ->
                {filename:join([REBAR_DEPS_DIR, "gpb", "ebin"]),
                 filename:join([REBAR_DEPS_DIR, "gpb", "test"])};
           true ->
                %% rebar2 (or make)
                {"./ebin", "./test"}
        end,

    filelib:ensure_dir(filename:join(EbinPath, ".dummy")),

    GpbcOpts = [{i, "priv/proto3/google/protobuf"},
                {o, "descr_src"}, {module_name_prefix, "gpb_"},
                report],
    ErlcOpts = [{i, "descr_src"}, {i,"include"},
                {outdir, EbinPath}, debug_info,
                report],

    code:add_patha(EbinPath),

    MakeScript =
        [{"descr_src/gpb_descriptor.erl",
          ["priv/proto3/google/protobuf/descriptor.proto"],
          gpb_compile_file_cmd("descriptor.proto", GpbcOpts)},

         {filename:join(EbinPath, "gpb_descriptor.beam"),
          ["descr_src/gpb_descriptor.erl"],
          compile_file_cmd("descr_src/gpb_descriptor.erl", ErlcOpts)},

         {filename:join(EbinPath, "gpb_compile_descr.beam"),
          ["descr_src/gpb_compile_descr.erl",
           "descr_src/gpb_descriptor.hrl"],
          compile_file_cmd("descr_src/gpb_compile_descr.erl", ErlcOpts)},

         {filename:join(EbinPath, "gpb_parse_descr.beam"),
          ["descr_src/gpb_parse_descr.erl",
           "descr_src/gpb_descriptor.hrl"],
          compile_file_cmd("descr_src/gpb_parse_descr.erl", ErlcOpts)}],

    make(MakeScript),
    case filelib:is_dir(TestPath) of
        true ->
            %% eg: "rebar3 eunit" or "rebar2 eunit"
            %% but not "rebar3 compile"
            %% and not when compiling gpb as a dependency, since
            %% the test directory does not exist in the hex package.

            ErlcTestOpts = [{i, "descr_src"}, {i, "include"},
                            {outdir, TestPath}, debug_info],

            TestMakeScript =
                [{filename:join(TestPath, "gpb_compile_descr_tests.beam"),
                  ["descr_src/gpb_compile_descr_tests.erl",
                   "descr_src/gpb_compile_descr.erl",
                   "descr_src/gpb_descriptor.hrl"],
                  compile_file_cmd("descr_src/gpb_compile_descr_tests.erl",
                                   ErlcTestOpts)},

                 {filename:join(TestPath, "gpb_parse_descr_tests.beam"),
                  ["descr_src/gpb_parse_descr_tests.erl",
                   "descr_src/gpb_parse_descr.erl",
                   "descr_src/gpb_descriptor.hrl"],
                  compile_file_cmd("descr_src/gpb_parse_descr_tests.erl",
                                   ErlcTestOpts)}],
            make(TestMakeScript);
        _ ->
            next
    end.
