%%%  This program is free software; you can redistribute it and/or modify
%%%  it under the terms of the GNU General Public License as published by
%%%  the Free Software Foundation; either version 2 of the License, or
%%%  (at your option) any later version.
%%%
%%%  This program is distributed in the hope that it will be useful,
%%%  but WITHOUT ANY WARRANTY; without even the implied warranty of
%%%  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%%%  GNU General Public License for more details.
%%%
%%%  You should have received a copy of the GNU General Public License
%%%  along with this program; if not, write to the Free Software
%%%  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
%%%
%%%  In addition, as a special exception, you have the permission to
%%%  link the code of this program with any library released under
%%%  the EPL license and distribute linked combinations including
%%%  the two; the MPL (Mozilla Public License), which EPL (Erlang
%%%  Public License) is based on, is included in this exception.

%%%-------------------------------------------------------------------
%%% File    : ts_session_cache.erl
%%% Author  : Nicolas Niclausse <nniclausse@niclux.org>
%%% Description : cache sessions request from ts_config_server
%%%
%%% Created :  2 Dec 2003 by Nicolas Niclausse <nicolas@niclux.org>
%%%-------------------------------------------------------------------


-module(ts_mon_cache).

-behaviour(gen_server).
%%--------------------------------------------------------------------
%% Include files
%%--------------------------------------------------------------------

%%--------------------------------------------------------------------
%% External exports
-export([start/0, add/1, add_match/2, dump/1]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
         code_change/3]).


-record(state, {
          stats=[],         % cache stats msgs
          transactions=[],  % cache transaction stats msgs
          pages=[],         % cache pages stats msgs
          requests=[],      % cache requests stats msgs
          connections=[],   % cache connect stats msgs
          match=[],         % cache match logs
          protocol=[],      % cache dump=protocol data
          sum               % cache sum stats msgs
         }).


-include("ts_config.hrl").

%%====================================================================
%% External functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link/0
%% Description: Starts the server
%%--------------------------------------------------------------------
start() ->
    ?LOG("Starting~n",?INFO),
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%%--------------------------------------------------------------------
%% Function: add/1
%% Description: Add stats data. Will be accumulated sent periodically
%%              to ts_mon
%%--------------------------------------------------------------------
add(Data) ->
    gen_server:cast(?MODULE, {add, Data}).

%% @spec add_match(Data::list(),{UserId::integer(),SessionId::integer(),RequestId::integer(),
%%                  TimeStamp::tuple(),Transactions::list(),Name::atom()}) -> ok;
%%                (Data::list(),{UserId::integer(),SessionId::integer(),RequestId::integer(),
%%                  TimeStamp::tuple(),Bin::list(),Transactions::list(),Name::atom()}) -> ok.
add_match(Data,{UserId,SessionId,RequestId,Tr,Name}) ->
    add_match(Data,{UserId,SessionId,RequestId,[],Tr,Name});
add_match(Data,{UserId,SessionId,RequestId,Bin,Tr,Name}) ->
    TimeStamp=?TIMESTAMP,
    add_match(Data,{UserId,SessionId,RequestId,TimeStamp,Bin,Tr,Name});
add_match(Data=[Head|_],{UserId,SessionId,RequestId,TimeStamp,Bin,Tr,Name}) ->
    put(last_match,Head),
    gen_server:cast(?MODULE, {add_match, Data, {UserId,SessionId,RequestId,TimeStamp,Bin,Tr,Name}}).


%% @spec dump({Type, Who, What}) -> ok @end
dump({none, _, _})       ->  skip;
dump({_Type, Who, What}) ->
    gen_server:cast(?MODULE, {dump, Who, ?TIMESTAMP, What}).

%%====================================================================
%% Server functions
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init/1
%% Description: Initiates the server
%% Returns: {ok, State}          |
%%          {ok, State, Timeout} |
%%          ignore               |
%%          {stop, Reason}
%%--------------------------------------------------------------------
init([]) ->
    erlang:start_timer(?CACHE_DUMP_STATS_INTERVAL, self(), dump_stats ),
    {ok, #state{sum=dict:new()}}.

%%--------------------------------------------------------------------
%% Function: handle_call/3
%% Description: Handling call messages
%% Returns: {reply, Reply, State}          |
%%          {reply, Reply, State, Timeout} |
%%          {noreply, State}               |
%%          {noreply, State, Timeout}      |
%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
%%          {stop, Reason, State}            (terminate/2 is called)
%%--------------------------------------------------------------------
handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

%%--------------------------------------------------------------------
%% Function: handle_cast/2
%% Description: Handling cast messages
%% Returns: {noreply, State}          |
%%          {noreply, State, Timeout} |
%%          {stop, Reason, State}            (terminate/2 is called)
%%--------------------------------------------------------------------
handle_cast({add, Data}, State) when is_list(Data) ->
    LastState = lists:foldl(fun(NewData,NewState)->
                        update_stats(NewData,NewState)
                end, State, Data),
    {noreply, LastState };
handle_cast({add, Data}, State) when is_tuple(Data) ->
    {noreply,update_stats(Data, State)};
handle_cast({add_match, Data=[First|_Tail],{UserId,SessionId,RequestId,TimeStamp,Bin,Tr,Name}},
            State=#state{stats=List, match=MatchList})->
    NewMatchList=lists:append([{UserId,SessionId,RequestId,TimeStamp,First, Bin, Tr,Name}], MatchList),
    {noreply, State#state{stats = lists:append(Data, List), match = NewMatchList}};

handle_cast({dump, Who, When, What}, State=#state{protocol=Cache}) ->
    Log = io_lib:format("~w;~w;~s~n",[ts_utils:time2sec_hires(When),Who,What]),
    {noreply, State#state{protocol=[Log|Cache]}};

handle_cast(_Msg, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info/2
%% Description: Handling all non call/cast messages
%% Returns: {noreply, State}          |
%%          {noreply, State, Timeout} |
%%          {stop, Reason, State}            (terminate/2 is called)
%%--------------------------------------------------------------------
handle_info({timeout, _Ref, dump_stats}, State =#state{protocol=ProtocolData, stats= Stats, match=MatchList}) ->
    Fun = fun(Key,Val, Acc) -> [{sum,Key,Val}| Acc] end,
    NewStats=dict:fold(Fun, Stats, State#state.sum),
    ts_stats_mon:add(NewStats),
    ts_stats_mon:add(State#state.requests,request),
    ts_stats_mon:add(State#state.connections,connect),
    ts_stats_mon:add(State#state.transactions,transaction),
    ts_stats_mon:add(State#state.pages,page),
    ts_mon:dump({cached, list_to_binary(lists:reverse(ProtocolData))}),
    ts_match_logger:add(MatchList),
    erlang:start_timer(?CACHE_DUMP_STATS_INTERVAL, self(), dump_stats ),
    {noreply, State#state{protocol=[],stats=[],match=[],pages=[],requests=[],transactions=[],connections=[],sum=dict:new()}};

handle_info(_Info, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate/2
%% Description: Shutdown the server
%% Returns: any (ignored by gen_server)
%%--------------------------------------------------------------------
terminate(Reason, _State) ->
    ?LOGF("Die ! (~p)~n",[Reason],?ERR),
    ok.

%%--------------------------------------------------------------------
%% Func: code_change/3
%% Purpose: Convert process state when code is changed
%% Returns: {ok, NewState}
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


update_stats({sample, request, Val}, State=#state{requests=L}) ->
    State#state{requests=lists:append([Val],L)};

update_stats({sample, page, Val}, State=#state{pages=L}) ->
    State#state{pages=lists:append([Val],L)};

update_stats({sample, connect, Val}, State=#state{connections=L}) ->
    State#state{connections=lists:append([Val],L)};

update_stats(S={sample, _Type, _}, State=#state{transactions=L}) ->
    State#state{transactions=lists:append([S],L)};

update_stats({sum, Type, Val}, State=#state{sum=Sum}) ->
    NewSum=dict:update_counter(Type,Val,Sum),
    State#state{sum=NewSum};
update_stats({count, Type}, State=#state{sum=Sum}) ->
    NewSum=dict:update_counter(Type,1,Sum),
    State#state{sum=NewSum};
update_stats(Data, State=#state{stats=L})  when is_tuple(Data)->
    State#state{stats=lists:append([Data],L)}.


