%%%
%%% Copyright 2011, Boundary
%%% Copyright 2011, Opscode
%%%
%%% 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.
%%%


%%%-------------------------------------------------------------------
%%% File:      cloudi_x_folsom_metrics_meter_reader.erl
%%% @author    Seth Falcon <seth@opscode.com>
%%% @author    joe williams <j@boundary.com>
%%% @doc
%%% @end
%%%------------------------------------------------------------------

-module(cloudi_x_folsom_metrics_meter_reader).

-behaviour(gen_server).

-export([new/1,
         tick/1,
         mark/1,
         mark/2,
         get_values/1,
         get_acceleration/1
        ]).

-export([start_link/0]).

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

-define(SERVER, ?MODULE).

-record(state, {}).

-record(meter_reader, {
          instant,
          one,
          five,
          fifteen,
          count = 0,
          start_time,
          last_count = unset
         }).

-include("cloudi_x_folsom.hrl").

new(Name) ->
    gen_server:call(?SERVER, {new, Name}).

tick(Name) ->
    #meter_reader{instant = Instant,
                  one = OneMin,
                  five = FiveMin,
                  fifteen = FifteenMin} = Meter = get_value(Name),

    Instant1 = cloudi_x_folsom_ewma:tick(Instant),
    OneMin1 = cloudi_x_folsom_ewma:tick(OneMin),
    FiveMin1 = cloudi_x_folsom_ewma:tick(FiveMin),
    FifteenMin1 = cloudi_x_folsom_ewma:tick(FifteenMin),

    ets:insert(?METER_READER_TABLE,
               {Name, Meter#meter_reader{instant = Instant1,
                                         one = OneMin1,
                                         five = FiveMin1,
                                         fifteen = FifteenMin1}}).

mark(Name) ->
    mark(Name, 1).

mark(Name, Value) ->
    % skip first reading to bootstrap last value
    #meter_reader{count = Count,
                  last_count = LastCount,
                  instant = Instant,
                  one = OneMin,
                  five = FiveMin,
                  fifteen = FifteenMin} = Meter = get_value(Name),

    NewMeter = case LastCount of
                   unset ->
                       Meter#meter_reader{last_count = Value};
                   _ ->
                       Delta = Value - LastCount,
                       Instant1 = cloudi_x_folsom_ewma:update(Instant, Delta),
                       OneMin1 = cloudi_x_folsom_ewma:update(OneMin, Delta),
                       FiveMin1 = cloudi_x_folsom_ewma:update(FiveMin, Delta),
                       FifteenMin1 = cloudi_x_folsom_ewma:update(FifteenMin, Delta),
                       Meter#meter_reader{count = Count + Delta,
                                          last_count = Value,
                                          instant = Instant1,
                                          one = OneMin1,
                                          five = FiveMin1,
                                          fifteen = FifteenMin1}
               end,

    ets:insert(?METER_READER_TABLE, {Name, NewMeter}).

get_values(Name) ->
    #meter_reader{instant = Instant,
                  one = OneMin,
                  five = FiveMin,
                  fifteen = FifteenMin} = Meter = get_value(Name),

    L = [{instant, get_rate(Instant)},
         {one, get_rate(OneMin)},
         {five, get_rate(FiveMin)},
         {fifteen, get_rate(FifteenMin)},
         {mean, get_mean_rate(Meter)},
         {acceleration, get_acceleration(Name)}
        ],

    [ {K,V} || {K,V} <- L, V /= undefined ].

get_acceleration(Name) ->
    #meter_reader{instant = Instant,
                  one = OneMin,
                  five = FiveMin,
                  fifteen = FifteenMin} = get_value(Name),

    [{instant_to_one, calc_acceleration(get_rate(Instant), get_rate(OneMin), 60)},
     {one_to_five, calc_acceleration(get_rate(OneMin), get_rate(FiveMin), 300)},
     {five_to_fifteen, calc_acceleration(get_rate(FiveMin), get_rate(FifteenMin), 600)},
     {one_to_fifteen, calc_acceleration(get_rate(OneMin), get_rate(FifteenMin), 900)}
    ].

% internal functions

get_rate(EWMA) ->
    cloudi_x_folsom_ewma:rate(EWMA).

get_mean_rate(#meter_reader{count = Count, start_time = Start}) ->
    calc_mean_rate(Start, Count).

get_value(Name) ->
    [{_, Value}] = ets:lookup(?METER_READER_TABLE, Name),
    Value.

calc_mean_rate(_, 0) ->
    0.0;
calc_mean_rate(Start, Count) ->
    case cloudi_x_folsom_utils:now_epoch_micro() - Start of
        0 -> 0.0;
        Elapsed -> Count / Elapsed
    end.

calc_acceleration(Rate1, Rate2, Interval) ->
    % most current velocity minus previous velocity
    get_rate(Rate1, Rate2, Interval).

get_rate(Value1, Value2, Interval) ->
    Delta = Value1 - Value2,
    Delta / Interval.

start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

init([]) ->
    process_flag(trap_exit, true),
    {ok, #state{}}.

handle_call({new, Name}, _From, State) ->
    Reply = case ets:member(?METER_READER_TABLE, Name) of
                false ->
                    ok = cloudi_x_folsom_meter_timer_server:register(Name, cloudi_x_folsom_metrics_meter_reader),
                    Instant = cloudi_x_folsom_ewma:instant_ewma(),
                    OneMin = cloudi_x_folsom_ewma:one_minute_ewma(),
                    FiveMin = cloudi_x_folsom_ewma:five_minute_ewma(),
                    FifteenMin = cloudi_x_folsom_ewma:fifteen_minute_ewma(),

                    ets:insert(?METER_READER_TABLE,
                               {Name, #meter_reader{instant = Instant,
                                                    one = OneMin,
                                                    five = FiveMin,
                                                    fifteen = FifteenMin,
                                                    start_time = cloudi_x_folsom_utils:now_epoch_micro()}}),
                    true = ets:insert(?FOLSOM_TABLE, {Name, #metric{type = meter_reader}});
                true ->
                    true
            end,
    {reply, Reply, State}.

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

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

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

format_status(_Opt, Status) ->
    Status.
