%%%----------------------------------------------------------------------
%%% File    : extract_translations.erl
%%% Author  : Sergei Golovan <sgolovan@nes.ru>
%%% Purpose : Auxiliary tool for interface/messages translators
%%% Created : 23 Apr 2005 by Sergei Golovan <sgolovan@nes.ru>
%%% Id      : $Id$
%%%----------------------------------------------------------------------

-module(extract_translations).
-author('sgolovan@nes.ru').

-export([start/0]).

-define(STATUS_SUCCESS, 0).
-define(STATUS_ERROR,   1).
-define(STATUS_USAGE,   2).

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


start() ->
    ets:new(translations, [named_table, public]),
    ets:new(translations_obsolete, [named_table, public]),
    ets:new(files, [named_table, public]),
    ets:new(vars, [named_table, public]),
    case init:get_plain_arguments() of
	["-srcmsg2po", Dir, File] ->
	    print_po_header(File),
	    Status = process(Dir, File, srcmsg2po),
	    halt(Status);
	["-unused", Dir, File] ->
	    Status = process(Dir, File, unused),
	    halt(Status);
	[Dir, File] ->
	    Status = process(Dir, File, used),
	    halt(Status);
	_ ->
	    print_usage(),
	    halt(?STATUS_USAGE)
    end.


process(Dir, File, Used) ->
    case load_file(File) of
	{error, Reason} ->
	    io:format("~s: ~s~n", [File, file:format_error(Reason)]),
	    ?STATUS_ERROR;
	_ ->
	    FileList = find_src_files(Dir),
	    lists:foreach(
	      fun(F) ->
		    parse_file(Dir, F, Used)
	      end, FileList),
	    case Used of
		unused ->
		    ets:foldl(fun({Key, _}, _) ->
				      io:format("~p~n", [Key])
			      end, ok, translations);
		srcmsg2po ->
		    ets:foldl(fun({Key, Trans}, _) ->
				      print_translation_obsolete(Key, Trans)
			      end, ok, translations_obsolete);
		_ ->
		    ok
	    end,
	    ?STATUS_SUCCESS
    end.

parse_file(Dir, File, Used) ->
    ets:delete_all_objects(vars),
    case epp:parse_file(File, [Dir, filename:dirname(File) | code:get_path()], []) of
	{ok, Forms} ->
	    lists:foreach(
	      fun(F) ->
		    parse_form(Dir, File, F, Used)
	      end, Forms);
	_ ->
	    ok
    end.

parse_form(Dir, File, Form, Used) ->
    case Form of
	%%{undefined, Something} ->
        %% io:format("Undefined: ~p~n", [Something]);
	{call,
	 _,
	 {remote, _, {atom, _, translate}, {atom, _, translate}},
	 [_, {string, Line, Str}]
	} ->
	    process_string(Dir, File, Line, Str, Used);
	{call,
	 _,
	 {remote, _, {atom, _, translate}, {atom, _, translate}},
	 [_,
	  {bin,_,
	   [{bin_element,_,
	     {string,Line,Str},
	     default,default}]}]
	} ->
	    process_string(Dir, File, Line, Str, Used);
	{call,
	 _,
	 {remote, _, {atom, _, translate}, {atom, _, translate}},
	 [_, {var, _, Name}]
	} ->
	    case ets:lookup(vars, Name) of
		[{_Name, Value, Line}] ->
		    process_string(Dir, File, Line, Value, Used);
		_ ->
		    ok
	    end;
	{match,
	 _,
	 {var, _, Name},
	 {string, Line, Value}
	} ->
	    ets:insert(vars, {Name, Value, Line});
        {match,
         _,
         {var, _, Name},
         {bin,Line,[{bin_element,_,{string,_,Value},_,_}]}
        } ->
            ets:insert(vars, {Name, Value, Line});
	L when is_list(L) ->
	    lists:foreach(
	      fun(F) ->
		      parse_form(Dir, File, F, Used)
	      end, L);
	T when is_tuple(T) ->
	    lists:foreach(
	      fun(F) ->
		      parse_form(Dir, File, F, Used)
	      end, tuple_to_list(T));
	_ ->
	    ok
    end.
	    
process_string(_Dir, _File, _Line, "", _Used) ->
    ok;

process_string(_Dir, File, Line, Str, Used) ->
    case {ets:lookup(translations, Str), Used} of
	{[{_Key, _Trans}], unused} ->
	    ets:delete(translations, Str);
	{[{_Key, _Trans}], used} ->
	    ok;
	{[{_Key, Trans}], srcmsg2po} ->
	    ets:delete(translations_obsolete, Str),
	    print_translation(File, Line, Str, Trans);
	{_, used} ->
	    case ets:lookup(files, File) of
		[{_}] ->
		    ok;
		_ ->
		    io:format("~n% ~s~n", [File]),
		    ets:insert(files, {File})
	    end,
	    case Str of
		[] -> ok;
		_ -> io:format("{~p, \"\"}.~n", [Str])
	    end,
	    ets:insert(translations, {Str, ""});
	{_, srcmsg2po} ->
	    case ets:lookup(files, File) of
		[{_}] ->
		    ok;
		_ ->
		    ets:insert(files, {File})
	    end,
	    ets:insert(translations, {Str, ""}),
	    print_translation(File, Line, Str, "");
	_ ->
	    ok
    end.

load_file(File) ->
    case file:consult(File) of
	{ok, Terms} ->
	    lists:foreach(
	      fun({Orig, Trans}) ->
		    case Trans of
			"" ->
			    ok;
			_ ->
			    ets:insert(translations, {Orig, Trans}),
			    ets:insert(translations_obsolete, {Orig, Trans})
		    end
	      end, Terms);
	Err ->
	    Err
    end.

find_src_files(Dir) ->
    case file:list_dir(Dir) of
	{ok, FileList} ->
	    recurse_filelist(
	      lists:map(
	        fun(F) ->
			filename:join(Dir, F)
		end, FileList));
	_ ->
	    []
    end.

recurse_filelist(FileList) ->
    recurse_filelist(FileList, []).

recurse_filelist([], Acc) ->
    lists:reverse(Acc);

recurse_filelist([H | T], Acc) ->
    case file:read_file_info(H) of
	{ok, #file_info{type = directory}} ->
	    recurse_filelist(T, lists:reverse(find_src_files(H)) ++ Acc);
	{ok, #file_info{type = regular}} ->
	    case string:substr(H, string:len(H) - 3) of
		".erl" ->
		    recurse_filelist(T, [H | Acc]);
		".hrl" ->
		    recurse_filelist(T, [H | Acc]);
		_ ->
		    recurse_filelist(T, Acc)
	    end;
	_ ->
	    recurse_filelist(T, Acc)
    end.


print_usage() ->
    io:format(
      "Usage: extract_translations [-unused] dir file~n"
      "~n"
      "Example:~n"
      "  extract_translations . ./msgs/ru.msg~n"
     ).


%%%
%%% Gettext
%%%

print_po_header(File) ->
    MsgProps = get_msg_header_props(File),
    {Language, [LastT | AddT]} = prepare_props(MsgProps),
    print_po_header(Language, LastT, AddT).

get_msg_header_props(File) ->
    {ok, F} = file:open(File, [read]),
    Lines = get_msg_header_props(F, []),
    file:close(F),
    Lines.

get_msg_header_props(F, Lines) ->
    String = io:get_line(F, ""),
    case io_lib:fread("% ", String) of
	{ok, [], RemString} ->
	    case io_lib:fread("~s", RemString) of
		{ok, [Key], Value} when Value /= "\n" ->
		    %% The first character in Value is a blankspace:
		    %% And the last characters are 'slash n'
		    ValueClean = string:substr(Value, 2, string:len(Value)-2),
		    get_msg_header_props(F, Lines ++ [{Key, ValueClean}]);
		_ ->
		    get_msg_header_props(F, Lines)
	    end;
	_ ->
	    Lines
    end.

prepare_props(MsgProps) ->
    Language = proplists:get_value("Language:", MsgProps),
    Authors = proplists:get_all_values("Author:", MsgProps),
    {Language, Authors}.

print_po_header(Language, LastTranslator, AdditionalTranslatorsList) ->
    AdditionalTranslatorsString = build_additional_translators(AdditionalTranslatorsList),
    HeaderString =
	"msgid \"\"\n"
	"msgstr \"\"\n"
	++ "\"X-Language: " ++ Language ++ "\\n\"\n"
	"\"Last-Translator: " ++ LastTranslator ++ "\\n\"\n"
	++ AdditionalTranslatorsString ++
	"\"MIME-Version: 1.0\\n\"\n"
	"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
	"\"Content-Transfer-Encoding: 8bit\\n\"\n",
    io:format("~s~n", [HeaderString]).

build_additional_translators(List) ->
    lists:foldl(
      fun(T, Str) ->
	      Str ++ "\"X-Additional-Translator: " ++ T ++ "\\n\"\n"
      end,
      "",
      List).

print_translation(File, Line, Str, StrT) ->
    StrQ = ejabberd_regexp:greplace(list_to_binary(Str), <<"\\\"">>, <<"\\\\\"">>),
    StrTQ = ejabberd_regexp:greplace(list_to_binary(StrT), <<"\\\"">>, <<"\\\\\"">>),
    io:format("#: ~s:~p~nmsgid \"~s\"~nmsgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).

print_translation_obsolete(Str, StrT) ->
    File = "unknown.erl",
    Line = 1,
    StrQ = ejabberd_regexp:greplace(Str, "\\\"", "\\\\\""),
    StrTQ = ejabberd_regexp:greplace(StrT, "\\\"", "\\\\\""),
    io:format("#: ~s:~p~n#~~ msgid \"~s\"~n#~~ msgstr \"~s\"~n~n", [File, Line, StrQ, StrTQ]).

