%%%-------------------------------------------------------------------
%%% @doc    跨服军衔排行榜
%%% Module  : mod_military_rank_mgr.erl
%%% Created : 2021-02-07
%%% @Author : tyl
%%%-------------------------------------------------------------------
-module(mod_military_rank_mgr).

-include("kf_legion.hrl").
-include("common.hrl").
-include("server.hrl").
-include("figure.hrl").
-include("role.hrl").
-include("errcode.hrl").
-include("goods.hrl").
-include("czone.hrl").
-include("rec_event.hrl").
-include("def_event.hrl").
-include("predefine.hrl").

-behaviour(gen_server).

%% API
-export([
	update_military_pos/4           %% 更新军衔
	,handle_event/2
	,update_role/4                  %% 玩家数据更新
	,get_military_rank_info/3       %% 获取军衔排行榜数据
	,check_in_center/0
	,get_role_info/3

	%% 重新分区后数据处理
	,recalc_zone/1
	,node_update_military_msg/0
	,update_military_after_merge/1      %% 合服后更新所有服军衔数据
	,update_military_after_rezone/1     %% 重新分区后更新所有军衔数据
]).

-export([start_link/0, cast/1 ,call/1 ,info/1]).

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

%% 军衔排行长度
-define(MILITARY_RANK_LAST, data_legion:get(military_max_rank)).
%% 来取数据的最近登录时间
-define(MILITARY_LAST_LOGIN, data_legion:get(limit_day)*86400).

%% 玩家形象更新类型
-define(UPDATE_NAME,    1).
-define(UPDATE_VIP,     2).
-define(UPDATE_SUP_VIP, 3).

-record(military_rank_state, {
	rank_map = #{}        %% 各个分区军衔排行,只保留前50 #{zone->ranks}
}).

-record(military_rank, {
	rank     = 0        %% 排行
	,role_id = 0        %% 角色Id
	,pos     = 0        %% 军衔
	,zone_id = 0        %% 分区
	,faction = 0        %% 阵型
	,name    = ""
	,sex     = 0
	,vip     = 0
	,sup_vip = 0
	,server_num = 0
	,time    = 0        %% 更新时间
}).

-record(up_per_legion_info, {
	role_id     = 0
	,role_name  = ""
	,sex        = 0
	,vip        = 0
	,sup_vip    = 0
	,server_num = 0
	,faction    = 0     %% 阵型(国家)
}).

%% 超级贵族
handle_event(Player = #player_status{}, #event_callback{type_id = ?EVENT_SUP_VIP_CHANGE}) ->
	#player_status{id = RoleId, figure = Figure, role_legion = RoleLegion} = Player,
	#role_legion{post = Post} = RoleLegion,
	#figure{sup_vip = SupVip} = Figure,
	case czone_api:env_get_zone() of
		#czone_svr_local{
			zone_id = ZoneId
		} -> ok;
		_ -> ZoneId = 0
	end,
	Post > 0 andalso clusters_node:apply_cast(mod_military_rank_mgr, update_role,
		[ZoneId, RoleId, ?UPDATE_SUP_VIP, SupVip]),
	{ok, Player};
%% vip等级
handle_event(Player = #player_status{}, #event_callback{type_id = ?EVENT_VIP}) ->
	#player_status{id = RoleId, figure = Figure, role_legion = RoleLegion} = Player,
	#role_legion{post = Post} = RoleLegion,
	#figure{vip = Vip} = Figure,
	case czone_api:env_get_zone() of
		#czone_svr_local{
			zone_id = ZoneId
		} -> ok;
		_ -> ZoneId = 0
	end,
	Post > 0 andalso clusters_node:apply_cast(mod_military_rank_mgr, update_role,
		[ZoneId, RoleId, ?UPDATE_VIP, Vip]),
	{ok, Player};
%% 名字
handle_event(Player = #player_status{}, #event_callback{type_id = ?EVENT_RENAME}) ->
	#player_status{id = RoleId, figure = Figure, role_legion = RoleLegion} = Player,
	#role_legion{post = Post} = RoleLegion,
	#figure{name = Name} = Figure,
	case czone_api:env_get_zone() of
		#czone_svr_local{
			zone_id = ZoneId
		} -> ok;
		_ -> ZoneId = 0
	end,
	Post > 0 andalso clusters_node:apply_cast(mod_military_rank_mgr, update_role,
		[ZoneId, RoleId, ?UPDATE_NAME, Name]),
	{ok, Player};
%% 登录后更新数据到榜单
handle_event(Player = #player_status{}, #event_callback{type_id = ?EVENT_CLIENT_LOGIN_SUCCESS}) ->
	#player_status{id = RoleId, figure = Figure, role_legion = RoleLegion, server_num = ServerNum} = Player,
	#role_legion{post = Post, post_utime = UTime} = RoleLegion,
	{ZoneId, RoleInfo} = mod_military_rank_mgr:get_role_info(RoleId, Figure, ServerNum),
	%% 随机一个时间延时，避免同时登陆人数过多
	Post > 0 andalso spawn(fun() -> timer:sleep(urand:rand(1000,10000)),
		mod_military_rank_local:update_military_pos(RoleId, ZoneId, RoleInfo, Post, UTime) end),
	{ok, Player};
handle_event(Player, _Event) ->
	{ok, Player}.

%% 更新军衔
update_military_pos(ZoneId, RoleInfo, Pos, Time) ->
	cast({update_military_pos, ZoneId, RoleInfo, Pos, Time}).

%% 获取军衔排行数据
get_military_rank_info(ZoneId, Node, RoleId) ->
	cast({get_military_rank_info, ZoneId, Node, RoleId}).

%% 更新玩家形象数据
update_role(ZoneId, RoleId, Type, Value) ->
	cast({update_role, ZoneId, RoleId, Type, Value}).

%% 重新分区
recalc_zone(CBMap) ->
	cast({recalc_zone, CBMap}).

%% 合服后数据上报 todo
update_military_after_merge(NodeMsg) ->
	cast({update_military_after_merge, NodeMsg}).

%% 重新分区后数据上报
update_military_after_rezone(NodeMsg) ->
	cast({update_military_after_rezone, NodeMsg}).

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

cast(Msg) ->
	gen_server:cast(?MODULE, Msg).

call(Msg) ->
	gen_server:call(?MODULE, Msg).

info(Msg) ->
	erlang:send(?MODULE, Msg).

init([]) ->
	process_flag(trap_exit, true),
	%% 初始化数据(延时) todo:延时并考虑分区变更因素
	erlang:send_after(3*1000, self(), {init_military_rank}),
	State = #military_rank_state{},
	{ok, State}.

handle_call(Msg, From, State) ->
	try do_handle_call(Msg, From, State) of
		Reply ->
			Reply
	catch ?EXCEPTION(Class, Reason, StackTrace) ->
		Error = {Class, Reason, StackTrace},
		?ERR("~w:handle_call error:~p~n", [?MODULE, Error]),
		{error, Reason}
	end.

handle_cast(Msg, State) ->
	try do_handle_cast(Msg, State) of
		Result ->
			Result
	catch ?EXCEPTION(Class, Reason, StackTrace) ->
		Error = {Class, Reason, StackTrace},
		?ERR("~w:handle_cast error:~p~n", [?MODULE, Error]),
		{noreply, State}
	end.

handle_info(Info, State) ->
	try do_handle_info(Info, State) of
		Result ->
			Result
	catch ?EXCEPTION(Class, Reason, StackTrace) ->
		Error = {Class, Reason, StackTrace},
		?ERR("~w:handle_info error:~p~n", [?MODULE, Error]),
		{noreply, State}
	end.

terminate(_Reason, _State) ->
	ok.

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

%% ---------------------------------------------------------------------------
do_handle_call(_Msg, _From, State) ->
	{reply, ok, State}.

%% ---------------------------------------------------------------------------
do_handle_cast({recalc_zone, CBMap}, State) ->
	#{
		svr_zone_map := SvrZoneMap,
		stage := _Stage
	} = CBMap,
	%% todo:清空数据库表
	db:execute(<<"truncate table legion_military_rank">>),
	%% 各个分区服分批上报
	ZoneMap = to_zone_map(SvrZoneMap),
	UpF = fun(Zone, ServerList) ->
		SleepTime = Zone rem 10,
		ForF = fun(ServerId) ->
			clusters_center:apply_cast(ServerId, {?MODULE, node_update_military_msg, []})
		end,
		spawn(fun() -> timer:sleep(SleepTime*1000), lists:map(ForF, ServerList) end)
	end,
	maps:map(UpF, ZoneMap),
	{noreply, State#military_rank_state{rank_map = #{}}};

%% 各个游戏服的数据上报，单服统一整理赋值后再排序
do_handle_cast({update_military_after_rezone, NodeMsg}, State) ->
	#military_rank_state{rank_map = OldRankMap} = State,
	F = fun({ZoneId, RoleInfo, UpPos, Time}, RankMap) ->
		RankList = maps:get(ZoneId, RankMap, []),
		#up_per_legion_info{role_id = RoleId} = RoleInfo,
		MilitaryRank = tran_to_military_rank(ZoneId, RoleInfo, UpPos, Time),
		#up_per_legion_info{faction = Faction, server_num = ServerNum,
			role_name = RoleName, sex = Sex, vip = Vip, sup_vip = SupVip} = RoleInfo,
		db_save_military_rank(RoleId, ZoneId, Faction, ServerNum, RoleName,
			Sex, UpPos, Vip, SupVip, Time),
		RankList1 = lists:keystore(RoleId, #military_rank.role_id, RankList,
			MilitaryRank#military_rank{pos = UpPos, time = Time}),
		maps:put(ZoneId, RankList1, RankMap)
	end,
	NewRankMap = lists:foldl(F, OldRankMap, NodeMsg),
	SortFun = fun(TZoneId, TRankList, TRankMap) ->
		SortRankList = sort_rank_list(TRankList),
		maps:put(TZoneId, SortRankList, TRankMap)
	end,
	SortRankMap = maps:fold(SortFun, NewRankMap, NewRankMap),
	NewState = State#military_rank_state{rank_map = SortRankMap},
	{noreply, NewState};

do_handle_cast({update_military_pos, ZoneId, RoleInfo, UpPos, Time}, State) ->
	#military_rank_state{rank_map = RankMap} = State,
	RankList = maps:get(ZoneId, RankMap, []),
	%% 跟排行榜最后一名比较
	case lists:keyfind(?MILITARY_RANK_LAST, #military_rank.rank, RankList) of
		#military_rank{pos = Pos} -> ok;
		_ -> Pos = 0
	end,
	case UpPos > Pos of
		true ->
			#up_per_legion_info{role_id = RoleId} = RoleInfo,
			case lists:keyfind(RoleId, #military_rank.role_id, RankList) of
				MilitaryRank when is_record(MilitaryRank, military_rank) ->
					db_update_military_rank(UpPos, Time, RoleId),
					RankList1 = lists:keystore(RoleId, #military_rank.role_id, RankList,
						MilitaryRank#military_rank{pos = UpPos, time = Time});
				_ ->
					MilitaryRank = tran_to_military_rank(ZoneId, RoleInfo, UpPos, Time),
					#up_per_legion_info{faction = Faction, server_num = ServerNum,
						role_name = RoleName, sex = Sex, vip = Vip, sup_vip = SupVip} = RoleInfo,
					db_save_military_rank(RoleId, ZoneId, Faction, ServerNum, RoleName,
						Sex, UpPos, Vip, SupVip, Time),
					RankList1 = lists:keystore(RoleId, #military_rank.role_id, RankList,
						MilitaryRank#military_rank{pos = UpPos, time = Time})
			end,
			SortRankList = sort_rank_list(RankList1),
			NewRankMap   = maps:put(ZoneId, SortRankList, RankMap),
			NewState = #military_rank_state{rank_map = NewRankMap};
		_ ->
			NewState = State
	end,
	{noreply, NewState};

do_handle_cast({get_military_rank_info, ZoneId, Node, RoleId}, State) ->
	#military_rank_state{rank_map = RankMap} = State,
	RankList = maps:get(ZoneId, RankMap, []),
	case lists:keyfind(RoleId, #military_rank.role_id, RankList) of
		#military_rank{rank = MyRank} -> ok;
		_ -> MyRank = 0
	end,
	PackList = pack_rank_list(RankList, []),
	{ok, BinData} = pt_603:write(60313, [PackList, MyRank]),
	lib_server_send:send_to_uid(Node, RoleId, BinData),
	{noreply, State};

do_handle_cast({update_role, ZoneId, RoleId, Type, Value}, State) ->
	#military_rank_state{rank_map = RankMap} = State,
	RankList = maps:get(ZoneId, RankMap, []),
	NewRankMap =
	case lists:keyfind(RoleId, #military_rank.role_id, RankList) of
		MilitaryRank = #military_rank{faction = Faction, server_num = ServerNum,
			name = RoleName, sex = Sex, pos = UpPos, time = Time, vip = Vip, sup_vip = SupVip} ->
			case Type of
				?UPDATE_NAME -> NewName = Value, NewVip = Vip, NewSupVip = SupVip;
				?UPDATE_VIP when is_integer(Value) -> NewName = RoleName, NewVip = Value, NewSupVip = SupVip;
				?UPDATE_SUP_VIP when is_integer(Value) -> NewName = RoleName, NewVip = Vip, NewSupVip = Value;
				_ -> NewName = RoleName, NewVip = Vip, NewSupVip = SupVip
			end,
			db_save_military_rank(RoleId, ZoneId, Faction, ServerNum, NewName,
				Sex, UpPos, NewVip, NewSupVip, Time),
			RankList1 = lists:keystore(RoleId, #military_rank.role_id, RankList,
				MilitaryRank#military_rank{name = NewName, vip = NewVip, sup_vip = NewSupVip}),
			maps:put(ZoneId, RankList1, RankMap);
		_ ->
			RankMap
	end,
	{noreply, State#military_rank_state{rank_map = NewRankMap}};

do_handle_cast(_Msg, State) ->
	?ERR("Handle unknow msg[~w]~n", [_Msg]),
	{noreply, State}.

%% ---------------------------------------------------------------------------
do_handle_info({init_military_rank}, State) ->
	case db_select_military_rank() of
		[_|_] = Info ->
			RankMap = init_rank_map(Info, #{});
		_ ->
			RankMap = #{}
	end,
	NewState = State#military_rank_state{rank_map = RankMap},
	{noreply, NewState};

do_handle_info(_Info, State) ->
	?ERR("Handle unknow msg[~w]~n", [_Info]),
	{noreply, State}.

%% ------------------------------------------------------------------
%% @doc 游戏服拉取数据库中过去3天上线的玩家的军衔值，上报到跨服分区
%%      role_id     = RoleId
%%		,role_name  = RoleName
%%		,sex        = Sex
%%		,vip        = Vip
%%		,sup_vip    = SupVip
%%		,server_num = ServerNum
%%		,faction    = Faction

%% todo:因为数据量比较大联表查询sql必须经过优化(最好使用right join)
node_update_military_msg() ->
	spawn(fun() -> node_update_military_msg_core() end).

node_update_military_msg_core() ->
	FilterTime = utime:unixtime() - ?MILITARY_LAST_LOGIN,
	Sql = io_lib:format(<<"select r.rid, r.post, r.p_utime from role_legion as r"
	" right join player_login as p on r.rid = p.id"
	" where p.last_login_time >= ~p">>, [FilterTime]),
	case db:get_all(Sql) of
		[_|_] = DbInfo ->
			F = fun([RoleId, Pos, UTime], TList) when is_integer(RoleId), Pos > 0 ->
				[RoleName, Sex|_] = lib_player:get_player_low_data(RoleId),
				Vip = lib_vip_util:get_vip_lv_db(RoleId),
				SupVip = lib_sup_vip:get_sup_vip_on_db(RoleId),
				ServerNum = config:get_server_num(),
				case czone_api:env_get_zone() of
					#czone_svr_local{
						faction  = Faction
						,zone_id = ZoneId
					} -> ok;
					_ -> Faction = 0, ZoneId = 0
				end,
				RoleInfo = #up_per_legion_info{
					role_id     = RoleId
					,role_name  = RoleName
					,sex        = Sex
					,vip        = Vip
					,sup_vip    = SupVip
					,server_num = ServerNum
					,faction    = Faction
				},
				[{ZoneId, RoleInfo, Pos, UTime}|TList];
			(_, TList) ->
					TList
			end,
			NodeInfos = lists:foldl(F, [], DbInfo),
			RoleList = [TRoleId||[TRoleId, _, _]<-DbInfo],
			mod_military_rank_local:update_role_list(RoleList),
			clusters_node:apply_cast(?MODULE, update_military_after_rezone, [NodeInfos]);
		_ ->
			ignore
	end.

%% ---------------------- builtin function -------------------------------------------
init_rank_map([], RankMap) ->
	SortF = fun(ZoneId, RankList, TRankMap) ->
		SortRankList = sort_rank_list(RankList),
		maps:put(ZoneId, SortRankList, TRankMap)
	end,
	NewRankMap = maps:fold(SortF, RankMap, RankMap),
	NewRankMap;
init_rank_map([[RoleId, ZoneId, Faction, ServerNum, RoleName, Sex, UpPos, Vip, SupVip, Time]|TList], RankMap) ->
	MilitaryRank = #military_rank{
		role_id  = RoleId
		,pos     = UpPos
		,zone_id = ZoneId
		,faction = Faction
		,name    = RoleName
		,sex     = Sex
		,vip     = Vip
		,sup_vip = SupVip
		,server_num = ServerNum
		,time       = Time
	},
	RankList = maps:get(ZoneId, RankMap, []),
	NewRankList = lists:keystore(RoleId, #military_rank.role_id, RankList, MilitaryRank),
	NewRankMap = maps:put(ZoneId, NewRankList, RankMap),
	init_rank_map(TList, NewRankMap).

to_zone_map(SvrZoneMap) ->
	F = fun(Svr, Zone, TMap) ->
		case maps:get(Zone, TMap, []) of
			[_|_] = SvrList ->
				maps:put(Zone, [Svr|SvrList], TMap);
			_ ->
				maps:put(Zone, [Svr], TMap)
		end end,
	maps:fold(F, #{}, SvrZoneMap).

sort_rank_list(RankList) ->
	SortF = fun(RankA, RankB) ->
		#military_rank{pos = PosA, time = TimeA} = RankA,
		#military_rank{pos = PosB, time = TimeB} = RankB,
		if
			PosA == PosB ->
				case TimeA =< TimeB of
					true -> true;
					_    -> false
				end;
			PosA > PosB ->
				true;
			true ->
				false
		end end,
	SortList = lists:sort(SortF, RankList),
	RankF = fun(RankInfo, {TRankList, Rank})  ->
					case Rank =< ?MILITARY_RANK_LAST of
						true ->
							NewRankInfo = RankInfo#military_rank{rank = Rank},
							RankList1 = [NewRankInfo|TRankList],
							{RankList1, Rank + 1};
						_ ->
							{TRankList, Rank}
					end end,
	{NewRankList, _} = lists:foldl(RankF, {[], 1}, SortList),
	lists:reverse(NewRankList).

tran_to_military_rank(ZoneId, RoleInfo, UpPos, Time) ->
	#up_per_legion_info{
		role_id     = RoleId
		,role_name  = RoleName
		,sex        = Sex
		,vip        = Vip
		,sup_vip    = SupVip
		,server_num = ServerNum
		,faction    = Faction
	} = RoleInfo,
	MilitaryRank = #military_rank{
		role_id  = RoleId
		,pos     = UpPos
		,zone_id = ZoneId
		,faction = Faction
		,name    = RoleName
		,sex     = Sex
		,vip     = Vip
		,sup_vip = SupVip
		,server_num = ServerNum
		,time       = Time
	},
	MilitaryRank.

pack_rank_list([], PackList) -> PackList;
pack_rank_list([MilitaryRank|TList], PackList) ->
	#military_rank{
		rank = Rank
		,role_id = RoleId
		,vip     = Vip
		,sup_vip = SupVip
		,name    = Name
		,sex     = Sex
		,pos     = Pos
		,faction = Faction
		,server_num = ServerNum
	} = MilitaryRank,
	PackInfo = {Rank, RoleId, Vip, SupVip, ServerNum, Name, Sex, Faction, Pos},
	pack_rank_list(TList, [PackInfo|PackList]).

%% 本服调用,检查是否已经进入跨服中心
check_in_center() ->
	case czone_api:zone_open_check() of
		{true, _ZoneId} -> true;
		_ -> false
	end.

%% 获取玩家数据
get_role_info(RoleId, Figure, ServerNum) ->
	#figure{
		name = RoleName
		,sex = Sex
		,vip = Vip
		,sup_vip = SupVip
	} = Figure,
	case czone_api:env_get_zone() of
		#czone_svr_local{
			faction = Faction
			,zone_id = ZoneId
		} -> ok;
		_ -> Faction = 0, ZoneId = 0
	end,
	RoleInfo = #up_per_legion_info{
		role_id     = RoleId
		,role_name  = RoleName
		,sex        = Sex
		,vip        = Vip
		,sup_vip    = SupVip
		,server_num = ServerNum
		,faction    = Faction
	},
	{ZoneId, RoleInfo}.

%% ---------------------- db操作 -------------------------------------------
db_save_military_rank(RoleId, ZoneId, Faction, ServerNum, RoleName, Sex, UpPos, Vip, SupVip, Time) ->
	db:execute(io_lib:format(<<"replace into legion_military_rank (`role_id`, `zone_id`, `faction`, `server_num`, `name`, `sex`, `pos`, `vip`, `sup_vip`, `time`) values (~p,~p,~p,~p,'~ts',~p,~p,~p,~p,~p)">>,
		[RoleId, ZoneId, Faction, ServerNum, RoleName, Sex, UpPos, Vip, SupVip, Time])),
	ok.

db_update_military_rank(UpPos, Time, RoleId) ->
	db:execute(io_lib:format(<<"update legion_military_rank set `pos`=~p, `time`=~p where `role_id`=~p">>,[UpPos, Time, RoleId])),
	ok.

db_select_military_rank() ->
	db:get_all(<<"select * from legion_military_rank">>).
