%%%----------------------------------------------------------------------
%%% File    : mod_statsdx.erl
%%% Author  : Badlop <badlop@ono.com>
%%% Purpose : Calculates and gathers statistics actively
%%% Created :
%%% Id      : $Id: mod_statsdx.erl 1118 2011-07-11 17:16:30Z badlop $
%%%----------------------------------------------------------------------

%%%% Definitions

-module(mod_statsdx).
-author('badlop@ono.com').

-behaviour(gen_mod).

-export([start/2, loop/1, stop/1, mod_opt_type/1, get_statistic/2,
	 received_response/3,
	 %% Commands
	 getstatsdx/1, getstatsdx/2,
	 get_top_users/2,
	 %% WebAdmin
	 web_menu_main/2, web_page_main/2,
	 web_menu_node/3, web_page_node/5,
	 web_menu_host/3, web_page_host/3,
	 %% Hooks
	 register_user/2, remove_user/2, user_send_packet/1,
         user_send_packet_traffic/1, user_receive_packet_traffic/1,
	 %%user_logout_sm/3,
	 user_login/1, user_logout/4]).

-include("ejabberd_commands.hrl").
-include("xmpp.hrl").
-include("logger.hrl").
-include("mod_roster.hrl").
-include("ejabberd_http.hrl").
-include("ejabberd_web_admin.hrl").

-define(XCTB(Name, Text), ?XCT(list_to_binary(Name), list_to_binary(Text))).

-define(PROCNAME, ejabberd_mod_statsdx).

%% Copied from ejabberd_s2s.erl Used in function get_s2sconnections/1
-record(s2s, {fromto, pid, key}).

%%%==================================
%%%% Module control

start(Host, Opts) ->
    Hooks = gen_mod:get_opt(hooks, Opts,
			    fun(O) when is_boolean(O) -> O;
			       (traffic) -> traffic
			    end, false),
    %% Default value for the counters
    CD = case Hooks of
	     true -> 0;
	     traffic -> 0;
	     false -> "disabled"
	 end,

    ejabberd_commands:register_commands(commands()),

    %% If the process that handles statistics for the server is not started yet,
    %% start it now
    case whereis(?PROCNAME) of
	undefined ->
	    application:start(os_mon),
	    initialize_stats_server();
	_ ->
	    ok
    end,
    ?PROCNAME ! {initialize_stats, Host, Hooks, CD},
    ok.

stop(Host) ->
    finish_stats(Host),
    ejabberd_commands:unregister_commands(commands()),
    case whereis(?PROCNAME) of
	undefined -> ok;
	_ -> ?PROCNAME ! {stop, Host}
    end.

mod_opt_type(hooks) -> fun (B) when is_boolean(B) or (B==traffic) -> B end;
mod_opt_type(_) -> [hooks].

%%%==================================
%%%% Stats Server

%%% +++ TODO: why server and "server"
table_name(server) -> gen_mod:get_module_proc(<<"server">>, mod_statsdx);
table_name("server") -> gen_mod:get_module_proc(<<"server">>, mod_statsdx);
table_name(Host) -> gen_mod:get_module_proc(tob(Host), mod_statsdx).

tob(A) when is_atom(A) -> A;
tob(B) when is_binary(B) -> B;
tob(L) when is_list(L) -> list_to_binary(L).

initialize_stats_server() ->
    register(?PROCNAME, spawn(?MODULE, loop, [[]])).

loop(Hosts) ->
    receive
	{initialize_stats, Host, Hooks, CD} ->
	    case Hosts of
		[] -> prepare_stats_server(CD);
		_ -> ok
	    end,
	    prepare_stats_host(Host, Hooks, CD),
	    loop([Host | Hosts]);
	{stop, Host} ->
	    case Hosts -- [Host] of
		[] ->
                    finish_stats();
		RemainingHosts ->
		    loop(RemainingHosts)
	    end
    end.

%% Si no existe una tabla de stats del server, crearla.
%% Deberia ser creada por un proceso que solo muera cuando se detenga el ultimo mod_statsdx del servidor
prepare_stats_server(CD) ->
    Table = table_name(server),
    ets:new(Table, [named_table, public]),
    ets:insert(Table, {{user_login, server}, CD}),
    ets:insert(Table, {{user_logout, server}, CD}),
    ets:insert(Table, {{register_user, server}, CD}),
    ets:insert(Table, {{remove_user, server}, CD}),
    lists:foreach(
      fun(E) -> ets:insert(Table, {{client, server, E}, CD}) end,
      list_elem(clients, id)
     ),
    lists:foreach(
      fun(E) -> ets:insert(Table, {{conntype, server, E}, CD}) end,
      list_elem(conntypes, id)
     ),
    lists:foreach(
      fun(E) -> ets:insert(Table, {{os, server, E}, CD}) end,
      list_elem(oss, id)
     ),
    ejabberd_hooks:add(webadmin_menu_main, ?MODULE, web_menu_main, 50),
    ejabberd_hooks:add(webadmin_menu_node, ?MODULE, web_menu_node, 50),
    ejabberd_hooks:add(webadmin_page_main, ?MODULE, web_page_main, 50),
    ejabberd_hooks:add(webadmin_page_node, ?MODULE, web_page_node, 50).

prepare_stats_host(Host, Hooks, CD) ->
    Table = table_name(Host),
    ets:new(Table, [named_table, public]),
    ets:insert(Table, {{user_login, Host}, CD}),
    ets:insert(Table, {{user_logout, Host}, CD}),
    ets:insert(Table, {{register_user, Host}, CD}),
    ets:insert(Table, {{remove_user, Host}, CD}),
    ets:insert(Table, {{send, Host, iq, in}, CD}),
    ets:insert(Table, {{send, Host, iq, out}, CD}),
    ets:insert(Table, {{send, Host, message, in}, CD}),
    ets:insert(Table, {{send, Host, message, out}, CD}),
    ets:insert(Table, {{send, Host, presence, in}, CD}),
    ets:insert(Table, {{send, Host, presence, out}, CD}),
    ets:insert(Table, {{recv, Host, iq, in}, CD}),
    ets:insert(Table, {{recv, Host, iq, out}, CD}),
    ets:insert(Table, {{recv, Host, message, in}, CD}),
    ets:insert(Table, {{recv, Host, message, out}, CD}),
    ets:insert(Table, {{recv, Host, presence, in}, CD}),
    ets:insert(Table, {{recv, Host, presence, out}, CD}),
    lists:foreach(
      fun(E) -> ets:insert(Table, {{client, Host, E}, CD}) end,
      list_elem(clients, id)
     ),
    lists:foreach(
      fun(E) -> ets:insert(Table, {{conntype, Host, E}, CD}) end,
      list_elem(conntypes, id)
     ),
    lists:foreach(
      fun(E) -> ets:insert(Table, {{os, Host, E}, CD}) end,
      list_elem(oss, id)
     ),
    case Hooks of
	true ->
	    ejabberd_hooks:add(register_user, Host, ?MODULE, register_user, 90),
	    ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 90),
	    ejabberd_hooks:add(c2s_session_opened, Host, ?MODULE, user_login, 90),
	    ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE, user_logout, 90),
	    %%ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, user_logout_sm, 90),
	    ejabberd_hooks:add(user_send_packet, Host, ?MODULE, user_send_packet, 90);
	traffic ->
	    ejabberd_hooks:add(user_receive_packet, Host, ?MODULE, user_receive_packet_traffic, 92),
	    ejabberd_hooks:add(user_send_packet, Host, ?MODULE, user_send_packet_traffic, 92),
	    ejabberd_hooks:add(register_user, Host, ?MODULE, register_user, 90),
	    ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 90),
	    ejabberd_hooks:add(c2s_session_opened, Host, ?MODULE, user_login, 90),
	    ejabberd_hooks:add(unset_presence_hook, Host, ?MODULE, user_logout, 90),
	    %%ejabberd_hooks:add(sm_remove_connection_hook, Host, ?MODULE, user_logout_sm, 90),
	    ejabberd_hooks:add(user_send_packet, Host, ?MODULE, user_send_packet, 90);
	false ->
	    ok
    end,
    ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
    ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, web_page_host, 50).

finish_stats() ->
    ejabberd_hooks:delete(webadmin_menu_main, ?MODULE, web_menu_main, 50),
    ejabberd_hooks:delete(webadmin_menu_node, ?MODULE, web_menu_node, 50),
    ejabberd_hooks:delete(webadmin_page_main, ?MODULE, web_page_main, 50),
    ejabberd_hooks:delete(webadmin_page_node, ?MODULE, web_page_node, 50),
    Table = table_name(server),
    catch ets:delete(Table).

finish_stats(Host) ->
    ejabberd_hooks:delete(c2s_session_opened, Host, ?MODULE, user_login, 90),
    ejabberd_hooks:delete(unset_presence_hook, Host, ?MODULE, user_logout, 90),
    %%ejabberd_hooks:delete(sm_remove_connection_hook, Host, ?MODULE, user_logout_sm, 90),
    ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, user_send_packet, 90),
    ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, user_send_packet_traffic, 92),
    ejabberd_hooks:delete(user_receive_packet, Host, ?MODULE, user_receive_packet_traffic, 92),
    ejabberd_hooks:delete(register_user, Host, ?MODULE, register_user, 90),
    ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 90),
    ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
    ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
    Table = table_name(Host),
    catch ets:delete(Table).


%%%==================================
%%%% Hooks Handlers

register_user(_User, Host) ->
    TableHost = table_name(Host),
    TableServer = table_name(server),
    ets:update_counter(TableHost, {register_user, Host}, 1),
    ets:update_counter(TableServer, {register_user, server}, 1).

remove_user(_User, Host) ->
    TableHost = table_name(Host),
    TableServer = table_name(server),
    ets:update_counter(TableHost, {remove_user, Host}, 1),
    ets:update_counter(TableServer, {remove_user, server}, 1).

user_send_packet({NewEl, C2SState}) ->
    FromJID = xmpp:get_from(NewEl),
    ToJID = xmpp:get_from(NewEl),
    %% Registrarse para tramitar Host/mod_stats2file
    case catch binary_to_existing_atom(ToJID#jid.lresource, utf8) of
	?MODULE -> received_response(FromJID, ToJID, NewEl);
	_ -> ok
    end,
    {NewEl, C2SState}.

%% Only required for traffic stats
user_send_packet_traffic({NewEl, _C2SState} = Acc) ->
    From = xmpp:get_from(NewEl),
    To = xmpp:get_to(NewEl),
    Host = From#jid.lserver,
    HostTo = To#jid.lserver,
    Type2 = case NewEl of
		#iq{} -> iq;
		#message{} -> message;
		#presence{} -> presence
	    end,
    Dest = case is_host(HostTo, Host) of
    	       true -> in;
    	       false -> out
    	   end,
    Table = table_name(Host),
    ets:update_counter(Table, {send, tob(Host), Type2, Dest}, 1),
    Acc.

%% Only required for traffic stats
user_receive_packet_traffic({NewEl, _C2SState} = Acc) ->
    From = xmpp:get_from(NewEl),
    To = xmpp:get_to(NewEl),
    HostFrom = From#jid.lserver,
    Host = To#jid.lserver,
    Type2 = case NewEl of
		#iq{} -> iq;
		#message{} -> message;
		#presence{} -> presence
	    end,
    Dest = case is_host(HostFrom, Host) of
	       true -> in;
	       false -> out
	   end,
    Table = table_name(Host),
    ets:update_counter(Table, {recv, tob(Host), Type2, Dest}, 1),
    Acc.


%%%==================================
%%%% get(*

%%gett(Arg) -> get(node(), [Arg, title]).
getl(Args) -> get(node(), [Args]).
getl(Args, Host) -> get(node(), [Args, Host]).

%%get(_Node, ["", title]) -> "";

get_statistic(N, A) ->
    case catch get(N, A) of
	{'EXIT', R} ->
	    ?ERROR_MSG("get_statistic error for N: ~p, A: ~p~n~p", [N, A, R]),
	    unknown;
	Res -> Res
    end.

get(global, A) -> get(node(), A);

get(_, [{"reductions", _}, title]) -> "Reductions (per minute)";
get(_, [{"reductions", I}]) -> calc_avg(element(2, statistics(reductions)), I); %+++

get(_, ["cpu_avg1", title]) -> "Average system load (1 min)";
get(N, ["cpu_avg1"]) -> rpc:call(N, cpu_sup, avg1, [])/256;
get(_, ["cpu_avg5", title]) -> "Average system load (5 min)";
get(N, ["cpu_avg5"]) -> rpc:call(N, cpu_sup, avg1, [])/256;
get(_, ["cpu_avg15", title]) -> "Average system load (15 min)";
get(N, ["cpu_avg15"]) -> rpc:call(N, cpu_sup, avg15, [])/256;
get(_, ["cpu_nprocs", title]) -> "Number of UNIX processes running on this machine";
get(N, ["cpu_nprocs"]) -> rpc:call(N, cpu_sup, nprocs, []);
get(_, ["cpu_util", title]) -> "CPU utilization";
get(N, ["cpu_util"]) -> rpc:call(N, cpu_sup, util, []);

get(_, [{"cpu_util_user", _}, title]) -> "CPU utilization - user";
get(_, [{"cpu_util_nice_user", _}, title]) -> "CPU utilization - nice_user";
get(_, [{"cpu_util_kernel", _}, title]) -> "CPU utilization - kernel";
get(_, [{"cpu_util_wait", _}, title]) -> "CPU utilization - wait";
get(_, [{"cpu_util_idle", _}, title]) -> "CPU utilization - idle";
get(_, [{"cpu_util_user", U}]) -> U;
get(_, [{"cpu_util_nice_user", U}]) -> U;
get(_, [{"cpu_util_kernel", U}]) -> U;
get(_, [{"cpu_util_wait", U}]) -> U;
get(_, [{"cpu_util_idle", U}]) -> U;

get(_, [{"client", Id}, title]) -> atom_to_list(Id);
get(_, [{"client", Id}, Host]) ->
    Table = table_name(Host),
    case ets:lookup(Table, {client, tob(Host), Id}) of
	[{_, C}] -> C;
	[] -> 0
    end;
get(_, ["client", title]) -> "Client";
get(N, ["client", Host]) ->
    lists:map(
      fun(Id) ->
	      [Id_string] = io_lib:format("~p", [Id]),
	      {Id_string, get(N, [{"client", Id}, Host])}
      end,
      lists:usort(list_elem(clients, id))
     );

get(_, [{"os", Id}, title]) -> atom_to_list(Id);
get(_, [{"os", _Id}, list]) -> lists:usort(list_elem(oss, id));
get(_, [{"os", Id}, Host]) -> [{_, C}] = ets:lookup(table_name(Host), {os, tob(Host), Id}), C;
get(_, ["os", title]) -> "Operating System";
get(N, ["os", Host]) ->
    lists:map(
      fun(Id) ->
	      [Id_string] = io_lib:format("~p", [Id]),
	      {Id_string, get(N, [{"os", Id}, Host])}
      end,
      lists:usort(list_elem(oss, id))
     );

get(_, [{"conntype", Id}, title]) -> atom_to_list(Id);
get(_, [{"conntype", _Id}, list]) -> lists:usort(list_elem(conntypes, id));
get(_, [{"conntype", Id}, Host]) -> [{_, C}] = ets:lookup(table_name(Host), {conntype, Host, Id}), C;
get(_, ["conntype", title]) -> "Connection Type";
get(N, ["conntype", Host]) ->
    lists:map(
      fun(Id) ->
	      [Id_string] = io_lib:format("~p", [Id]),
	      {Id_string, get(N, [{"conntype", Id}, Host])}
      end,
      lists:usort(list_elem(conntypes, id))
     );

get(_, [{"memsup_system", _}, title]) -> "Memory physical (bytes)";
get(_, [{"memsup_system", M}]) -> proplists:get_value(system_total_memory, M, -1);
get(_, [{"memsup_free", _}, title]) -> "Memory free (bytes)";
get(_, [{"memsup_free", M}]) -> proplists:get_value(free_memory, M, -1);

get(_, [{"user_login", _}, title]) -> "Logins (per minute)";
get(_, [{"user_login", I}, Host]) -> get_stat({user_login, tob(Host)}, I);
get(_, [{"user_logout", _}, title]) -> "Logouts (per minute)";
get(_, [{"user_logout", I}, Host]) -> get_stat({user_logout, tob(Host)}, I);
get(_, [{"register_user", _}, title]) -> "Accounts registered (per minute)";
get(_, [{"register_user", I}, Host]) -> get_stat({register_user, tob(Host)}, I);
get(_, [{"remove_user", _}, title]) -> "Accounts deleted (per minute)";
get(_, [{"remove_user", I}, Host]) -> get_stat({remove_user, tob(Host)}, I);
get(_, [{Table, Type, Dest, _}, title]) -> filename:flatten([Table, Type, Dest]);
get(_, [{Table, Type, Dest, I}, Host]) -> get_stat({Table, tob(Host), Type, Dest}, I);

get(_, ["user_login", title]) -> "Logins";
get(_, ["user_login", Host]) -> get_stat({user_login, Host});
get(_, ["user_logout", title]) -> "Logouts";
get(_, ["user_logout", Host]) -> get_stat({user_logout, Host});
get(_, ["register_user", title]) -> "Accounts registered";
get(_, ["register_user", Host]) -> get_stat({register_user, Host});
get(_, ["remove_user", title]) -> "Accounts deleted";
get(_, ["remove_user", Host]) -> get_stat({remove_user, Host});
get(_, [{Table, Type, Dest}, title]) -> filename:flatten([Table, Type, Dest]);
get(_, [{Table, Type, Dest}, Host]) -> get_stat({Table, tob(Host), Type, Dest});

get(_, ["localtime", title]) -> "Local time";
get(N, ["localtime"]) ->
    localtime_to_string(rpc:call(N, erlang, localtime, []));

get(_, ["memory_total", title]) -> "Memory total allocated: processes and system";
get(N, ["memory_total"]) -> rpc:call(N, erlang, memory, [total]);
get(_, ["memory_processes", title]) -> "Memory allocated by Erlang processes";
get(N, ["memory_processes"]) -> rpc:call(N, erlang, memory, [processes]);
get(_, ["memory_processes_used", title]) -> "Memory used by Erlang processes";
get(N, ["memory_processes_used"]) -> rpc:call(N, erlang, memory, [processes_used]);
get(_, ["memory_system", title]) -> "Memory allocated by Erlang emulator but not associated to processes";
get(N, ["memory_system"]) -> rpc:call(N, erlang, memory, [system]);
get(_, ["memory_atom", title]) -> "Memory allocated for atoms";
get(N, ["memory_atom"]) -> rpc:call(N, erlang, memory, [atom]);
get(_, ["memory_atom_used", title]) -> "Memory used for atoms";
get(N, ["memory_atom_used"]) -> rpc:call(N, erlang, memory, [atom_used]);
get(_, ["memory_binary", title]) -> "Memory allocated for binaries";
get(N, ["memory_binary"]) -> rpc:call(N, erlang, memory, [binary]);
get(_, ["memory_code", title]) -> "Memory allocated for Erlang code";
get(N, ["memory_code"]) -> rpc:call(N, erlang, memory, [code]);
get(_, ["memory_ets", title]) -> "Memory allocated for ETS tables";
get(N, ["memory_ets"]) -> rpc:call(N, erlang, memory, [ets]);

get(_, ["vhost", title]) -> "Virtual host";
get(_, ["vhost", Host]) -> Host;

get(_, ["ejabberdversion", title]) -> "ejabberd version";
get(N, ["ejabberdversion"]) -> element(2, rpc:call(N, application, get_key, [ejabberd, vsn]));

get(_, ["totalerlproc", title]) -> "Total Erlang processes running";
get(N, ["totalerlproc"]) -> rpc:call(N, erlang, system_info, [process_count]);
get(_, ["operatingsystem", title]) -> "Operating System";
get(N, ["operatingsystem"]) -> {rpc:call(N, os, type, []), rpc:call(N, os, version, [])};
get(_, ["erlangmachine", title]) -> "Erlang machine";
get(N, ["erlangmachine"]) -> rpc:call(N, erlang, system_info, [system_version]);
get(_, ["erlangmachinetarget", title]) -> "Erlang machine target";
get(N, ["erlangmachinetarget"]) -> rpc:call(N, erlang, system_info, [system_architecture]);
get(_, ["maxprocallowed", title]) -> "Maximum processes allowed";
get(N, ["maxprocallowed"]) -> rpc:call(N, erlang, system_info, [process_limit]);
get(_, ["procqueue", title]) -> "Number of processes on the running queue";
get(N, ["procqueue"]) -> rpc:call(N, erlang, statistics, [run_queue]);
get(_, ["uptimehuman", title]) -> "Uptime";
get(N, ["uptimehuman"]) ->
    io_lib:format("~w days ~w hours ~w minutes ~p seconds", ms_to_time(get(N, ["uptime"])));
get(_, ["lastrestart", title]) -> "Last restart";
get(N, ["lastrestart"]) ->
    Now = calendar:datetime_to_gregorian_seconds(rpc:call(N, erlang, localtime, [])),
    UptimeMS = get(N, ["uptime"]),
    Last_restartS = trunc(Now - (UptimeMS/1000)),
    Last_restart = calendar:gregorian_seconds_to_datetime(Last_restartS),
    localtime_to_string(Last_restart);

get(_, ["plainusers", title]) -> "Plain users";
get(_, ["plainusers"]) -> {R, _, _} = get_connectiontype(), R;
get(_, ["tlsusers", title]) -> "TLS users";
get(_, ["tlsusers"]) -> {_, R, _} = get_connectiontype(), R;
get(_, ["sslusers", title]) -> "SSL users";
get(_, ["sslusers"]) -> {_, _, R} = get_connectiontype(), R;
get(_, ["registeredusers", title]) -> "Registered users";
get(N, ["registeredusers"]) -> rpc:call(N, mnesia, table_info, [passwd, size]);
get(_, ["registeredusers", Host]) -> ejabberd_auth:count_users(Host);
get(_, ["onlineusers", title]) -> "Online users";
get(N, ["onlineusers"]) -> rpc:call(N, mnesia, table_info, [session, size]);
get(_, ["onlineusers", Host]) -> length(ejabberd_sm:get_vh_session_list(Host));
get(_, ["httppollusers", title]) -> "HTTP-Poll users (aprox)";
get(N, ["httppollusers"]) -> rpc:call(N, mnesia, table_info, [http_poll, size]);
get(_, ["httpbindusers", title]) -> "HTTP-Bind users (aprox)";
get(N, ["httpbindusers"]) -> rpc:call(N, mnesia, table_info, [http_bind, size]);

get(_, ["s2sconnectionsoutgoing", title]) -> "Outgoing S2S connections";
get(_, ["s2sconnectionsoutgoing"]) -> ejabberd_s2s:outgoing_s2s_number();
get(_, ["s2sconnectionsincoming", title]) -> "Incoming S2S connections";
get(_, ["s2sconnectionsincoming"]) -> ejabberd_s2s:incoming_s2s_number();
get(_, ["s2sconnections", title]) -> "S2S connections";
get(_, ["s2sconnections"]) -> length(get_S2SConns());
get(_, ["s2sconnections", Host]) -> get_s2sconnections(Host);
get(_, ["s2sservers", title]) -> "S2S servers";
get(_, ["s2sservers"]) -> length(lists:usort([element(2, C) || C <- get_S2SConns()]));

get(_, ["offlinemsg", title]) -> "Offline messages";
get(N, ["offlinemsg"]) -> rpc:call(N, mnesia, table_info, [offline_msg, size]);
get(_, ["offlinemsg", Host]) -> get_offlinemsg(Host);
get(_, ["totalrosteritems", title]) -> "Total roster items";
get(N, ["totalrosteritems"]) -> rpc:call(N, mnesia, table_info, [roster, size]);
get(_, ["totalrosteritems", Host]) -> get_totalrosteritems(Host);

get(_, ["meanitemsinroster", title]) -> "Mean items in roster";
get(_, ["meanitemsinroster"]) -> get_meanitemsinroster();
get(_, ["meanitemsinroster", Host]) -> get_meanitemsinroster(Host);

get(_, ["totalmucrooms", title]) -> "Total MUC rooms";
get(_, ["totalmucrooms"]) -> ets:info(muc_online_room, size);
get(_, ["totalmucrooms", Host]) -> get_totalmucrooms(Host);
get(_, ["permmucrooms", title]) -> "Permanent MUC rooms";
get(N, ["permmucrooms"]) -> rpc:call(N, mnesia, table_info, [muc_room, size]);
get(_, ["permmucrooms", Host]) -> get_permmucrooms(Host);
get(_, ["regmucrooms", title]) -> "Users registered at MUC service";
get(N, ["regmucrooms"]) -> rpc:call(N, mnesia, table_info, [muc_registered, size]);
get(_, ["regmucrooms", Host]) -> get_regmucrooms(Host);
get(_, ["regpubsubnodes", title]) -> "Registered nodes at Pub/Sub";
get(N, ["regpubsubnodes"]) -> rpc:call(N, mnesia, table_info, [pubsub_node, size]);
get(_, ["vcards", title]) -> "Total vCards published";
get(N, ["vcards"]) -> rpc:call(N, mnesia, table_info, [vcard, size]);
get(_, ["vcards", Host]) -> get_vcards(Host);

%%get(_, ["ircconns", title]) -> "IRC connections";
%%get(_, ["ircconns"]) -> ets:info(irc_connection, size);
%%get(_, ["ircconns", Host]) -> get_irccons(Host); % This seems to crash for some people
get(_, ["uptime", title]) -> "Uptime";
get(N, ["uptime"]) -> element(1, rpc:call(N, erlang, statistics, [wall_clock]));
get(_, ["cputime", title]) -> "CPU Time";
get(N, ["cputime"]) -> element(1, rpc:call(N, erlang, statistics, [runtime]));

get(_, ["languages", title]) -> "Languages";
get(_, ["languages", Server]) -> get_languages(Server);

get(_, ["client_os", title]) -> "Client/OS";
get(_, ["client_os", Server]) -> get_client_os(Server);

get(_, ["client_conntype", title]) -> "Client/Connection Type";
get(_, ["client_conntype", Server]) -> get_client_conntype(Server);

get(N, A) ->
    io:format(" ----- node: '~p', A: '~p'~n", [N, A]),
    "666".

%%%==================================
%%%% get_*

get_S2SConns() -> ejabberd_s2s:dirty_get_connections().

get_tls_drv() ->
    R = lists:filter(
	  fun(P) ->
		  case erlang:port_info(P, name) of
		      {name, "tls_drv"} -> true;
		      _ -> false
		  end
	  end, erlang:ports()),
    length(R).

get_connections(Port) ->
    R = lists:filter(
	  fun(P) ->
		  case inet:port(P) of
		      {ok, Port} -> true;
		      _ -> false
		  end
	  end, erlang:ports()),
    length(R).

get_totalrosteritems(Host) ->
    F = fun() ->
		F2 = fun(R, {H, A}) ->
			     {_LUser, LServer, _LJID} = R#roster.usj,
			     A2 = case LServer of
				      H -> A+1;
				      _ -> A
				  end,
			     {H, A2}
		     end,
		mnesia:foldl(F2, {Host, 0}, roster)
	end,
    {atomic, {Host, Res}} = mnesia:transaction(F),
    Res.

%% Copied from ejabberd_sm.erl
%%-record(session, {sid, usr, us, priority}).

%%get_authusers(Host) ->
%%    F = fun() ->
%%		F2 = fun(R, {H, A}) ->
%%			{_LUser, LServer, _LResource} = R#session.usr,
%%			A2 = case LServer of
%%				H -> A+1;
%%				_ -> A
%%			end,
%%			{H, A2}
%%		end,
%%		mnesia:foldl(F2, {Host, 0}, session)
%%	end,
%%    {atomic, {Host, Res}} = mnesia:transaction(F),
%%	Res.

-record(offline_msg, {us, timestamp, expire, from, to, packet}).

get_offlinemsg(Host) ->
    F = fun() ->
		F2 = fun(R, {H, A}) ->
			     {_LUser, LServer} = R#offline_msg.us,
			     A2 = case LServer of
				      H -> A+1;
				      _ -> A
				  end,
			     {H, A2}
		     end,
		mnesia:foldl(F2, {Host, 0}, offline_msg)
	end,
    {atomic, {Host, Res}} = mnesia:transaction(F),
    Res.

-record(vcard, {us, vcard}).

get_vcards(Host) ->
    F = fun() ->
		F2 = fun(R, {H, A}) ->
			     {_LUser, LServer} = R#vcard.us,
			     A2 = case LServer of
				      H -> A+1;
				      _ -> A
				  end,
			     {H, A2}
		     end,
		mnesia:foldl(F2, {Host, 0}, vcard)
	end,
    {atomic, {Host, Res}} = mnesia:transaction(F),
    Res.

get_s2sconnections(Host) ->
    F = fun() ->
		F2 = fun(R, {H, A}) ->
			     {MyServer, _Server} = R#s2s.fromto,
			     A2 = case MyServer of
				      H -> A+1;
				      _ -> A
				  end,
			     {H, A2}
		     end,
		mnesia:foldl(F2, {Host, 0}, s2s)
	end,
    {atomic, {Host, Res}} = mnesia:transaction(F),
    Res.

%%-record(irc_connection, {jid_server_host, pid}).

%%get_irccons(Host) ->
%%	F2 = fun(R, {H, A}) ->
%%		{From, _Server, _Host} = R#irc_connection.jid_server_host,
%%		A2 = case From#jid.lserver of
%%			H -> A+1;
%%			_ -> A
%%		end,
%%		{H, A2}
%%	end,
%%    {Host, Res} = ets:foldl(F2, {Host, 0}, irc_connection),
%%	Res.

is_host(HostBin, SubhostBin) ->
    case catch binary:split(HostBin, SubhostBin) of
	[_Sub, <<"">>] -> true;
	_ -> false
    end.

-record(muc_online_room, {name_host, pid}).

get_totalmucrooms(Host) ->
    F2 = fun(R, {H, A}) ->
		 {_Name, MUCHost} = R#muc_online_room.name_host,
		 A2 = case is_host(MUCHost, H) of
			  true -> A+1;
			  false -> A
		      end,
		 {H, A2}
	 end,
    {Host, Res} = ets:foldl(F2, {Host, 0}, muc_online_room),
    Res.

-record(muc_room, {name_host, opts}).

get_permmucrooms(Host) ->
    F = fun() ->
		F2 = fun(R, {H, A}) ->
			     {_Name, MUCHost} = R#muc_room.name_host,
			     A2 = case is_host(MUCHost, H) of
				      true -> A+1;
				      false -> A
				  end,
			     {H, A2}
		     end,
		mnesia:foldl(F2, {Host, 0}, muc_room)
	end,
    {atomic, {Host, Res}} = mnesia:transaction(F),
    Res.

-record(muc_registered, {us_host, nick}).

get_regmucrooms(Host) ->
    F = fun() ->
		F2 = fun(R, {H, A}) ->
			     {_User, MUCHost} = R#muc_registered.us_host,
			     A2 = case is_host(MUCHost, H) of
				      true -> A+1;
				      false -> A
				  end,
			     {H, A2}
		     end,
		mnesia:foldl(F2, {Host, 0}, muc_registered)
	end,
    {atomic, {Host, Res}} = mnesia:transaction(F),
    Res.

get_stat(Stat) ->
    Host = case Stat of
	       {_, H} -> H;
	       {_, H, _, _} -> H
	   end,
    Table = table_name(Host),
    Res = ets:lookup(Table, Stat),
    [{_, C}] = Res,
    C.

get_stat(Stat, Ims) ->
    Host = case Stat of
	       {_, H} -> H;
	       {_, H, _, _} -> H
	   end,
    Table = table_name(Host),
    Res = ets:lookup(Table, Stat),
    ets:update_counter(Table, Stat, {2,1,0,0}),
    [{_, C}] = Res,
    calc_avg(C, Ims).
%%C.

calc_avg(Count, TimeMS) ->
    TimeMIN = TimeMS/(1000*60),
    Count/TimeMIN.

%%%==================================
%%%% utilities

get_connectiontype() ->
    C2 = get_connections(5222) -1,
    C3 = get_connections(5223) -1,
    NUplain = C2 + C3 - get_tls_drv(),
    NUtls = C2 - NUplain,
    NUssl = C3,
    {NUplain, NUtls, NUssl}.

ms_to_time(T) ->
    DMS = 24*60*60*1000,
    HMS = 60*60*1000,
    MMS = 60*1000,
    SMS = 1000,
    D = trunc(T/DMS),
    H = trunc((T - (D*DMS)) / HMS),
    M = trunc((T - (D*DMS) - (H*HMS)) / MMS),
    S = trunc((T - (D*DMS) - (H*HMS) - (M*MMS)) / SMS),
    [D, H, M, S].


%% Cuando un usuario conecta, pedirle iq:version a nombre de Host/mod_stats2file
user_login(#{user := User, lserver := Host, resource := Resource} = State) ->
    ets:update_counter(table_name(server), {user_login, server}, 1),
    ets:update_counter(table_name(Host), {user_login, Host}, 1),
    request_iqversion(User, Host, Resource),
    State.


%%user_logout_sm(_, JID, _Data) ->
%%    user_logout(JID#jid.luser, JID#jid.lserver, JID#jid.lresource, no_status).

%% cuando un usuario desconecta, buscar en la tabla su JID/USR y quitarlo
user_logout(User, Host, Resource, _Status) ->
    TableHost = table_name(Host),
    TableServer = table_name(server),
    ets:update_counter(TableServer, {user_logout, server}, 1),
    ets:update_counter(TableHost, {user_logout, Host}, 1),

    JID = jlib:make_jid(User, Host, Resource),
    case ets:lookup(TableHost, {session, JID}) of
	[{_, Client_id, OS_id, Lang, ConnType, _Client, _Version, _OS}] ->
	    ets:delete(TableHost, {session, JID}),
	    ets:update_counter(TableHost, {client, Host, Client_id}, -1),
	    ets:update_counter(TableServer, {client, server, Client_id}, -1),
	    ets:update_counter(TableHost, {os, Host, OS_id}, -1),
	    ets:update_counter(TableServer, {os, server, OS_id}, -1),
	    ets:update_counter(TableHost, {conntype, Host, ConnType}, -1),
	    ets:update_counter(TableServer, {conntype, server, ConnType}, -1),
	    update_counter_create(TableHost, {client_os, Host, Client_id, OS_id}, -1),
	    update_counter_create(TableServer, {client_os, server, Client_id, OS_id}, -1),
	    update_counter_create(TableHost, {client_conntype, Host, Client_id, ConnType}, -1),
	    update_counter_create(TableServer, {client_conntype, server, Client_id, ConnType}, -1),
	    update_counter_create(TableHost, {lang, Host, Lang}, -1),
	    update_counter_create(TableServer, {lang, server, Lang}, -1);
	[] ->
	    ok
    end.

request_iqversion(User, Host, Resource) ->
    From = jid:make(<<"">>, Host, list_to_binary(atom_to_list(?MODULE))),
    To = jid:make(User, Host, Resource),
    Query = #xmlel{name = <<"query">>, attrs = [{<<"xmlns">>, ?NS_VERSION}]},
    IQ = #iq{type = get,
             from = From,
             to = To,
             id = randoms:get_string(),
             sub_els = [Query]},
    HandleResponse = fun(#iq{type = result} = IQr) ->
			       spawn(?MODULE, received_response,
				     [To, From, IQr]);
			  (R) ->
			       ?INFO_MSG("Unexpected response: ~n~p", [R]),
			       ok % Hmm.
		       end,
    ejabberd_router:route_iq(IQ, HandleResponse).

%% cuando el virtualJID recibe una respuesta iqversion,
%% almacenar su JID/USR + client + OS en una tabla
received_response(From, _To, El) ->
    try received_response(From, El)
    catch
    	_:_ -> ok
    end.
received_response(From, #iq{type = Type, lang = Lang1, sub_els = Elc}) ->
    User = From#jid.luser,
    Host = From#jid.lserver,
    Resource = From#jid.lresource,

    result = Type,
    Lang = case Lang1 of
	       <<"">> -> "unknown";
	       L -> binary_to_list(L)
	   end,
    TableHost = table_name(Host),
    TableServer = table_name(server),
    update_counter_create(TableHost, {lang, Host, Lang}, 1),
    update_counter_create(TableServer, {lang, server, Lang}, 1),

    [El] = fxml:remove_cdata(Elc),
    {xmlel, _, Attrs2, _Els2} = El,
    ?NS_VERSION = fxml:get_attr_s(<<"xmlns">>, Attrs2),

    Client = get_tag_cdata_subtag(El, <<"name">>),
    Version = get_tag_cdata_subtag(El, <<"version">>),
    OS = get_tag_cdata_subtag(El, <<"os">>),
    {Client_id, OS_id} = identify(Client, OS),

    ConnType = get_connection_type(User, Host, Resource),

    TableHost = table_name(Host),
    TableServer = table_name(server),
    ets:update_counter(TableHost, {client, Host, Client_id}, 1),
    ets:update_counter(TableServer, {client, server, Client_id}, 1),
    ets:update_counter(TableHost, {os, Host, OS_id}, 1),
    ets:update_counter(TableServer, {os, server, OS_id}, 1),
    ets:update_counter(TableHost, {conntype, Host, ConnType}, 1),
    ets:update_counter(TableServer, {conntype, server, ConnType}, 1),
    update_counter_create(TableHost, {client_os, Host, Client_id, OS_id}, 1),
    update_counter_create(TableServer, {client_os, server, Client_id, OS_id}, 1),
    update_counter_create(TableHost, {client_conntype, Host, Client_id, ConnType}, 1),
    update_counter_create(TableServer, {client_conntype, server, Client_id, ConnType}, 1),

    JID = jlib:make_jid(User, Host, Resource),
    ets:insert(TableHost, {{session, JID}, Client_id, OS_id, Lang, ConnType, Client, Version, OS}).

get_connection_type(User, Host, Resource) ->
    UserInfo = ejabberd_sm:get_user_info(User, Host, Resource),
    {conn, Conn} = lists:keyfind(conn, 1, UserInfo),
    Conn.

update_counter_create(Table, Element, C) ->
    case ets:lookup(Table, Element) of
	[] -> ets:insert(Table, {Element, 1});
	_ -> ets:update_counter(Table, Element, C)
    end.

get_tag_cdata_subtag(E, T) ->
    E2 = fxml:get_subtag(E, T),
    case E2 of
	false -> "unknown";
	_ -> binary_to_list(fxml:get_tag_cdata(E2))
    end.

list_elem(Type, id) ->
    {_, Ids} = lists:unzip(list_elem(Type, full)),
    Ids;
list_elem(clients, full) ->
    [
     {"Pidgin", pidgin},
     {"pidgin", pidgin},
     {"gaim", gaim},
     {"Gajim", gajim},
     {"Tkabber", tkabber},
     {"Psi", psi},
     {"Adium", adium},
     {"Pandion", pandion},
     {"Instantbird", instantbird},
     {"Telepathy Gabble", 'telepathy-gabble'},
     {"Swift", swift},
     {"Kopete", kopete},
     {"Exodus", exodus},
     {"libgaim", libgaim},
     {"JBother", jbother},
     {"iChat", ichat},
     {"imagent", messages},
     {"Miranda", miranda},
     {"Trillian", trillian},
     {"QIP Infium", qipinfium},
     {"JAJC", jajc},
     {"Coccinella", coccinella},
     {"Gabber", gabber},
     {"BitlBee", bitlbee},
     {"jabber.el", jabberel},
     {"irssi-xmpp", 'irssi-xmpp'},
     {"mcabber", mcabber},
     {"poezio", poezio},
     {"Profanity", profanity},
     {"centerim", centerim},
     {"Conversations", conversations},
     {"Monal", monal},
     {"AQQ", aqq},
     {"WTW", wtw},
     {"yaxim", yaxim},
     {"unknown", unknown}
    ];
list_elem(conntypes, full) ->
    [
     {"c2s", c2s},
     {"c2s_tls", c2s_tls},
     {"c2s_compressed", c2s_compressed},
     {"c2s_compressed_tls", c2s_compressed_tls},
     {"http_poll", http_poll},
     {"http_bind", http_bind},
     {"unknown", unknown}
    ];
list_elem(oss, full) ->
    [
     {"Linux", linux},
     {"Win", windows},
     {"Gentoo", linux},
     {"Mac", mac},
     {"BSD", bsd},
     {"SunOS", linux},
     {"Debian", linux},
     {"Ubuntu", linux},
     {"unknown", unknown}
    ].

identify(Client, OS) ->
    Res = {try_match(Client, list_elem(clients, full)), try_match(OS, list_elem(oss, full))},
    case Res of
	{libgaim, mac} -> {adium, mac};
	{adium, unknown} -> {adium, mac};
	{qipinfium, unknown} -> {qipinfium, windows};
	_ -> Res
    end.

try_match(_E, []) -> unknown;
try_match(E, [{Str, Id} | L]) ->
    case string:str(E, Str) of
	0 -> try_match(E, L);
	_ -> Id
    end.

get_client_os(Server) ->
    CO1 = ets:match(table_name(Server), {{client_os, Server, '$1', '$2'}, '$3'}),
    CO2 = lists:map(
	    fun([Cl, Os, A3]) ->
		    {lists:flatten([atom_to_list(Cl), "/", atom_to_list(Os)]), A3}
	    end,
	    CO1
	   ),
    lists:keysort(1, CO2).

get_client_conntype(Server) ->
    CO1 = ets:match(table_name(Server), {{client_conntype, Server, '$1', '$2'}, '$3'}),
    CO2 = lists:map(
	    fun([Cl, Os, A3]) ->
		    {lists:flatten([atom_to_list(Cl), "/", atom_to_list(Os)]), A3}
	    end,
	    CO1
	   ),
    lists:keysort(1, CO2).

get_languages(Server) ->
    L1 = ets:match(table_name(Server), {{lang, Server, '$1'}, '$2'}),
    L2 = lists:map(
	   fun([La, C]) ->
		   {La, C}
	   end,
	   L1
	  ),
    lists:keysort(1, L2).

get_meanitemsinroster() ->
    get_meanitemsinroster2(getl("totalrosteritems"), getl("registeredusers")).
get_meanitemsinroster(Host) ->
    get_meanitemsinroster2(getl("totalrosteritems", Host), getl("registeredusers", Host)).
get_meanitemsinroster2(Items, Users) ->
    case Users of
	0 -> 0;
	_ -> Items/Users
    end.

localtime_to_string({{Y, Mo, D},{H, Mi, S}}) ->
    lists:concat([H, ":", Mi, ":", S, " ", D, "/", Mo, "/", Y]).

%% cuando toque mostrar estadisticas
%%get_iqversion() ->
%% contar en la tabla cuantos tienen cliente: *psi*
%%buscar en la tabla iqversion
%%ok.


%%%==================================
%%%% Web Admin Menu

web_menu_main(Acc, Lang) ->
    Acc ++ [{<<"statsdx">>, <<(?T(<<"Statistics">>))/binary, " Dx">>}].

web_menu_node(Acc, _Node, Lang) ->
    Acc ++ [{<<"statsdx">>, <<(?T(<<"Statistics">>))/binary, " Dx">>}].

web_menu_host(Acc, _Host, Lang) ->
    Acc ++ [{<<"statsdx">>, <<(?T(<<"Statistics">>))/binary, " Dx">>}].

%%%==================================
%%%% Web Admin Page

web_page_main(_, #request{path=[<<"statsdx">>], lang = Lang} = _Request) ->
    Res = [?XC(<<"h1">>, <<(?T(<<"Statistics">>))/binary, " Dx">>),
	   ?XC(<<"h3">>, <<"Accounts">>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>, [
			       do_stat(global, Lang, "registeredusers")
			      ])
		]),
	   ?XC(<<"h3">>, <<"Roster">>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>, [
			       do_stat(global, Lang, "totalrosteritems"),
			       do_stat(global, Lang, "meanitemsinroster"),
			       ?XE(<<"tr">>,
				  [?XE(<<"td">>, [?CT(<<"Top rosters">>)]),
				   ?XE(<<"td">>, [
				    ?ACT(<<"top/roster/30">>, <<"30">>), ?C(<<", ">>),
				    ?ACT(<<"top/roster/100">>, <<"100">>), ?C(<<", ">>),
				    ?ACT(<<"top/roster/500">>, <<"500">>) ])]
				 )
			      ])
		]),
	   ?XC(<<"h3">>, <<"Users">>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>, [
			       do_stat(global, Lang, "onlineusers"),
			       do_stat(global, Lang, "offlinemsg"),
			       ?XE(<<"tr">>,
				  [?XE(<<"td">>, [?CT(<<"Top offline message queues">>) ]),
				   ?XE(<<"td">>, [
				    ?ACT(<<"top/offlinemsg/30">>, <<"30">>), ?C(<<", ">>),
				    ?ACT(<<"top/offlinemsg/100">>, <<"100">>), ?C(<<", ">>),
				    ?ACT(<<"top/offlinemsg/500">>, <<"500">>) ])]
				 ),
			       do_stat(global, Lang, "vcards"),
			       ?XE(<<"tr">>,
				  [?XE(<<"td">>, [?CT(<<"Top vCard sizes">>) ]),
				   ?XE(<<"td">>, [
				    ?ACT(<<"top/vcard/5">>, <<"5">>), ?C(<<", ">>),
				    ?ACT(<<"top/vcard/30">>, <<"30">>), ?C(<<", ">>),
				    ?ACT(<<"top/vcard/100">>, <<"100">>), ?C(<<", ">>),
				    ?ACT(<<"top/vcard/500">>, <<"500">>) ])]
				 )
			      ])
		]),
	   ?XC(<<"h3">>, <<"MUC">>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>, [
			       do_stat(global, Lang, "totalmucrooms"),
			       do_stat(global, Lang, "permmucrooms"),
			       do_stat(global, Lang, "regmucrooms")
			      ])
		]),
	   ?XC(<<"h3">>, <<"Pub/Sub">>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>, [
			       do_stat(global, Lang, "regpubsubnodes")
			      ])
		]),
	   %%?XC("h3", "IRC"),
	   %%?XAE("table", [],
	   %% [?XE("tbody", [
	   %%  do_stat(global, Lang, "ircconns")
	   %% ])
	   %%]),
	   %%?XC("h3", "Ratios"),
	   %%?XAE("table", [],
	   %%	[?XE("tbody", [
	   %%		      ])
	   %%	]),
	   ?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("client"))/binary>>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_stat_table(global, Lang, "client", server)
		    )
		]),
	   ?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("os"))/binary>>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_stat_table(global, Lang, "os", server)
		    )
		]),
	   ?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("client"))/binary, "/", (get_stat_n("os"))/binary>>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_stat_table(global, Lang, "client_os", server)
		    )
		]),
	   ?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("conntype"))/binary>>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_stat_table(global, Lang, "conntype", server)
		    )
		]),
	   ?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("client"))/binary, "/", (get_stat_n("conntype"))/binary>>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_stat_table(global, Lang, "client_conntype", server)
		    )
		]),
	   ?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("languages"))/binary>>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_stat_table(global, Lang, "languages", server)
		    )
		])
	  ],
    {stop, Res};
web_page_main(_, #request{path=[<<"statsdx">>, <<"top">>, Topic, Topnumber], q = _Q, lang = Lang} = _Request) ->
    Res = [?XC(<<"h1">>, <<(?T(<<"Statistics">>))/binary, " Dx">>),
	   case Topic of
		<<"offlinemsg">> -> ?XCT(<<"h2">>, <<"Top offline message queues">>);
		<<"vcard">> -> ?XCT(<<"h2">>, <<"Top vCard sizes">>);
		<<"roster">> -> ?XCT(<<"h2">>, <<"Top rosters">>)
	   end,
	   ?XE(<<"table">>,
	       [?XE(<<"thead">>, [?XE(<<"tr">>,
				  [?XE(<<"td">>, [?CT(<<"#">>)]),
				   ?XE(<<"td">>, [?CT(<<"Jabber ID">>)]),
				   ?XE(<<"td">>, [?CT(<<"Value">>)])]
				 )]),
		?XE(<<"tbody">>, do_top_table(global, Lang, Topic, Topnumber, server))
	       ])
	  ],
    {stop, Res};
web_page_main(_, #request{path=[<<"statsdx">> | FilterURL], q = Q, lang = Lang} = _Request) ->
    Filter = parse_url_filter(FilterURL),
    Sort_query = get_sort_query(Q),
    FilterS = io_lib:format("~p", [Filter]),
    Res = [?XC(<<"h1">>, <<(?T(<<"Statistics">>))/binary, " Dx">>),
	   ?XC(<<"h2">>, list_to_binary("Sessions with: " ++ FilterS)),
	   ?XE(<<"table">>,
	       [
		?XE(<<"thead">>, [?XE(<<"tr">>, make_sessions_table_tr(Lang) )]),
		?XE(<<"tbody">>, do_sessions_table(global, Lang, Filter, Sort_query, server))
	       ])
	  ],
    {stop, Res};
web_page_main(Acc, _) -> Acc.

do_top_table(_Node, Lang, Topic, TopnumberBin, Host) ->
    List = get_top_users(Host, jlib:binary_to_integer(TopnumberBin), Topic),
    %% get_top_users(Topnumber, "roster")
    {List2, _} = lists:mapfoldl(
      fun({Value, UserB, ServerB}, Counter) ->
	    User = binary_to_list(UserB),
	    Server = binary_to_list(ServerB),
	    UserJID = User++"@"++Server,
	    UserJIDUrl = "/admin/server/" ++ Server ++ "/user/" ++ User ++ "/",
			ValueString = integer_to_list(Value),
	    ValueEl = case Topic of
		<<"offlinemsg">> -> {url, UserJIDUrl++"queue/", ValueString};
		<<"vcard">> -> {url, UserJIDUrl++"vcard/", ValueString};
		<<"roster">> -> {url, UserJIDUrl++"roster/", ValueString};
		_ -> ValueString
	    end,
	    {do_table_element(Counter, Lang, UserJID, {fixed_url, UserJIDUrl}, ValueEl),
		Counter+1}
      end,
    1,
      List
     ),
     List2.

%% Code copied from mod_muc_admin.erl
%% Returns: {normal | reverse, Integer}
get_sort_query(Q) ->
    case catch get_sort_query2(Q) of
	{ok, Res} -> Res;
	_ -> {normal, 1}
    end.
get_sort_query2(Q) ->
    {value, {_, String}} = lists:keysearch("sort", 1, Q),
    Integer = list_to_integer(String),
    case Integer >= 0 of
	true -> {ok, {normal, Integer}};
	false -> {ok, {reverse, abs(Integer)}}
    end.
make_sessions_table_tr(Lang) ->
    Titles = [<<"Jabber ID">>,
	      <<"Client ID">>,
	      <<"OS ID">>,
	      <<"Lang">>,
	      <<"Connection">>,
	      <<"Client">>,
	      <<"Version">>,
	      <<"OS">>],
    {Titles_TR, _} =
	lists:mapfoldl(
	  fun(Title, Num_column) ->
		  NCS = list_to_binary(integer_to_list(Num_column)),
		  TD = ?XE(<<"td">>, [?CT(Title),
				  ?BR,
				  ?ACT(<<"?sort=", NCS/binary>>, <<"<">>),
				  ?C(<<" ">>),
				  ?ACT(<<"?sort=-", NCS/binary>>, <<">">>)]),
		  {TD, Num_column+1}
	  end,
	  1,
	  Titles),
    Titles_TR.

%% @spec (Filter::string()) -> [{Class::string(), Type::string()}]
parse_url_filter(FilterURL) ->
    [List] = string:tokens(FilterURL, "/"),
    parse_url_filter(List, []).
parse_url_filter([Class, Type | List], Res) ->
    parse_url_filter(List, Res ++ [{Class, Type}]);
parse_url_filter(_, Res) ->
    Res.


web_page_node(_, Node, [<<"statsdx">>], _Query, Lang) ->
    TransactionsCommited =
	rpc:call(Node, mnesia, system_info, [transaction_commits]),
    TransactionsAborted =
	rpc:call(Node, mnesia, system_info, [transaction_failures]),
    TransactionsRestarted =
	rpc:call(Node, mnesia, system_info, [transaction_restarts]),
    TransactionsLogged =
	rpc:call(Node, mnesia, system_info, [transaction_log_writes]),

    Res =
	[?XC(<<"h1">>, list_to_binary(io_lib:format(?T("~p statistics"), [Node]))),
	 ?XC(<<"h3">>, <<"Connections">>),
	 ?XAE(<<"table">>, [],
	      [?XE(<<"tbody">>, [
			     do_stat(global, Lang, "onlineusers"),
			     do_stat(Node, Lang, "httppollusers"),
			     do_stat(Node, Lang, "httpbindusers"),
			     do_stat(Node, Lang, "s2sconnections"),
			     do_stat(Node, Lang, "s2sservers")
			    ])
	      ]),
	 ?XC(<<"h3">>, <<"ejabberd">>),
	 ?XAE(<<"table">>, [],
	      [?XE(<<"tbody">>, [
			     do_stat(Node, Lang, "ejabberdversion")
			    ])
	      ]),
	 ?XC(<<"h3">>, <<"Erlang">>),
	 ?XAE(<<"table">>, [],
	      [?XE(<<"tbody">>, [
			     do_stat(Node, Lang, "operatingsystem"),
			     do_stat(Node, Lang, "erlangmachine"),
			     do_stat(Node, Lang, "erlangmachinetarget"),
			     do_stat(Node, Lang, "maxprocallowed"),
			     do_stat(Node, Lang, "procqueue"),
			     do_stat(Node, Lang, "totalerlproc")
			    ])
	      ]),
	 ?XC(<<"h3">>, <<"Times">>),
	 ?XAE(<<"table">>, [],
	      [?XE(<<"tbody">>, [
			     do_stat(Node, Lang, "uptime"),
			     do_stat(Node, Lang, "uptimehuman"),
			     do_stat(Node, Lang, "lastrestart"),
			     do_stat(Node, Lang, "cputime")
			    ])
	      ]),
	 ?XC(<<"h3">>, <<"CPU">>),
	 ?XAE(<<"table">>, [],
	      [?XE(<<"tbody">>, [
			     do_stat(Node, Lang, "cpu_avg1"),
			     do_stat(Node, Lang, "cpu_avg5"),
			     do_stat(Node, Lang, "cpu_avg15"),
			     do_stat(Node, Lang, "cpu_nprocs")%,
			     %%do_stat(Node, Lang, "cpu_util_user"),
			     %%do_stat(Node, Lang, "cpu_nice_user"),
			     %%do_stat(Node, Lang, "cpu_kernel"),
			     %%do_stat(Node, Lang, "cpu_idle"),
			     %%do_stat(Node, Lang, "cpu_wait")
			    ])
	      ]),
	 %%?XC("h3", "RAM"),
	 %%?XAE("table", [],
	 %% [?XE("tbody", [
	 %%  do_stat(Node, Lang, "memsup_system"),
	 %%  do_stat(Node, Lang, "memsup_free"),
	 %%  do_stat(Node, Lang, "reductions")
	 %%])
	 %%]),
	 ?XC(<<"h3">>, <<"Memory (bytes)">>),
	 ?XAE(<<"table">>, [],
	      [?XE(<<"tbody">>, [
			     do_stat(Node, Lang, "memory_total"),
			     do_stat(Node, Lang, "memory_processes"),
			     do_stat(Node, Lang, "memory_processes_used"),
			     do_stat(Node, Lang, "memory_system"),
			     do_stat(Node, Lang, "memory_atom"),
			     do_stat(Node, Lang, "memory_atom_used"),
			     do_stat(Node, Lang, "memory_binary"),
			     do_stat(Node, Lang, "memory_code"),
			     do_stat(Node, Lang, "memory_ets")
			    ])
	      ]),
	 ?XC(<<"h3">>, <<"Database">>),
	 ?XAE(<<"table">>, [],
	      [?XE(<<"tbody">>,
		   [
		    ?XE(<<"tr">>, [?XCT(<<"td">>, <<"Transactions commited">>),
			       ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
				    list_to_binary(integer_to_list(TransactionsCommited)))]),
		    ?XE(<<"tr">>, [?XCT(<<"td">>, <<"Transactions aborted">>),
			       ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
				    list_to_binary(integer_to_list(TransactionsAborted)))]),
		    ?XE(<<"tr">>, [?XCT(<<"td">>, <<"Transactions restarted">>),
			       ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
				    list_to_binary(integer_to_list(TransactionsRestarted)))]),
		    ?XE(<<"tr">>, [?XCT(<<"td">>, <<"Transactions logged">>),
			       ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
				    list_to_binary(integer_to_list(TransactionsLogged)))])
		   ])
	      ])],
    {stop, Res};
web_page_node(Acc, _, _, _, _) -> Acc.

web_page_host(_, Host,
	      #request{path = [<<"statsdx">>],
		       lang = Lang} = _Request) ->
    Res = [?XC(<<"h1">>, <<(?T(<<"Statistics">>))/binary, " Dx">>),
	   ?XC(<<"h2">>, Host),
	   ?XC(<<"h3">>, <<"Accounts">>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>, [
			       do_stat(global, Lang, "registeredusers", Host)
			      ])
		]),
	   ?XC(<<"h3">>, <<"Roster">>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>, [
			       do_stat(global, Lang, "totalrosteritems", Host),
			       %%get_meanitemsinroster2(TotalRosterItems, RegisteredUsers)
			       ?XE(<<"tr">>,
				  [?XE(<<"td">>, [?C(<<"Top rosters">>) ]),
				   ?XE(<<"td">>, [
				    ?ACT(<<"top/roster/30">>, <<"30">>), ?C(<<", ">>),
				    ?ACT(<<"top/roster/100">>, <<"100">>), ?C(<<", ">>),
				    ?ACT(<<"top/roster/500">>, <<"500">>) ])]
				 )
			      ])
		]),
	   ?XC(<<"h3">>, <<"Users">>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>, [
			       do_stat(global, Lang, "onlineusers", Host),
			       %%do_stat(global, Lang, "offlinemsg", Host), %% This make take a lot of time
			       %%do_stat(global, Lang, "vcards", Host) %% This make take a lot of time
			       ?XE(<<"tr">>,
				  [?XE(<<"td">>, [?C(<<"Top offline message queues">>)]),
				   ?XE(<<"td">>, [
				    ?ACT(<<"top/offlinemsg/30">>, <<"30">>), ?C(<<", ">>),
				    ?ACT(<<"top/offlinemsg/100">>, <<"100">>), ?C(<<", ">>),
				    ?ACT(<<"top/offlinemsg/500">>, <<"500">>) ])]
				 ),
			       ?XE(<<"tr">>,
				  [?XE(<<"td">>, [?C(<<"Top vCard sizes">>) ]),
				   ?XE(<<"td">>, [
				    ?ACT(<<"top/vcard/5">>, <<"5">>), ?C(<<", ">>),
				    ?ACT(<<"top/vcard/30">>, <<"30">>), ?C(<<", ">>),
				    ?ACT(<<"top/vcard/100">>, <<"100">>), ?C(<<", ">>),
				    ?ACT(<<"top/vcard/500">>, <<"500">>) ])]
				 )
			      ])
		]),
	   ?XC(<<"h3">>, <<"Connections">>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>, [
			       do_stat(global, Lang, "s2sconnections", Host)
			      ])
		]),
	   ?XC(<<"h3">>, <<"MUC">>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>, [
			       do_stat(global, Lang, "totalmucrooms", Host),
			       do_stat(global, Lang, "permmucrooms", Host),
			       do_stat(global, Lang, "regmucrooms", Host)
			      ])
		]),
	   %%?XC("h3", "IRC"),
	   %%?XAE("table", [],
	   %% [?XE("tbody", [
	   %%  do_stat(global, Lang, "ircconns", Host)
	   %% ])
	   %%]),
	   %%?XC("h3", "Pub/Sub"),
	   %%?XAE("table", [],
	   %% [?XE("tbody", [
	   %%  do_stat(global, Lang, "regpubsubnodes", Host)
	   %% ])
	   %%]),
	   ?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("client"))/binary>>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_stat_table(global, Lang, "client", Host)
		    )
		]),
	   ?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("os"))/binary>>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_stat_table(global, Lang, "os", Host)
		    )
		]),
	   ?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("client"))/binary, "/", (get_stat_n("os"))/binary>>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_stat_table(global, Lang, "client_os", Host)
		    )
		]),
	   ?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("conntype"))/binary>>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_stat_table(global, Lang, "conntype", Host)
		    )
		]),
	   ?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("client"))/binary, "/", (get_stat_n("conntype"))/binary>>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_stat_table(global, Lang, "client_conntype", Host)
		    )
		]),
	   ?XC(<<"h3">>, <<"Sessions: ", (get_stat_n("languages"))/binary>>),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_stat_table(global, Lang, "languages", Host)
		    )
		]),
	   ?XC(<<"h3">>, <<"Ratios">>),
	   ?XAE(<<"table">>, [],
	   	[?XE(<<"tbody">>, [
			       do_stat(global, Lang, "user_login", Host),
			       do_stat(global, Lang, "user_logout", Host),
			       do_stat(global, Lang, "register_user", Host),
			       do_stat(global, Lang, "remove_user", Host),
			       do_stat(global, Lang, {send, iq, in}, Host),
			       do_stat(global, Lang, {send, iq, out}, Host),
			       do_stat(global, Lang, {send, message, in}, Host),
			       do_stat(global, Lang, {send, message, out}, Host),
			       do_stat(global, Lang, {send, presence, in}, Host),
			       do_stat(global, Lang, {send, presence, out}, Host),
			       do_stat(global, Lang, {recv, iq, in}, Host),
			       do_stat(global, Lang, {recv, iq, out}, Host),
			       do_stat(global, Lang, {recv, message, in}, Host),
			       do_stat(global, Lang, {recv, message, out}, Host),
			       do_stat(global, Lang, {recv, presence, in}, Host),
			       do_stat(global, Lang, {recv, presence, out}, Host)
	   		      ])
	   	])
	  ],
    {stop, Res};
web_page_host(_, Host, #request{path=[<<"statsdx">>, <<"top">>, Topic, Topnumber], q = _Q, lang = Lang} = _Request) ->
    Res = [?XC(<<"h1">>, <<(?T(<<"Statistics">>))/binary, " Dx">>),
	   case Topic of
		<<"offlinemsg">> -> ?XCT(<<"h2">>, <<"Top offline message queues">>);
		<<"vcard">> -> ?XCT(<<"h2">>, <<"Top vCard sizes">>);
		<<"roster">> -> ?XCT(<<"h2">>, <<"Top rosters">>)
	   end,
	   ?XE(<<"table">>,
	       [?XE(<<"thead">>, [?XE(<<"tr">>,
				  [?XE(<<"td">>, [?CT(<<"#">>)]),
				   ?XE(<<"td">>, [?CT(<<"Jabber ID">>)]),
				   ?XE(<<"td">>, [?CT(<<"Value">>)])]
				 )]),
		?XE(<<"tbody">>, do_top_table(global, Lang, Topic, Topnumber, Host))
	       ])
	  ],
    {stop, Res};
web_page_host(_, Host, #request{path=[<<"statsdx">> | FilterURL], q = Q,
				lang = Lang} = _Request) ->
    Filter = parse_url_filter(FilterURL),
    Sort_query = get_sort_query(Q),
    Res = [?XC(<<"h1">>, <<(?T(<<"Statistics">>))/binary, " Dx">>),
	   ?XC(<<"h2">>, list_to_binary("Sessions with: "++io_lib:format("~p", [Filter]))),
	   ?XAE(<<"table">>, [],
		[?XE(<<"tbody">>,
		     do_sessions_table(global, Lang, Filter, Sort_query, Host)
		    )
		])
	  ],
    {stop, Res};
web_page_host(Acc, _, _) -> Acc.


%%%==================================
%%%% Web Admin Utils

do_table_element(Lang, L, StatLink, N) ->
    do_table_element(no_counter, Lang, L, StatLink, N).
do_table_element(Counter, Lang, L, StatLink, N) ->
    ?XE(<<"tr">>, [
	       case Counter of
		   no_counter -> ?C(<<"">>);
		   _ -> ?XE(<<"td">>, [?C(list_to_binary(integer_to_list(Counter)))])
               end,
	       case StatLink of
		   no_link -> ?XCT(<<"td">>, L);
		   {fixed_url, Fixedurl} -> ?XE(<<"td">>, [?AC(list_to_binary(Fixedurl), list_to_binary(L))]);
		   _ -> ?XE(<<"td">>, [?AC(list_to_binary(make_url(StatLink, L)), list_to_binary(L))])
               end,
	       case N of
		   {url, NUrl, NName} -> ?XAE(<<"td">>, [{<<"class">>, <<"alignright">>}], [?AC(list_to_binary(NUrl), list_to_binary(NName))]);
		   N when is_list(N) -> ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], list_to_binary(N));
		   _ -> ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], N)
               end
	      ]).

make_url(StatLink, L) ->
    List = case string:tokens(StatLink, "_") of
	       [Stat] ->
		   [Stat, L];
	       [Stat1, Stat2] ->
		   [L1, L2] = string:tokens(L, "/"),
		   [Stat1, L1, Stat2, L2]
	   end,
    string:join(List, "/").

do_stat_table(global, Lang, Stat, Host) ->
    Os = mod_statsdx:get_statistic(global, [Stat, Host]),
    lists:map(
      fun({L, N}) ->
	      do_table_element(Lang, L, Stat, io_lib:format("~p", [N]))
      end,
      lists:reverse(lists:keysort(2, Os))
     ).

do_sessions_table(_Node, _Lang, Filter, {Sort_direction, Sort_column}, Host) ->
    Sessions = get_sessions_filtered(Filter, Host),
    SessionsSorted = sort_sessions(Sort_direction, Sort_column, Sessions),
    lists:map(
      fun( {{session, JID}, Client_id, OS_id, LangS, ConnType, Client, Version, OS} ) ->
	      Lang = list_to_binary(LangS),
	      User = binary_to_list(JID#jid.luser),
	      Server = binary_to_list(JID#jid.lserver),
	      UserURL = "/admin/server/" ++ Server ++ "/user/" ++ User ++ "/",
	      ?XE(<<"tr">>, [
			 ?XE(<<"td">>, [?AC(list_to_binary(UserURL), jlib:jid_to_string(JID))]),
			 ?XCTB("td", atom_to_list(Client_id)),
			 ?XCTB("td", atom_to_list(OS_id)),
			 ?XCTB("td", LangS),
			 ?XCTB("td", atom_to_list(ConnType)),
			 ?XCTB("td", Client),
			 ?XCTB("td", Version),
			 ?XCTB("td", OS)
			])
      end,
      SessionsSorted
     ).

%% Code copied from mod_muc_admin.erl
sort_sessions(Direction, Column, Rooms) ->
    Rooms2 = lists:keysort(Column, Rooms),
    case Direction of
	normal -> Rooms2;
	reverse -> lists:reverse(Rooms2)
    end.

get_sessions_filtered(Filter, server) ->
    lists:foldl(
      fun(Host, Res) ->
	      try get_sessions_filtered(Filter, Host) of
		  List when is_list(List) -> List ++ Res
	      catch
		  _:_ -> Res
	      end
      end,
      [],
      ejabberd_config:get_myhosts());
get_sessions_filtered(Filter, Host) ->
    Match = case Filter of
		[{<<"client">>, Client}] -> {{session, '$1'}, jlib:binary_to_atom(Client), '$2', '$3', '$4', '$5', '$6', '$7'};
		[{<<"os">>, OS}] -> {{session, '$1'}, '$2', jlib:binary_to_atom(OS), '$3', '$4', '$5', '$6', '$7'};
		[{<<"conntype">>, ConnType}] -> {{session, '$1'}, '$2', '$3', '$4', jlib:binary_to_atom(ConnType), '$5', '$6', '$7'};
		[{<<"languages">>, Lang}] -> {{session, '$1'}, '$2', '$3', binary_to_list(Lang), '$4', '$5', '$6', '$7'};
		[{<<"client">>, Client}, {<<"os">>, OS}] -> {{session, '$1'}, jlib:binary_to_atom(Client), jlib:binary_to_atom(OS), '$3', '$4', '$5', '$6', '$7'};
		[{<<"client">>, Client}, {<<"conntype">>, ConnType}] -> {{session, '$1'}, jlib:binary_to_atom(Client), '$2', '$3', jlib:binary_to_atom(ConnType), '$5', '$6', '$7'};
		_ -> {{session, '$1'}, '$2', '$3', '$4', '$5'}
	    end,
    ets:match_object(table_name(Host), Match).

do_stat(Node, Lang, Stat) ->
    ?XE(<<"tr">>, [
	       ?XCT(<<"td">>, get_stat_n(Stat)),
	       ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}],
		    get_stat_v(Node, [Stat]))]).

do_stat(Node, Lang, Stat, Host) ->
    %%[Res] = get_stat_v(Node, [Stat, Host]),
    %%do_table_element(Lang, get_stat_n(Stat), Res).
    do_table_element(Lang, get_stat_n(Stat), no_link, get_stat_v(Node, [Stat, Host])).

%% Get a stat name
get_stat_n(Stat) ->
    list_to_binary(mod_statsdx:get_statistic(foo, [Stat, title])).
%% Get a stat value
get_stat_v(Node, Stat) -> list_to_binary(get_stat_v2(mod_statsdx:get_statistic(Node, Stat))).
get_stat_v2(Value) when is_list(Value) -> Value;
get_stat_v2(Value) when is_float(Value) -> io_lib:format("~.4f", [Value]);
get_stat_v2(Value) when is_integer(Value) ->
    [Str] = io_lib:format("~p", [Value]),
    pretty_string_int(Str);
get_stat_v2(Value) -> io_lib:format("~p", [Value]).

%% Transform "1234567890" into "1,234,567,890"
pretty_string_int(String) ->
    {_, Result} = lists:foldl(
		    fun(NewNumber, {3, Result}) ->
			    {1, [NewNumber, $, | Result]};
		       (NewNumber, {CountAcc, Result}) ->
			    {CountAcc+1, [NewNumber | Result]}
		    end,
		    {0, ""},
		    lists:reverse(String)),
    Result.

%%%==================================
%%%% Commands

commands() ->
    [
     #ejabberd_commands{name = get_top_users, tags = [stats],
			desc = "Get top X users with larger offlinemsg, vcard or roster.",
			policy = admin,
			module = ?MODULE, function = get_top_users,
			args = [{topnumber, integer}, {topic, string}],
			result = {top, {list,
					{user, {tuple, [
							{value, integer},
							{user, string},
							{server, string}
						       ]}}
				       }}},
     #ejabberd_commands{name = getstatsdx, tags = [stats],
			desc = "Get statistical value.",
			policy = admin,
			module = ?MODULE, function = getstatsdx,
			args = [{name, string}],
			result = {stat, integer}},
     #ejabberd_commands{name = getstatsdx_host, tags = [stats],
			desc = "Get statistical value for this host.",
			policy = admin,
			module = ?MODULE, function = getstatsdx,
			args = [{name, string}, {host, string}],
			result = {stat, integer}}
    ].

getstatsdx(Name) ->
    get_statistic(global, [Name]).

getstatsdx(Name, Host) ->
    get_statistic(global, [Name, Host]).

get_top_users(Number, Topic) ->
    get_top_users(server, Number, Topic).

%% Returns: [{Integer, User, Server}]
get_top_users(Host, Number, <<"vcard">>) ->
    get_top_users_vcard(Host, Number);
get_top_users(Host, Number, <<"offlinemsg">>) ->
    get_top_users(Host, Number, offline_msg, #offline_msg.us);
get_top_users(Host, Number, <<"roster">>) ->
    get_top_users(Host, Number, roster, #roster.us).


get_top_users(Host, TopX, Table, RecordUserPos) ->
    F = fun() ->
		F2 = fun(R, {H, Dict}) ->
			     {LUser, LServer} = element(RecordUserPos, R),
			     case H of
				 server ->
				     {Host, dict:update_counter({LUser, LServer}, 1, Dict)};
				 LServer ->
				     {Host, dict:update_counter({LUser, LServer}, 1, Dict)};
				 _ ->
				     {Host, Dict}
			     end
		     end,
		mnesia:foldl(F2, {Host, dict:new()}, Table)
	end,
    {atomic, {Host, DictRes}} = mnesia:transaction(F),
    {_, _, Result} = dict:fold(
		       fun({User, Server}, Num, {EntryNumber, Size, TopList}) ->
			       case {Num > EntryNumber, Size < TopX} of
				   {false, true} ->
				       {Num, Size+1, lists:keymerge(1, TopList, [{Num, User, Server}])};
				   {true, true} ->
				       {EntryNumber, Size+1, lists:keymerge(1, TopList, [{Num, User, Server}])};
				   {true, false} ->
				       [{NewEntryNumber, _, _} | _] = TopList2 = lists:keydelete(EntryNumber, 1, TopList),
				       {NewEntryNumber, Size, lists:keymerge(1, TopList2, [{Num, User, Server}])};
				   {false, false} ->
				       {EntryNumber, Size, TopList}
			       end
		       end,
		       {10000000000000000, 0, []},
		       DictRes),
    lists:reverse(Result).

get_top_users_vcard(Host, Number) ->
    F = fun() ->
	B = fun get_users_vcard_fun/2,
	{_Host, _NumSelects, _MinSize, _Sizes, Selects} = mnesia:foldl(B, {Host, Number, -1, [], []}, vcard), %+++
	Selects
    end,
    {atomic, Result} = mnesia:transaction(F),
    lists:reverse(Result).

%% Selects = [{Size, Vcard}] sorted from smaller to larger
get_users_vcard_fun(#vcard{us = {_, Host1}}, {HostReq, NumRemaining, MinSize, Sizes, Selects})
    when (Host1 /= HostReq) and (HostReq /= server) ->
    {HostReq, NumRemaining, MinSize, Sizes, Selects};
get_users_vcard_fun(Vcard, {HostReq, NumRemaining, MinSize, Sizes, Selects}) ->
    Binary = fxml:element_to_binary(Vcard#vcard.vcard),
    Size = byte_size(Binary),
    case {Size > MinSize, NumRemaining > 0} of
	{true, true} ->
	    {User, Host} = Vcard#vcard.us,
	    Selects2 = lists:umerge(Selects, [{Size, User, Host}]),
	    Sizes2 = lists:umerge(Sizes, [Size]),
	    MinSize2 = lists:min(Sizes2),
	    {HostReq, NumRemaining-1, MinSize2, Sizes2, Selects2};
	{true, false} ->
	    [_ | SelectsReduced] = Selects,
	    [_ | SizesReduced] = Sizes,
	    Sizes2 = lists:umerge(SizesReduced, [Size]),
	    MinSize2 = lists:min(Sizes2),
	    {User, Host} = Vcard#vcard.us,
	    Selects2 = lists:umerge(SelectsReduced, [{Size, User, Host}]),
	    {HostReq, NumRemaining, MinSize2, Sizes2, Selects2};
	{false, _} ->
	    {HostReq, NumRemaining, MinSize, Sizes, Selects}
    end.


%%%==================================

%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=:
