%%%----------------------------------------------------------------------
%%% File    : mod_webpresence.erl
%%% Author  : Igor Goryachev <igor@goryachev.org>, Badlop, runcom <antonio.murdaca@gmail.com>
%%% Purpose : Allow user to show presence in the web
%%% Created : 30 Apr 2006 by Igor Goryachev <igor@goryachev.org>
%%% Id      : $Id: mod_webpresence.erl 1083 2010-06-01 18:32:55Z badlop $
%%%----------------------------------------------------------------------

-module(mod_webpresence).

-behaviour(gen_server).
-behaviour(gen_mod).

%% API
-export([start/2,
         stop/1,
         remove_user/2,
         web_menu_host/3, web_page_host/3,
         process_disco_info/1,
         process_disco_items/1,
         process_vcard/1,
         process_register/1,
         process/2
        ]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3,
         mod_status/0,
	 mod_opt_type/1, mod_options/1, depends/2, mod_doc/0]).

%% API
-export([start_link/0]).

-export([web_menu_system/3]).

-include_lib("xmpp/include/xmpp.hrl").
-include("logger.hrl").
-include("translate.hrl").
-include("ejabberd_web_admin.hrl").
-include("ejabberd_http.hrl").

-record(webpresence, {us, ridurl = false, jidurl = false, xml = false, avatar = false, js = false, text = false, icon = "jsf-text"}).
-record(state, {host, server_host, base_url, access}).
-record(presence2, {resource, show, priority, status}).

-define(AUTO_ACL, webpresence_auto).

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

start(Host, Opts) ->
    ejabberd_hooks:add(webadmin_menu_system_post, global, ?MODULE, web_menu_system, 1000-$p),
    Dir = case gen_mod:get_opt(pixmaps_path, Opts) of
              auto ->
                  Package = atom_to_list(?MODULE),
                  filename:join([ext_mod:modules_dir(), Package, "priv", "pixmaps"]);
              PP ->
                  PP
          end,
    Heir = {heir, whereis(ext_mod), ?MODULE},
    catch ets:new(pixmaps_dirs, [named_table, public, Heir]),
    ets:insert(pixmaps_dirs, {directory, Dir}),
    case gen_mod:start_child(?MODULE, Host, Opts) of
       {ok, Ref} ->
           {ok, Ref};
       {error, {already_started, Ref}} ->
           {ok, Ref};
       {error, Reason} ->
           {error, Reason}
    end.

stop(Host) ->
    ejabberd_hooks:delete(webadmin_menu_system_post, global, ?MODULE, web_menu_system, 1000-$p),
    Proc = gen_mod:get_module_proc(Host, ?MODULE),
    gen_server:call(Proc, stop),
    gen_mod:stop_child(?MODULE, Host),
    ok.

mod_opt_type(host) ->
    econf:host();
mod_opt_type(access) ->
    econf:acl();
mod_opt_type(pixmaps_path) ->
    econf:either(auto, econf:directory());
mod_opt_type(port) ->
    econf:pos_int();
mod_opt_type(path) ->
    econf:binary();
mod_opt_type(baseurl) ->
    econf:binary().

-spec mod_options(binary()) -> [{atom(), any()}].
mod_options(Host) ->
    [{host, <<"webpresence.", Host/binary>>},
     {access, local},
     {pixmaps_path, auto},
     {port, 5280},
     {path, <<"presence">>},
     {baseurl, iolist_to_binary(io_lib:format(<<"http://~s:5280/presence/">>, [Host]))}].

-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
depends(_Host, _Opts) ->
    [].

mod_doc() -> #{}.

mod_status() ->
    Host = ejabberd_config:get_myname(),
    Url = case find_handler_port_path(any, ?MODULE) of
        [] -> undefined;
        [{ThisTls, Port, Path} | _] ->
            Protocol = case ThisTls of
                           false -> <<"http">>;
                           true -> <<"https">>
                       end,
            <<Protocol/binary,
              "://",
              Host/binary,
              ":",
              (integer_to_binary(Port))/binary,
              "/",
              (str:join(Path, <<"/">>))/binary,
              "/">>
    end,
    io_lib:format("Serving Webpresence in: ~s", [binary_to_list(Url)]).

find_handler_port_path(Tls, Module) ->
    lists:filtermap(
      fun({{Port, _, _},
           ejabberd_http,
           #{tls := ThisTls, request_handlers := Handlers}})
            when (Tls == any) or (Tls == ThisTls) ->
              case lists:keyfind(Module, 2, Handlers) of
                  false -> false;
                  {Path, Module} -> {true, {ThisTls, Port, Path}}
              end;
         (_) -> false
      end, ets:tab2list(ejabberd_listener)).

%%====================================================================
%% gen_server callbacks
%%====================================================================

%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([Host, Opts]) ->
    mnesia:create_table(webpresence,
			[{disc_copies, [node()]},
			 {attributes, record_info(fields, webpresence)}]),
    mnesia:add_table_index(webpresence, ridurl),
    update_table(),
    MyHost = gen_mod:get_opt(host, Opts),
    Access = gen_mod:get_opt(access, Opts),
    BaseURL1 = gen_mod:get_opt(baseurl, Opts),
    BaseURL2 = ejabberd_regexp:greplace(BaseURL1, <<"@HOST@">>, Host),
    register_iq_handlers(MyHost),
    ejabberd_router:register_route(MyHost, Host),
    ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50),
    ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
    ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
    {ok, #state{host = MyHost,
		server_host = Host,
		base_url = BaseURL2,
		access = Access}}.

register_iq_handlers(Host) ->
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_REGISTER,
                                  ?MODULE, process_register),
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
                                  ?MODULE, process_vcard),
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
                                  ?MODULE, process_disco_info),
    gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
                                  ?MODULE, process_disco_items).

unregister_iq_handlers(Host) ->
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_REGISTER),
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
    gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS).

%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(stop, _From, State) ->
    {stop, normal, ok, State}.

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({route, Packet},
	    #state{host = Host,
		   server_host = ServerHost,
		   base_url = BaseURL,
		   access = Access} = State) ->
    From = xmpp:get_from(Packet),
    To = xmpp:get_to(Packet),
    case catch do_route(Host, ServerHost, Access, From, To, Packet, BaseURL) of
	{'EXIT', Reason} ->
	    ?ERROR_MSG("~p", [Reason]);
	_ ->
	    ok
    end,
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, #state{host = Host}) ->
    ejabberd_router:unregister_route(Host),
    unregister_iq_handlers(Host),
    ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50),
    ejabberd_hooks:delete(webadmin_menu_host, Host, ?MODULE, web_menu_host, 50),
    ejabberd_hooks:delete(webadmin_page_host, Host, ?MODULE, web_page_host, 50),
    ok.

%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
do_route(Host, ServerHost, Access, From, To, Packet, BaseURL) ->
    case acl:match_rule(ServerHost, Access, From) of
	allow ->
	    do_route1(Host, From, To, Packet, BaseURL);
	_ ->
            Lang = xmpp:get_lang(Packet),
	    ErrText = <<"Access denied by service policy">>,
	    Err = xmpp:err_forbidden(ErrText, Lang),
	    ejabberd_router:route_error(Packet, Err)
    end.

-spec process_vcard(iq()) -> iq().
process_vcard(#iq{type = get, lang = Lang, sub_els = [#vcard_temp{}]} = IQ) ->
    Desc = translate:translate(Lang, <<"ejabberd Web Presence module">>),
    xmpp:make_iq_result(
      IQ, #vcard_temp{fn = <<"ejabberd/mod_webpresence">>,
                      url = ejabberd_config:get_uri(),
                      desc = Desc});
process_vcard(#iq{type = set, lang = Lang} = IQ) ->
    Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_vcard(#iq{lang = Lang} = IQ) ->
    Txt = <<"No module is handling this query">>,
    xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).

do_route1(_Host, _From, _To, #iq{} = IQ, _BaseURL) ->
    ejabberd_router:process_iq(IQ);
do_route1(_Host, _From, _To, Packet, _BaseURL) ->
    case xmpp:get_type(Packet) of
	error -> ok;
	_ ->
	    Err = xmpp:err_item_not_found(),
	    ejabberd_router:route_error(Packet, Err)
    end.

-spec process_register(iq()) -> iq().
process_register(#iq{type = get, from = From, to = To, lang = Lang,
                     sub_els = [#register{}]} = IQ) ->
    Host = To#jid.lserver,
    xmpp:make_iq_result(IQ, iq_get_register_info(Host, From, Lang));
process_register(#iq{type = set, from = From, to = To,
                     lang = Lang, sub_els = [El = #register{}]} = IQ) ->
    Host = To#jid.lserver,
    ServerHost = ejabberd_router:host_of_route(Host),
    case process_iq_register_set(ServerHost, Host, From, El, Lang) of
        {result, Result} ->
            xmpp:make_iq_result(IQ, Result);
        {error, Err} ->
            xmpp:make_error(IQ, Err)
    end.


-spec process_disco_info(iq()) -> iq().
process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
    Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_disco_info(#iq{type = get, lang = Lang,
                       sub_els = [#disco_info{node = <<"">>}]} = IQ) ->
    Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
                ?NS_REGISTER, ?NS_VCARD],
    Identity = #identity{category = <<"component">>,
                         type = <<"presence">>,
                         name = translate:translate(Lang, <<"Web Presence">>)},
    xmpp:make_iq_result(
      IQ, #disco_info{features = Features,
                      identities = [Identity]});
process_disco_info(#iq{type = get, lang = Lang,
                       sub_els = [#disco_info{}]} = IQ) ->
    xmpp:make_error(IQ, xmpp:err_item_not_found(<<"Node not found">>, Lang));
process_disco_info(#iq{lang = Lang} = IQ) ->
    Txt = <<"No module is handling this query">>,
    xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).

-spec process_disco_items(iq()) -> iq().
process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
    Txt = <<"Value 'set' of 'type' attribute is not allowed">>,
    xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
process_disco_items(#iq{type = get,
                        sub_els = [#disco_items{}]} = IQ) ->
    xmpp:make_iq_result(IQ, #disco_items{});
process_disco_items(#iq{lang = Lang} = IQ) ->
    Txt = <<"No module is handling this query">>,
    xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).

-define(XFIELDS(Type, Label, Var, Vals),
        #xmlel{
           name = <<"field">>,
           attrs = [
                    {<<"type">>, Type},
                    {<<"label">>, translate:translate(Lang, ?T(Label))},
                    {<<"var">>, Var}
                   ],
           children = Vals
          }).

-define(XFIELD(Type, Label, Var, Val),
	?XFIELDS(Type, Label, Var,
		 [
                  #xmlel{
                     name = <<"value">>,
                     attrs = [],
                     children = [{xmlcdata, Val}]
                    }])
       ).

-define(XFIELDFIXED(Val),
        #xmlel{
           name = <<"field">>,
           attrs = [{<<"type">>, <<"fixed">>}],
           children  = [
                        #xmlel{
                           name = <<"value">>,
                           attrs = [],
                           children = [{xmlcdata, Val}]
                          }
                       ]
          }
       ).

-define(ATOM2BINARY(Val), iolist_to_binary(atom_to_list(Val))).
-define(BC(L), iolist_to_binary(L)).

ridurl_out(false) -> <<"false">>;
ridurl_out(Id) when is_binary(Id) -> <<"true">>.

get_pr(LUS) ->
    case catch mnesia:dirty_read(webpresence, LUS) of
       [#webpresence{jidurl = J, ridurl = H, xml = X, avatar = A, js = S, text = T, icon = I}] ->
           {J, H, X, A, S, T, I, true};
       _ ->
           {true, false, false, false, false, false, <<"">>, false}
    end.


iq_get_register_info(Host, From, Lang) ->
    LUS = {From#jid.luser, From#jid.lserver},
    {_JidUrl, _RidUrl, _XML, _Avatar, _JS, _Text, Icon, Registered} = get_pr(LUS),
    Nick = Icon,
    Title = <<(translate:translate(
                 Lang, <<"Web Presence Registration at ">>))/binary, Host/binary>>,
    Inst = translate:translate(Lang, <<"Enter iconset you want to use by default">>),
    Fields = muc_register:encode([{roomnick, Nick}], Lang),
    X = #xdata{type = form, title = Title,
               instructions = [Inst], fields = Fields},
    #register{nick = Nick,
              registered = Registered,
              instructions =
                  translate:translate(
                    Lang, <<"You need a client that supports x:data "
                            "to register to Web Presence">>),
              xdata = X}.

process_iq_register_set(_ServerHost, Host, From,
                        #register{remove = true}, Lang) ->
    unregister_webpresence(From, Host, Lang);
process_iq_register_set(_ServerHost, _Host, _From,
                        #register{xdata = #xdata{type = cancel}}, _Lang) ->
    {result, undefined};
process_iq_register_set(ServerHost, Host, From,
                        #register{nick = Nick, xdata = XData}, Lang) ->
    case XData of
        #xdata{type = submit, fields = Fs} ->
            try
                Options = muc_register:decode(Fs),
                N = proplists:get_value(roomnick, Options),
                iq_set_register_info(ServerHost, Host, From, N, Lang)
            catch _:{muc_register, Why} ->
                    ErrText = muc_register:format_error(Why),
                    {error, xmpp:err_bad_request(ErrText, Lang)}
            end;
        #xdata{} ->
            Txt = <<"Incorrect data form">>,
            {error, xmpp:err_bad_request(Txt, Lang)};
        _ when is_binary(Nick), Nick /= <<"">> ->
            iq_set_register_info(ServerHost, Host, From, Nick, Lang);
        _ ->
            ErrText = <<"You must fill in field \"Nickname\" in the form">>,
            {error, xmpp:err_not_acceptable(ErrText, Lang)}
    end.

iq_set_register_info(ServerHost, Host, From, Nick,
                     Lang) ->
    case iq_set_register_info2(ServerHost, Host, From, Nick, Lang) of
      {atomic, ok} -> {result, undefined};
      _ ->
          Txt = <<"Database failure">>,
          {error, xmpp:err_internal_server_error(Txt, Lang)}
    end.

iq_set_register_info2(ServerHost, Host, From, Icon, Lang) ->
    %% RidUrl2 = get_rid_final_value(RidUrl, LUS),
    LUS = {From#jid.luser, From#jid.lserver},
    WP = #webpresence{us = LUS,
		      jidurl = true,
		      ridurl = false,
		      xml = true,
		      avatar = true,
		      js = true,
		      text = true,
		      icon = Icon},
    F = fun() -> mnesia:write(WP) end,
    case mnesia:transaction(F) of
	{atomic, ok} ->
	    BaseURL = get_baseurl(ServerHost),
	    send_message_registered(WP, From, Host, BaseURL, Lang),
	    {atomic, ok};
	_ ->
	    {error, xmpp:err_internal_server_error()}
    end.

send_message_registered(WP, To, Host, BaseURL, Lang) ->
    {User, Server} = WP#webpresence.us,
    JIDS = jid:encode({User, Server, <<"">>}),
    Oavatar = case WP#webpresence.avatar of
		  %%false -> <<"">>;
		  true -> <<"  avatar\n"
			    "  avatar/my.png\n">>
	      end,
    Ojs = case WP#webpresence.js of
	      %%false -> <<"">>;
	      true -> <<"  js\n">>
	  end,
    Otext = case WP#webpresence.text of
		%%false -> <<"">>;
		true -> ?BC([
                        <<"  text\n"
			  "  text/res/<">>, translate:translate(Lang, ?T("Resource")), <<">\n">>
                                         ])
	    end,
    Oimage = case WP#webpresence.icon of
		 <<"---">> -> "";
		 I when is_binary(I) ->
                     ?BC([
		     <<"  image\n"
		       "  image/example.php\n"
		       "  image/mypresence.png\n"
		       "  image/res/<">>, translate:translate(Lang, ?T("Resource")), <<">\n"
		       "  image/theme/<">>, translate:translate(Lang, ?T("Icon Theme")), <<">\n"
		       "  image/theme/<">>, translate:translate(Lang, ?T("Icon Theme")), <<">/res/<">>, translate:translate(Lang, ?T("Resource")), <<">\n">>
                                      ])
	     end,
    Oxml = case WP#webpresence.xml of
	       %%false -> <<"">>;
	       true -> <<"  xml\n">>
	   end,
    Allowed_type = case {Oimage, Oxml, Oavatar, Otext, Ojs} of
		       {<<"">>, <<"">>, <<"">>, <<"">>, _} -> <<"js">>;
		       {<<"">>, <<"">>, <<"">>, _, _} -> <<"text">>;
		       {<<"">>, <<"">>, _, _, _} -> <<"avatar">>;
		       {<<"">>, _, _, _, _} -> <<"xml">>;
		       {_, _, _, _, _} -> <<"image">>
		   end,
    {USERID_jid, Example_jid} = case WP#webpresence.jidurl of
				    %%false -> {<<"">>, <<"">>};
				    true ->
					JIDT = ?BC([<<"jid/">>, User, <<"/">>, Server]),
					{?BC([<<"  ">>, JIDT, <<"\n">>]),
					 ?BC([<<"  ">>, BaseURL, JIDT, <<"/">>, Allowed_type, <<"/\n">>])}
				end,
    {USERID_rid, Example_rid, Text_rid} = case WP#webpresence.ridurl of
					      false -> {<<"">>, <<"">>, <<"">>}%;
					      %%RID when is_binary(RID) ->
						%%  RIDT = ?BC([<<"rid/">>, RID]),
						%%  {?BC([<<"  ">>, RIDT, <<"\n">>]),
						%%   ?BC([<<"  ">>, BaseURL, RIDT, <<"/">>, Allowed_type, <<"/\n">>]),
						%%   ?BC([translate:translate(Lang, ?T("If you forget your RandomID, register again to receive this message.")), <<"\n">>,
						%%   translate:translate(Lang, ?T("To get a new RandomID, disable the option and register again.")), <<"\n">>])
						%%  }
					  end,
    Subject = ?BC([translate:translate(Lang, ?T("Web Presence")), <<": ">>, translate:translate(Lang, ?T("registered"))]),
    Body = ?BC([translate:translate(Lang, ?T("You have registered:")), <<" ">>, JIDS, <<"\n\n">>,
	translate:translate(Lang, ?T("Use URLs like:")), <<"\n">>,
	<<"  ">>, BaseURL, <<"USERID/OUTPUT/\n">>,
	<<"\n">>,
	<<"USERID:\n">>, USERID_jid, USERID_rid, <<"\n">>,
	<<"OUTPUT:\n">>, Oimage, Oxml, Ojs, Otext, Oavatar, <<"\n">>,
	translate:translate(Lang, ?T("Example:")), <<"\n">>, Example_jid, Example_rid, <<"\n">>,
	Text_rid]),
    send_headline(Host, To, Subject, Body).

send_message_unregistered(To, Host, Lang) ->
    Subject = ?BC([translate:translate(Lang, ?T("Web Presence")), <<": ">>, translate:translate(Lang, ?T("unregistered"))]),
    Body = ?BC([translate:translate(Lang, ?T("You have unregistered.")), <<"\n\n">>]),
    send_headline(Host, To, Subject, Body).

send_headline(Host, To, Subject, Body) ->
    Packet = #message{type = headline,
	from = jid:make(Host),
	to = To,
	body = xmpp:mk_text(Body),
	subject = xmpp:mk_text(Subject)},
    ejabberd_router:route(Packet).

unregister_webpresence(From, Host, Lang) ->
    remove_user(From#jid.luser, From#jid.lserver),
    send_message_unregistered(From, Host, Lang),
    {result, undefined}.

remove_user(User, Server) ->
    mnesia:dirty_delete(webpresence, {User, Server}).

get_wp(LUser, LServer) ->
    LUS = {LUser, LServer},
    case catch mnesia:dirty_read(webpresence, LUS) of
        {'EXIT', _Reason} ->
	    try_auto_webpresence(LUser, LServer);
        [] ->
	    try_auto_webpresence(LUser, LServer);
	[WP] when is_record(WP, webpresence) ->
	    WP
    end.

try_auto_webpresence(LUser, LServer) ->
    From = jid:make(LUser, LServer, <<"">>),
    case acl:match_rule(LServer, ?AUTO_ACL, From) of
	deny ->
	    #webpresence{};
	allow ->
	    #webpresence{us = {LUser, LServer},
			 ridurl = true,
			 jidurl = true,
			 xml = true,
			 avatar = true,
			 js = true,
			 text = true,
			 icon = "jsf-jabber-text"}
   end.

get_status_weight(Show) ->
    case Show of
        <<"chat">>        -> 0;
        <<"available">>   -> 1;
        <<"away">>        -> 2;
        <<"xa">>          -> 3;
        <<"dnd">>         -> 4;
        _                 -> 9
    end.

sid_to_presence({_, Pid}) ->
    P = ejabberd_c2s:get_presence(Pid),
    #presence2{resource = (P#presence.from)#jid.resource,
              show = misc:atom_to_binary(humanize_show(P#presence.show)),
              priority = P#presence.priority,
              status = xmpp:get_text(P#presence.status)}.

humanize_show(undefined) ->
    available;
humanize_show(Show) ->
    Show.

get_presences({bare, LUser, LServer}) ->
    Sids = ejabberd_sm:get_session_sids(LUser, LServer),
    lists:map(fun sid_to_presence/1, Sids);

get_presences({sorted, LUser, LServer}) ->
    lists:sort(
      fun(A, B) ->
              if
                  A#presence2.priority == B#presence2.priority ->
                      WA = get_status_weight(A#presence2.show),
                      WB = get_status_weight(B#presence2.show),
                      WA < WB;
                  true ->
                      A#presence2.priority > B#presence2.priority
              end
      end,
      get_presences({bare, LUser, LServer}));

get_presences({xml, LUser, LServer, Show_us}) ->
    #xmlel{
       name = <<"presence">>,
       attrs = case Show_us of
                   true -> [{<<"user">>, LUser}, {<<"server">>, LServer}];
                   false -> []
               end,
       children = lists:map(
                    fun(Presence) ->
                            #xmlel{
                               name = <<"resource">>,
                               attrs = [
                                        {<<"name">>, Presence#presence2.resource},
                                        {<<"show">>, Presence#presence2.show},
                                        {<<"priority">>, intund2string(Presence#presence2.priority)}
                                       ],
                               children = [{xmlcdata, Presence#presence2.status}]
                              }
                    end,
                    get_presences({sorted, LUser, LServer})
                   )
      };

get_presences({status, LUser, LServer, LResource}) ->
    case get_presences({sorted, LUser, LServer}) of
	[] -> <<"unavailable">>;
	Rs ->
	    {value, R} = lists:keysearch(LResource, 2, Rs),
	    R#presence2.status
    end;

get_presences({status, LUser, LServer}) ->
    case get_presences({sorted, LUser, LServer}) of
        [Highest | _Rest] ->
            Highest#presence2.status;
        _ ->
            <<"unavailable">>
    end;

get_presences({show, LUser, LServer, LResource}) ->
    case get_presences({sorted, LUser, LServer}) of
	[] -> <<"unavailable">>;
	Rs ->
	    {value, R} = lists:keysearch(LResource, 2, Rs),
	    R#presence2.show
    end;

get_presences({show, LUser, LServer}) ->
    case get_presences({sorted, LUser, LServer}) of
        [Highest | _Rest] ->
            Highest#presence2.show;
        _ ->
            <<"unavailable">>
    end.

make_js(WP, Prs, Show_us, Lang, Q) ->
    {User, Server} = WP#webpresence.us,
    BaseURL = get_baseurl(Server),
    US_string = case Show_us of
		    true ->
			?BC([<<"var jabber_user='">>, User, <<"';\n">>, <<"var jabber_server='">>, Server, <<"';\n">>]); false -> <<"">>
		end,
    FunImage = fun(I, S) ->
		       case I of
			   <<"---">> -> <<"">>;
			   Icon -> ?BC([<<" image:'">>, BaseURL, <<"image/">>, Icon, <<"/">>, S, <<"'\n">>])
		       end
	       end,
    R_string_list = case Prs of
			[] ->
			    Show = <<"unavailable">>,
			    [?BC([<<"{show:'">>, Show, <<"',\n">>,
			     <<" long_show:'">>, long_show(Show, Lang), <<"',\n">>,
			     <<" status:'',\n">>, % TODO
			     FunImage(WP#webpresence.icon, Show),
			     <<"}">>])];
			_ -> lists:map(
			       fun(Pr) ->
				       Show =  Pr#presence2.show,
				       ?BC([<<"{name:'">>, Pr#presence2.resource, <<"',\n">>,
					   <<" priority:">>, intund2string(Pr#presence2.priority), <<",\n">>,
					   <<" show:'">>, Show, <<"',\n">>,
					   <<" long_show:'">>, long_show(Show, Lang), <<"',\n">>,
					   <<" status:'">>, escape(Pr#presence2.status), <<"',\n">>,
					   FunImage(WP#webpresence.icon, Show),
					   <<"}">>])
			       end,
			       Prs)
		    end,
    R_string = lists:foldl(
		 fun(RS, Res) ->
			 case Res of
			     <<"">> -> RS;
			     _ -> ?BC([Res, <<",\n">>, RS])
			 end
		 end,
		 <<"">>,
		 R_string_list),
    CB_string = case lists:keysearch(<<"cb">>, 1, Q) of
                    {value, {_, CB}} -> ?BC([<<" ">>, CB, <<"();">>]);
                    _                -> <<"">>
                end,
    ?BC([US_string, <<"var jabber_resources=[\n">>, R_string, <<"];">>, CB_string]).

long_show(<<"available">>, Lang) -> translate:translate(Lang, ?T("available"));
long_show(<<"chat">>, Lang) -> translate:translate(Lang, ?T("free for chat"));
long_show(<<"away">>, Lang) -> translate:translate(Lang, ?T("away"));
long_show(<<"xa">>, Lang) -> translate:translate(Lang, ?T("extended away"));
long_show(<<"dnd">>, Lang) -> translate:translate(Lang, ?T("do not disturb"));
long_show(_, Lang) -> translate:translate(Lang, ?T("unavailable")).

intund2string(undefined) -> intund2string(0);
intund2string(Int) when is_integer(Int) -> list_to_binary(integer_to_list(Int)).

escape(S1) ->
    S2 = re:replace(S1, "\'", "\\'", [global, {return, list}]),
    re:replace(S2, "\n", "\\n", [global, {return, list}]).

get_baseurl(Host) ->
    BaseURL1 = gen_mod:get_module_opt(Host, ?MODULE, baseurl),
    ejabberd_regexp:greplace(BaseURL1, <<"@HOST@">>, Host).

-define(XML_HEADER, <<"<?xml version='1.0' encoding='utf-8'?>">>).

get_pixmaps_directory() ->
    [{directory, Path} | _] = ets:lookup(pixmaps_dirs, directory),
    Path.

available_themes(list) ->
    case file:list_dir(get_pixmaps_directory()) of
        {ok, List} ->
            L2 = lists:sort(List),
	    %% Remove from the list of themes the directories that start with a dot
	    [T || T <- L2, hd(T) =/= 46];
        {error, _} ->
            []
    end.

show_presence({image_no_check, Theme, Pr}) ->
    Dir = get_pixmaps_directory(),
    Image = ?BC([Pr, <<".{gif,png,jpg}">>]),
    [First | _Rest] = filelib:wildcard(binary_to_list(filename:join([Dir, Theme, Image]))),
    Mime = string:substr(First, string:len(First) - 2, 3),
    {ok, Content} = file:read_file(First),
    {200, [{<<"Content-Type">>, ?BC([<<"image/">>, ?BC(Mime)])}], binary_to_list(Content)};

show_presence({image, WP, LUser, LServer}) ->
    Icon = WP#webpresence.icon,
    false = (<<"---">> == Icon),
    Pr = get_presences({show, LUser, LServer}),
    show_presence({image_no_check, Icon, Pr});

show_presence({image, WP, LUser, LServer, Theme}) ->
    false = (<<"---">> == WP#webpresence.icon),
    Pr = get_presences({show, LUser, LServer}),
    show_presence({image_no_check, Theme, Pr});

show_presence({image_res, WP, LUser, LServer, LResource}) ->
    Icon = WP#webpresence.icon,
    false = (<<"---">> == Icon),
    Pr = get_presences({show, LUser, LServer, LResource}),
    show_presence({image_no_check, Icon, Pr});

show_presence({image_res, WP, LUser, LServer, Theme, LResource}) ->
    false = (<<"---">> == WP#webpresence.icon),
    Pr = get_presences({show, LUser, LServer, LResource}),
    show_presence({image_no_check, Theme, Pr});

show_presence({xml, WP, LUser, LServer, Show_us}) ->
    true = WP#webpresence.xml,
    Presence_xml = fxml:element_to_binary(get_presences({xml, LUser, LServer, Show_us})),
    {200, [{"Content-Type", "text/xml; charset=utf-8"}], ?BC([?XML_HEADER, Presence_xml])};

show_presence({js, WP, LUser, LServer, Show_us, Lang, Q}) ->
    true = WP#webpresence.js,
    Prs = get_presences({sorted, LUser, LServer}),
    Js = make_js(WP, Prs, Show_us, Lang, Q),
    {200, [{"Content-Type", "text/html; charset=utf-8"}], Js};

show_presence({text, WP, LUser, LServer}) ->
    true = WP#webpresence.text,
    Presence_text = get_presences({status, LUser, LServer}),
    {200, [{"Content-Type", "text/html; charset=utf-8"}], Presence_text};

show_presence({text, WP, LUser, LServer, LResource}) ->
    true = WP#webpresence.text,
    Presence_text = get_presences({status, LUser, LServer, LResource}),
    {200, [{"Content-Type", "text/html; charset=utf-8"}], Presence_text};

show_presence({avatar, WP, LUser, LServer}) ->
    true = WP#webpresence.avatar,
    [{_, Module, Function}] = ets:lookup(ejabberd_sm, {LServer, ?NS_VCARD}),
    JID = jid:make(LUser, LServer, <<"">>),
    IQ = #iq{type = get, from = JID, to = JID},
    IQr = Module:Function(IQ),
    [VCard] = IQr#iq.sub_els,
    VCard2 = xmpp:decode(VCard),
    Mime = (VCard2#vcard_temp.photo)#vcard_photo.type,
    Photo = (VCard2#vcard_temp.photo)#vcard_photo.binval,
    {200, [{"Content-Type", Mime}], Photo};

show_presence({image_example, Theme, Show}) ->
    Dir = get_pixmaps_directory(),
    Image = ?BC([Show, <<".{gif,png,jpg}">>]),
    [First | _Rest] = filelib:wildcard(binary_to_list(filename:join([Dir, Theme, Image]))),
    Mime = string:substr(First, string:len(First) - 2, 3),
    {ok, Content} = file:read_file(First),
    {200, [{<<"Content-Type">>, ?BC([<<"image/">>, ?BC(Mime)])}], binary_to_list(Content)}.

%% ---------------------
%% Web Publish
%% ---------------------

make_xhtml(Els) -> make_xhtml([], Els).
make_xhtml(Title, Els) ->
    #xmlel{
       name = <<"html">>,
       attrs = [
                {<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>},
                {<<"xml:lang">>, <<"en">>},
                {<<"lang">>, <<"en">>}
               ],
       children = [
                   #xmlel{
                      name = <<"head">>,
                      attrs = [],
                      children = [
                                  #xmlel{
                                     name = <<"meta">>,
                                     attrs = [
                                              {<<"http-equiv">>, <<"Content-Type">>},
                                              {<<"content">>, <<"text/html; charset=utf-8">>}
                                             ],
                                     children = []
                                    }
                                 ] ++ Title
                     },
                   #xmlel{
                      name = <<"body">>,
                      attrs = [],
                      children = Els
                     }
                  ]
      }.

themes_to_xhtml(Themes) ->
    ShowL = ["available", "chat", "dnd", "away", "xa", "unavailable"],
    THeadL = [""] ++ ShowL,
    [?XAE(<<"table">>, [],
          [?XE( <<"tr">>, [?XC(<<"th">>, ?BC(T)) || T <- THeadL])] ++
	  [?XE(<<"tr">>, [?XC(<<"td">>, ?BC(Theme)) |
		      [?XE(<<"td">>, [?XA(<<"img">>, [{<<"src">>, ?BC([<<"image/">>, ?BC(Theme), <<"/">>, ?BC(T)]) }])]) || T <- ShowL]
		     ]
	      ) || Theme <- Themes]
	 )
    ].

parse_lang(Lang) -> iolist_to_binary(hd(string:tokens(binary_to_list(Lang),"-"))).

process(LocalPath, Request) ->
    case catch process2(LocalPath, Request) of
	{'EXIT', Reason} ->
            ?DEBUG("~p", [Request]),
	    ?DEBUG("The call to path ~p in the~nrequest: ~p~ncrashed with error: ~p", [LocalPath, Request, Reason]),
	    {404, [], make_xhtml([?XC(<<"h1">>, <<"Not found">>)])};
	Res ->
	    Res
    end.

process2([], #request{lang = Lang1}) ->
    Lang = parse_lang(Lang1),
    Title = [?XC(<<"title">>, translate:translate(Lang, ?T("Web Presence")))],
    Desc = [?XC(<<"p">>, ?BC([ translate:translate(Lang, ?T("To publish your presence using this system you need a Jabber account in this Jabber server.")), <<" ">>,
		translate:translate(Lang, ?T("Login with a Jabber client, open the Service Discovery and register in Web Presence.")),
		translate:translate(Lang, ?T("You will receive a message with further instructions."))]))],
    Link_themes = [?AC(<<"themes">>, translate:translate(Lang, ?T("Icon Theme")))],
    Body = [?XC(<<"h1">>, translate:translate(Lang, ?T("Web Presence")))] ++ Desc ++ Link_themes,
    make_xhtml(Title, Body);

process2([<<"themes">>], #request{lang = Lang1}) ->
    Lang = parse_lang(Lang1),
    Title = [?XC(<<"title">>, ?BC([translate:translate(Lang, ?T("Web Presence")), <<" - ">>, translate:translate(Lang, ?T("Icon Theme"))]))],
    Themes = available_themes(list),
    Icon_themes = themes_to_xhtml(Themes),
    Body = [?XC(<<"h1">>, translate:translate(Lang, ?T("Icon Theme")))] ++ Icon_themes,
    make_xhtml(Title, Body);

process2([<<"image">>, Theme, Show], #request{} = _Request) ->
    Args = {image_example, Theme, Show},
    show_presence(Args);

process2([<<"jid">>, User, Server | Tail], Request) ->
    serve_web_presence(jid, User, Server, Tail, Request);

process2([<<"rid">>, Rid | Tail], Request) ->
    [Pr] = mnesia:dirty_index_read(webpresence, Rid, #webpresence.ridurl),
    {User, Server} = Pr#webpresence.us,
    serve_web_presence(rid, User, Server, Tail, Request);

%% Compatibility with old mod_presence
process2([User, Server | Tail], Request) ->
    serve_web_presence(jid, User, Server, Tail, Request).

serve_web_presence(TypeURL, User, Server, Tail, #request{lang = Lang1, q = Q}) ->
    LServer = jid:nameprep(Server),
    true = lists:member(Server, ejabberd_config:get_option(hosts)),
    LUser = jid:nodeprep(User),
    WP = get_wp(LUser, LServer),
    case TypeURL of
	jid -> true = WP#webpresence.jidurl;
	rid -> true = is_binary(WP#webpresence.ridurl)
    end,
    Show_us = (TypeURL == jid),
    Lang = parse_lang(Lang1),
    Args = case Tail of
               [<<"image">>, <<"theme">>, Theme, <<"res">>, Resource | _] ->
                   {image_res, WP, LUser, LServer, Theme, Resource};
               [<<"image">>, <<"theme">>, Theme | _] ->
                   {image, WP, LUser, LServer, Theme};
               [<<"image">>, <<"res">>, Resource | _] ->
                   {image_res, WP, LUser, LServer, Resource};
               [<<"image">> | _] ->
                   {image, WP, LUser, LServer};
               [<<"xml">>] ->
                   {xml, WP, LUser, LServer, Show_us};
               [<<"js">>] ->
                   {js, WP, LUser, LServer, Show_us, Lang, Q};
               [<<"text">>] ->
                   {text, WP, LUser, LServer};
               [<<"text">>, <<"res">>, Resource] ->
                   {text, WP, LUser, LServer, Resource};
               [<<"avatar">> | _] ->
                   {avatar, WP, LUser, LServer}
	   end,
    show_presence(Args).


%%%% ---------------------
%%%% Web Admin
%%%% ---------------------

web_menu_system(Result, _Request, _Level) ->
    Els = ejabberd_web_admin:make_menu_system(?MODULE, "👁️", "Presence Web", ""),
    Els ++ Result.

web_menu_host(Acc, _Host, Lang) ->
    [{<<"webpresence">>, translate:translate(Lang, ?T("Presence Web"))} | Acc].

web_page_host(_, _Host,
	      #request{path = [<<"webpresence">>],
		       lang = Lang} = _Request) ->
    Res = [?XCT(<<"h1">>, <<"Web Presence">>),
	   ?XE(<<"ul">>, [
		      ?LI([?ACT(<<"stats/">>, <<"Statistics">>)]),
		      ?LI([?ACT(<<"users/">>, <<"Registered Users">>)])])],
    {stop, Res};

web_page_host(_, Host,
	      #request{path = [<<"webpresence">>, <<"users">>],
		       lang = Lang} = _Request) ->
    Users = get_users(Host),
    Table = make_users_table(Users, Lang),
    Res = [?XCT(<<"h1">>, <<"Web Presence">>),
	   ?XCT(<<"h2">>, <<"Registered Users">>)] ++ Table,
    {stop, Res};

web_page_host(_, Host,
	      #request{path = [<<"webpresence">>, <<"stats">>],
		       lang = Lang} = _Request) ->
    Users = get_users(Host),
    Res = [?XCT(<<"h1">>, <<"Web Presence">>),
	   css_table(),
	   ?XCT(<<"h2">>, <<"Statistics">>)]
	++ make_stats_options(Users, Lang)
	++ make_stats_iconthemes(Users, Lang),
    {stop, Res};

web_page_host(Acc, _, _) -> Acc.

get_users(Host) ->
    Select = [{{webpresence, {'$1', Host}, '$2', '$3', '$4', '$5', '$6', '$7', '$8'}, [], ['$$']}],
    mnesia:dirty_select(webpresence, Select).

make_users_table(Records, Lang) ->
    TList = lists:map(
	      fun([User, RidUrl, JIDUrl, XML, Avatar, JS, Text, Icon]) ->
		      ?XE(<<"tr">>,
			  [?XE(<<"td">>, [?AC(?BC([<<"../../user/">>, User, <<"/">>]), User)]),
			   ?XC(<<"td">>, iolist_to_binary(atom_to_list(JIDUrl))),
			   ?XC(<<"td">>, ridurl_out(RidUrl)),
			   ?XC(<<"td">>, Icon),
			   ?XC(<<"td">>, iolist_to_binary(atom_to_list(XML))),
			   ?XC(<<"td">>, iolist_to_binary(atom_to_list(JS))),
			   ?XC(<<"td">>, iolist_to_binary(atom_to_list(Text))),
			   ?XC(<<"td">>, iolist_to_binary(atom_to_list(Avatar)))])
	      end, Records),
    [?XE(<<"table">>,
	 [?XE(<<"thead">>,
	      [?XE(<<"tr">>,
		   [?XCT(<<"td">>, <<"User">>),
		    ?XCT(<<"td">>, <<"Jabber ID">>),
		    ?XCT(<<"td">>, <<"Random ID">>),
		    ?XCT(<<"td">>, <<"Icon Theme">>),
		    ?XC(<<"td">>, <<"XML">>),
		    ?XC(<<"td">>, <<"JS">>),
		    ?XCT(<<"td">>, <<"Text">>),
		    ?XCT(<<"td">>, <<"Avatar">>)
		   ])]),
	  ?XE(<<"tbody">>, TList)])].

make_stats_options(Records, Lang) ->
    [RegUsers, JJ, RR, XX, AA, SS, TT, II] = lists:foldl(
					       fun([_User, RidUrl, JidUrl, XML, Avatar, JS, Text, Icon], [N, J, R, X, A, S, T, I]) ->
						       J2 = J + case JidUrl of false -> 0; true -> 1 end,
						       R2 = R + case RidUrl of false -> 0; _ -> 1 end,
						       X2 = X + case XML of false -> 0; true -> 1 end,
						       A2 = A + case Avatar of false -> 0; true -> 1 end,
						       S2 = S + case JS of false -> 0; true -> 1 end,
						       T2 = T + case Text of false -> 0; true -> 1 end,
						       I2 = I + case Icon of <<"---">> -> 0; _ -> 1 end,
						       [N+1, J2, R2, X2, A2, S2, T2, I2]
					       end,
					       [0, 0, 0, 0, 0, 0, 0, 0],
					       Records),
    URLTList = [{<<"Jabber ID">>, JJ}, {<<"Random ID">>, RR}],
    OutputTList = [{<<"Icon Theme">>, II}, {<<"XML">>, XX}, {<<"JavaScript">>, SS}, {<<"Text">>, TT}, {<<"Avatar">>, AA}],
    [
     ?C(?BC([<<"Registered Users">>, <<": ">>, iolist_to_binary(integer_to_list(RegUsers))])),
     ?XCT(<<"h3">>, <<"URL Type">>),
     ?XAE(<<"table">>, [{<<"class">>, <<"stats">>}],
	  [?XE(<<"tbody">>, do_stat_table_with(URLTList, RegUsers))]
	 ),
     ?XCT(<<"h3">>, <<"Output Type">>),
     ?XAE(<<"table">>, [{<<"class">>, <<"stats">>}],
	  [?XE(<<"tbody">>, do_stat_table_with(OutputTList, RegUsers))]
	 )].

make_stats_iconthemes(Records, Lang) ->
    Themes1 = [{T, 0} || T <- available_themes(list)],
    Dict = lists:foldl(
	     fun([_, _, _, _, _, _, _, Icon], D) ->
		     dict:update_counter(Icon, 1, D)
	     end,
	     dict:from_list(Themes1),
	     Records),
    Themes = lists:keysort(1, dict:to_list(Dict)),
    [?XCT(<<"h3">>, <<"Icon Theme">>),
     ?XAE(<<"table">>, [{<<"class">>, <<"stats">>}],
	  [?XE(<<"tbody">>, do_stat_table_with(Themes))]
	 )].

%% Do table with bars
do_stat_table_with(Values) ->
    Ns = [Ni || {_, Ni} <- Values],
    Total = lists:sum(Ns),
    do_stat_table_with(Values, Total).

do_stat_table_with(Values, Total) ->
    lists:map(
      fun({L, N}) ->
	      Perc = case Total of
			 0 -> <<"0">>;
			 _ -> iolist_to_binary(integer_to_list(trunc(100 * N / Total)))
		     end,
	      do_table_element(?C(L), io_lib:format("~p", [N]), Perc)
      end,
      Values).
do_table_element(L, [N], Perc) ->
    ?XE(<<"tr">>,
	[?XE(<<"td">>, [L]),
	 ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], ?BC(N)),
	 ?XE(<<"td">>,
	     [?XAE(<<"div">>,
		   [{<<"class">>, <<"graph">>}],
		   [?XAC(<<"div">>,
			 [{<<"class">>, <<"bar">>}, {<<"style">>, ?BC(["width: ", Perc, "%;"])}],
			 <<>>
			)]
		  )]
	    ),
	 ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], ?BC([Perc, "%"]))
	]).

css_table()->
    ?XAE(<<"style">>, [{<<"type">>, <<"text/css">>}],
	 [?C(<<".stats {
             padding-left: 20px;
	     padding-top: 10px;
	    }
	  .graph {
	     position: relative;
	     width: 200px;
	     border: 1px solid #D47911;
	     padding: 1px;
	    }
	  .graph .bar {
		    display: block;
		    position: relative;
		    background: #FFE3C9;
		    text-align: center;
		    color: #333;
		    height: 1.5em;
		    line-height: 1.5em;
		   }
	  .graph .bar span { position: absolute; left: 1em; }">>)]).


%%%--------------------------------
%%% Update table schema and content from older versions
%%%--------------------------------

update_table() ->
		 case catch mnesia:table_info(presence_registered, size) of
		     Size when is_integer(Size) -> catch migrate_data_mod_presence(Size);
		     _ -> ok
		 end.

migrate_data_mod_presence(Size) ->
    Migrate = fun(Old, S) ->
		      {presence_registered, {US, _Host}, XML, Icon} = Old,
		      New = #webpresence{us = US,
					 ridurl = false,
					 jidurl = true,
					 xml = list_to_atom(XML),
					 avatar = false,
					 js = false,
					 text = false,
					 icon = Icon},
		      mnesia:write(New),
		      mnesia:delete_object(Old),
		      S-1
	      end,
    F = fun() -> mnesia:foldl(Migrate, Size, presence_registered) end,
    {atomic, 0} = mnesia:transaction(F),
    {atomic, ok} = mnesia:delete_table(presence_registered).
