#!/usr/bin/env escript
%% Use -*- erlang -*- mode in Erlang

%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2010-2018. All Rights Reserved.
%%
%% 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.
%%
%% %CopyrightEnd%
%%

-module(diameterc).

-mode(compile).

-include_lib("kernel/include/file.hrl").

%% The parsed command line.
-record(argv, {file,
               options = [{outdir, "."}],
               output  = [erl, hrl]}).

usage() ->
    io:format(
      "~w [options] dict~n"
      "~n"
      "  Compile a diameter dictionary file (.dia) to Erlang source (.erl)~n"
      "  and/or header (.hrl) files.~n"
      "~n"
      "  options:~n"
      "~n"
      "      --name name       = set @name~n"
      "      --prefix prefix   = set @prefix~n"
      "      --inherits dict|- = set/clear @inherits~n"
      "~n"
      "      -h        = print this message~n"
      "      -v        = verbose output~n"
      "      -o dir    = set the output directory (default .)~n"
      "      -i dir    = set an include directory for inherited beams~n"
      "      -E        = no .erl output~n"
      "      -H        = no .hrl output~n"
      "      -d        = write intermediate files (.D and .F)~n",
      [?MODULE]).

main(Args) ->
    halt(gen(Args)).

gen(Args) ->
    try parse_args(Args) of
        #argv{} = A ->
            compile(A)
    catch
        throw: usage  ->
            usage(),
            0;
        throw: Reason ->
            error_msg(norm(Reason)),
            1
    end.

compile(#argv{file = File, options = Opts, output = Out}) ->
    try diameter_make:codec({path, File}, Opts ++ Out) of
        ok ->
            0;
        {error, Reason} ->
            error_msg(diameter_make:format_error(Reason), []),
            1
    catch
        error: Reason: Stack ->
            error_msg("ERROR: ~p~n  ~p", [Reason, Stack]),
            2
    end.

error_msg({Fmt, Args}) ->
    error_msg(Fmt, Args).

error_msg(Fmt, Args) ->
    io:format(standard_error, "** " ++ Fmt ++ "~n", Args).

norm({_,_} = T) ->
    T;
norm(Str) ->
    {Str, []}.

%% parse_args/1

parse_args(Args)
  when is_list(Args) ->
    arg(Args, #argv{}).

arg(["-h" | _], _) ->
    throw(usage);

arg(["-v" | Args], #argv{options = Opts} = A) ->
    arg(Args, A#argv{options = [verbose | Opts]});

arg(["-o", Dir | Args], #argv{options = Opts} = A) ->
    true = dir_exists(Dir),
    arg(Args, A#argv{options = [{outdir, Dir} | Opts]});

arg(["-i", Dir | Args], #argv{} = A) ->
    code:add_patha(Dir), %% Set path here instead of passing an include
    arg(Args, A);        %% option so it's set before calling diameter_make.

arg(["--name", Name | Args], #argv{options = Opts} = A) ->
    arg(Args, A#argv{options = [{name, Name} | Opts]});

arg(["--prefix", Name | Args], #argv{options = Opts} = A) ->
    arg(Args, A#argv{options = [{prefix, Name} | Opts]});

arg(["--inherits", Dict | Args], #argv{options = Opts} = A) ->
    arg(Args, A#argv{options = Opts ++ [{inherits, Dict}]});

arg(["-E" | Args], #argv{output = Output} = A) ->
    arg(Args, A#argv{output = lists:delete(erl, Output)});

arg(["-H" | Args], #argv{output = Output} = A) ->
    arg(Args, A#argv{output = lists:delete(hrl, Output)});

arg(["-d" | Args], #argv{output = Output} = A) ->
    arg(Args, A#argv{output = [parse, forms | Output -- [parse, forms]]});

arg([[$- = M, C, H | T] | Args], A)  %% clustered options
  when C /= $i, C /= $o, C /= $- ->
    arg([[M,C], [M,H|T] | Args], A);

arg([File], A) ->
    true = file_exists(File),
    A#argv{file = File};

arg([], _) ->
    throw("No input file");

arg([Bad | _], _) ->
    throw({"Unknown option: ~p", [Bad]}).

%% path_exists/2

path_exists(File, Type) ->
    case file:read_file_info(File) of
	{ok, #file_info{type = Type}} ->
	    true;
	{ok, #file_info{type = WrongType}} ->
	    throw({"Invalid type for file: ~p, ~p", [WrongType, File]});
	_ ->
	    throw({"No such file: ~p", [File]})
    end.

file_exists(File) ->
    path_exists(File, regular).

dir_exists(File) ->
    path_exists(File, directory).
