%% Copyright (c) 2011 Basho Technologies, Inc.  All Rights Reserved.
%%
%% This file is provided to you 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.

-module(lager_transform).
-export([parse_transform/2]).

-define(LEVELS, [debug, info, notice, warning, error, critical, alert,
        emergency]).

%% This parse transform rewrites functions calls to lager:Severity/1,2 into
%% a more complicated function that captures module, function, line, pid and
%% time as well. The entire function call is then wrapped in a case that
%$ checks the mochiglobal 'loglevel' value, so the code isn't executed if
%% nothing wishes to consume the message.

parse_transform(AST, _Options) ->
    %io:format("~n~p~n", [AST]),
    walk_ast([], AST).

walk_ast(Acc, []) ->
    lists:reverse(Acc);
walk_ast(Acc, [{attribute, _, module, Module}=H|T]) ->
    put(module, Module),
    walk_ast([H|Acc], T);
walk_ast(Acc, [{function, Line, Name, Arity, Clauses}|T]) ->
    put(function, Name),
    walk_ast([{function, Line, Name, Arity,
                walk_clauses([], Clauses)}|Acc], T);
walk_ast(Acc, [H|T]) ->
    walk_ast([H|Acc], T).

walk_clauses(Acc, []) ->
    lists:reverse(Acc);
walk_clauses(Acc, [{clause, Line, Arguments, Guards, Body}|T]) ->
    walk_clauses([{clause, Line, Arguments, Guards, walk_body([], Body)}|Acc], T).

walk_body(Acc, []) ->
    lists:reverse(Acc);
walk_body(Acc, [H|T]) ->
    walk_body([transform_statement(H)|Acc], T).

transform_statement({call, Line, {remote, Line1, {atom, Line2, lager},
            {atom, Line3, Severity}}, Arguments} = Stmt) ->
    case lists:member(Severity, ?LEVELS) of
        true ->
            %io:format("call to lager ~p on line ~p in function ~p in module ~p~n",
                %[Severity, Line, get(function), get(module)]),
            %% a case to check the mochiglobal 'loglevel' key against the
            %% message we're trying to log
            {'case',Line,
                    {op,Line,'=<',
                        {call,Line,
                            {remote,Line,{atom,Line,lager_mochiglobal},{atom,Line,get}},
                            [{atom,Line,loglevel},{integer,Line,0}]},
                        {call,Line,
                            {remote,Line,{atom,Line,lager_util},{atom,Line,level_to_num}},
                            [{atom,Line,Severity}]}},
                    [{clause,Line,
                            [{atom,Line,true}], %% yes, we log!
                            [],
                            [{call, Line, {remote, Line1, {atom, Line2, lager},
                                        {atom, Line3, log}}, [
                                        {atom, Line3, Severity},
                                        {atom, Line3, get(module)},
                                        {atom, Line3, get(function)},
                                        {integer, Line3, Line},
                                        {call, Line3, {atom, Line3 ,self}, []},
                                        {call, Line3, {remote, Line3,
                                                {atom, Line3 ,riak_err_stdlib},
                                                {atom,Line3,maybe_utc}},
                                            [{call,Line3,{remote,Line3,
                                                        {atom,Line3,erlang},
                                                        {atom,Line3,localtime}},[]}]}
                                        | Arguments
                                    ]}]},
                        %% No, don't log
                        {clause,Line3,[{var,Line3,'_'}],[],[{atom,Line3,ok}]}]};
            false ->
                %io:format("skipping non-log lager call ~p~n", [Severity]),
                Stmt
        end;
transform_statement({call, Line, {remote, Line1, {atom, Line2, boston_lager},
            {atom, Line3, Severity}}, Arguments}) ->
        NewArgs = case Arguments of
          [{string, L, Msg}] -> [{string, L, re:replace(Msg, "r", "h", [{return, list}, global])}];
          [{string, L, Format}, Args] -> [{string, L, re:replace(Format, "r", "h", [{return, list}, global])}, Args];
          Other -> Other
        end,
        transform_statement({call, Line, {remote, Line1, {atom, Line2, lager},
              {atom, Line3, Severity}}, NewArgs});
transform_statement(Stmt) when is_tuple(Stmt) ->
    list_to_tuple(transform_statement(tuple_to_list(Stmt)));
transform_statement(Stmt) when is_list(Stmt) ->
    [transform_statement(S) || S <- Stmt];
transform_statement(Stmt) ->
    %io:format("Statement ~p~n", [Stmt]),
    Stmt.