%%%-------------------------------------------------------------------
%%% @doc    宠物-跨服战力榜管理进程(同时兼顾点赞功能)
%%% Module  : mod_pet_support_center.erl
%%% Created : 2021-05-12
%%% @Author : tyl
%%%-------------------------------------------------------------------
-module(mod_pet_support_center).
-behaviour(gen_server).

-include("common.hrl").
-include("server.hrl").
-include("figure.hrl").
-include("role.hrl").
-include("pet.hrl").
-include("errcode.hrl").
-include("def_module.hrl").
-include("predefine.hrl").
-include("def_goods.hrl").
-include("goods.hrl").
-include("clusters.hrl").


-export([
	start_link/0
	,center_send_yest_power_rank/3            %% 查询分区昨日战力前八名宠物
	,center_receive_rank_reward/4             %% 领取昨日战力排行奖励
	,center_vote_for_pet/4                    %% 宠物投票

	,upload_pet_info_to_center/2              %% 游戏服上报宠物战力数据
	,midnight_tidy_up_rank/0                  %% 4点02分整理tmp_rank_info_map

	,node_connect/1                           %% 游戏服连接上跨服后更新榜单长度到游戏服
	,recalc_zone/1                            %% 合区处理
	,handle_merge/3                           %% 合服处理

	,db_delete_zone_rank/1
]).

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

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

%% API
%% 查询全服昨日投票前八名宠物
center_send_yest_power_rank(ZoneId, Node, RoleId) ->
	cast({center_send_yest_power_rank, ZoneId, Node, RoleId}).

%% 领取昨日投票排行奖励
center_receive_rank_reward(ZoneId, Node, RoleId, PetId) ->
	cast({center_receive_rank_reward, ZoneId, Node, RoleId, PetId}).

%% 宠物投票
center_vote_for_pet(ZoneId, Node, RoleId, PetId) ->
	cast({center_vote_for_pet, ZoneId, Node, RoleId, PetId}).

%% 游戏服上传宠物战力榜信息到跨服
upload_pet_info_to_center(ZoneId, UpInfoList) ->
	cast({upload_pet_info_to_center, ZoneId, UpInfoList}).

%% 每小时02分整理tmp_rank_info_map
midnight_tidy_up_rank() ->
	cast({midnight_tidy_up_rank}).

%% 游戏服连接上跨服后更新游戏服中 跨服榜单长度
node_connect(Node) ->
	cast({node_connect, Node}).

%% 合区
recalc_zone(CBMap) ->
	#{zone_map := ZoneMap, svr_zone_map := NewSvrZoneMap, stage := Stage} = CBMap,
	cast({recalc_zone, ZoneMap, NewSvrZoneMap, Stage}).

%% 合服(暂时不使用)
handle_merge(NowServerId, NowServerNum, MergeServers) ->
	cast({handle_merge, NowServerId, NowServerNum, MergeServers}).

%% ---------------------------------------------------------------------------
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),
	{ok, NewState} = init_pet_power_mgr(#pet_power_rank_center_state{}),
	{ok, NewState}.

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}.

%% ---------------------------------------------------------------------------
%% @doc
%% 关于重新分区的数据处理
%%  1>第一次分区时，4点结算昨日游戏服的排行，9点分区。此时应该给玩家推送游戏服数据
%%  2>第二次分区时，此前已经在跨服中有数据，直接修正分区Id即可
%% @end
%% ---------------------------------------------------------------------------
%% 查询昨日分区战力前八位
do_handle_cast({center_send_yest_power_rank, ZoneId, Node, RoleId}, State) ->
	#pet_power_rank_center_state{rezone_info_map = RezoneMap} = State,
	Stage = get_new_stage(ZoneId, RezoneMap),
	case Stage of
		1 ->
			clusters_center:apply_cast(Node, mod_pet_support_mgr, send_server_yest_power_rank, [RoleId]);
		_ ->
			lib_pet_support_mod:center_send_yest_power_rank(State, ZoneId, Node, RoleId)
	end,
	{noreply, State};

%% 领取昨日战力排行奖励
do_handle_cast({center_receive_rank_reward, ZoneId, Node, RoleId, PetId}, State) ->
	#pet_power_rank_center_state{rezone_info_map = RezoneMap} = State,
	Stage = get_new_stage(ZoneId, RezoneMap),
	case Stage of
		1 ->
			clusters_center:apply_cast(Node, mod_pet_support_mgr, receive_rank_reward, [RoleId, PetId]);
		_ ->
			lib_pet_support_mod:center_receive_rank_reward(State, ZoneId, Node, RoleId, PetId)
	end,
	{noreply, State};

%% 点赞(只有在前八的宠物才可以点赞)
do_handle_cast({center_vote_for_pet, ZoneId, Node, RoleId, PetId}, State) ->
	#pet_power_rank_center_state{rezone_info_map = RezoneMap} = State,
	Stage = get_new_stage(ZoneId, RezoneMap),
	case Stage of
		1 ->
			clusters_center:apply_cast(Node, mod_pet_support_mgr, server_vote_for_pet, [RoleId, PetId]);
		_ ->
			lib_pet_support_mod:center_vote_for_pet(State, ZoneId, Node, RoleId, PetId)
	end,
	{noreply, State};

%% 游戏服上传宠物排行信息到跨服
do_handle_cast({upload_pet_info_to_center, ZoneId, UpInfoList}, State) ->
	#pet_power_rank_center_state{tmp_up_servers = UpSrvMap} = State,
	ServerList = maps:get(ZoneId, UpSrvMap, []),
	case UpInfoList of
		[#up_pet_info{server_id = ServerId}|_] -> ok;
		_ -> ServerId = 0
	end,
	case lists:member(ServerId, ServerList) of
		true ->
			NewState = State;
		_ ->
			State1 = lib_pet_support_mod:upload_pet_info_to_center(State, ZoneId, UpInfoList),
			NewServerList = [ServerId|ServerList],
			NewUpSrvMap   = maps:put(ZoneId, NewServerList, UpSrvMap),
			NewState = State1#pet_power_rank_center_state{tmp_up_servers = NewUpSrvMap}
	end,
	{noreply, NewState};

%% 4点02分整理tmp_rank_info_map
do_handle_cast({midnight_tidy_up_rank}, State0) ->
	%% 先清理重新分区的旧分区数据
	State = clean_with_rezone_info(State0),
	#pet_power_rank_center_state{
		tmp_rank_info_map   = TmpRankInfoMap
		,yest_rank_info_map = YestRankInfoMap
	} = State,
	%% 如果是4点赋值给昨日榜单，更新昨日榜单
	%% 这里采用替换的形式而不是直接整个覆盖，避免tmp上报的数据为空的情况发生
	NowTime = utime:unixtime(),
	{{_Y, _M, _D}, {H, _N, _S}} = utime:unixtime_to_localtime(NowTime),
	case H of
		4 ->
			batch_save_power_rank(TmpRankInfoMap),
			F = fun(ZoneId, RankInfoList, TMap) ->
				SortList = lib_pet_support_mod:sort_rank_list(RankInfoList),
				lib_pet_support_mod:center_send_rank_reward(SortList),
				maps:put(ZoneId, SortList, TMap)
				end,
			NewRankInfoMap = maps:fold(F, YestRankInfoMap, TmpRankInfoMap),
			%% 更新各个服榜单长度
			midnight_update_center_rank_len(NewRankInfoMap),
			NewState = State#pet_power_rank_center_state{
				rank_info_map = NewRankInfoMap, yest_rank_info_map = NewRankInfoMap,
				tmp_rank_info_map = #{}, tmp_up_servers = #{}
			};
		_ ->
			F = fun(ZoneId, RankInfoList, TMap) ->
				maps:put(ZoneId, RankInfoList, TMap)
			    end,
			NewRankInfoMap = maps:fold(F, YestRankInfoMap, TmpRankInfoMap),
			NewState = State#pet_power_rank_center_state{
				rank_info_map = NewRankInfoMap, tmp_rank_info_map = #{},
				tmp_up_servers = #{}
			}
	end,
	{noreply, NewState};

%% 游戏服连接上跨服后更新游戏服中 跨服榜单长度
do_handle_cast({node_connect, Node}, State) ->
	#pet_power_rank_center_state{
		yest_rank_info_map = YestRankInfoMap
	} = State,
	F = fun(ZoneId, RankList, TMap) ->
		maps:put(ZoneId, erlang:length(RankList), TMap)
		end,
	LenMap = maps:fold(F, #{}, YestRankInfoMap),
	%% todo: 延迟30s更新
	spawn(fun() ->
		timer:sleep(30*1000),
		clusters_center:apply_cast(
			Node, mod_pet_support_mgr, set_center_rank_len, [LenMap]
		)
		end
	),
	{noreply, State};

%% 合区(被合并分区数据需要清理)
do_handle_cast({recalc_zone, ZoneMap, _NewSvrZoneMap, Stage}, State) ->
	% NewZones = lists:usort(maps:values(ZoneMap)),
	% ServerIds = maps:fold(fun(SrvId, ZoneId, TList) ->
	% 	case lists:member(ZoneId, NewZones) of
	% 		true -> [SrvId|lists:delete(SrvId, TList)];
	% 		_ -> TList
	% 	end end, [], NewSvrZoneMap),
	% lists:map(fun(ServerId) ->
	% 	clusters_center:apply_cast(ServerId, mod_pet_support_mgr, hour_upload_pet_info, [])
	% 	end, ServerIds),
	#pet_power_rank_center_state{
		rezone_info_map    = OldRezoneMap
	} = State,
	%% 先记录分区数据，4点再清理。如果遇到重启情况，部分分区数据会保留在数据库中。
	NewRezoneMap = trans_to_rezone_info(ZoneMap, Stage, OldRezoneMap),
	NewState = State#pet_power_rank_center_state{
		rezone_info_map = NewRezoneMap
	},
	{noreply, NewState};

do_handle_cast({handle_merge, NowServerId, _NowServerNum, MergeServers}, State) ->
	ServerIds = [NowServerId|lists:delete(NowServerId, MergeServers)],
	lists:map(fun(ServerId) ->
		clusters_center:apply_cast(ServerId, mod_pet_support_mgr, hour_upload_pet_info, [])
	          end, ServerIds),
	{noreply, State};

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

%% ---------------------------------------------------------------------------
do_handle_info(_Info, State) ->
	{noreply, State}.

%% -------------------------- builtin function -------------------------------------
%% 初始化宠物战力榜单
init_pet_power_mgr(State) ->
	Sql = io_lib:format(?SQL_SELECT_PET_POWER_CENTER_RANK, []),
	case db:get_all(Sql) of
		[_|_] = PowerRankInfos -> ok;
		_ -> PowerRankInfos = []
	end,
	%% 昨日榜单
	PowerRankMap = trans_to_power_rank_record(PowerRankInfos, #{}),
	NewState = State#pet_power_rank_center_state{
		yest_rank_info_map = PowerRankMap
	},
	{ok, NewState}.

trans_to_power_rank_record([], RecordMap) ->
	%% 排序
	F = fun(ZoneId, RecordList, TMap) ->
		NewRecordList = lib_pet_support_mod:sort_rank_list(RecordList),
		maps:put(ZoneId, NewRecordList, TMap)
		end,
	NewRecordMap = maps:fold(F, RecordMap, RecordMap),
	NewRecordMap;
trans_to_power_rank_record([SupportRankInfo|L], RecordMap) ->
	case SupportRankInfo of
		[
			ZoneId, PetId, RoleId, Sex, RoleName, ServerNum,
			Vip, SupVip, DressId, PhotoId, SumPower
		] ->
			RecordList = maps:get(ZoneId, RecordMap, []),
			PetRankInfo = #pet_power_rank_info{
				pet_id        = PetId,
				player_id     = RoleId,
				sex           = Sex,
				player_name   = RoleName,
				server_num    = ServerNum,
				vip           = Vip,
				sup_vip       = SupVip,
				dress_id      = DressId,
				photo_id      = PhotoId,
				sum_power     = SumPower
			},
			NewRecordList = [PetRankInfo|RecordList],
			NewRecordMap = maps:put(ZoneId, NewRecordList, RecordMap),
			trans_to_power_rank_record(L, NewRecordMap);
		_ ->
			trans_to_power_rank_record(L, RecordMap)
	end.

%% 分区数据转换成临时存储格式
trans_to_rezone_info(ZoneMap, Stage, OldMap) ->
	F = fun(OldZone, NewZone, TMap) ->
		case is_integer(OldZone)
			andalso is_integer(NewZone)
			andalso is_integer(Stage)
		of
			true -> maps:put(OldZone, [Stage, NewZone], TMap);
			_ ->
				TMap
		end end,
	maps:fold(F, OldMap, ZoneMap).

%% 根据重新分区数据获取 分区阶段
get_new_stage(ZoneId, RezoneMap) ->
	F = fun(_OldZone, Info, TStage) ->
		case Info of
			[Stage, ZoneId] -> Stage;
			_ -> TStage
		end end,
	case maps:fold(F, 0, RezoneMap) of
		NewStage when NewStage > 0 -> NewStage;
		_ -> 0
	end.

%% 根据分区数据清理 被合分区数据
clean_with_rezone_info(State) ->
	#pet_power_rank_center_state{
		rank_info_map       = RankInfoMap
		,yest_rank_info_map = YestRankMap
		,tmp_rank_info_map  = TmpRankMap
		,tmp_up_servers     = TemUpMap
		,rezone_info_map    = RezoneMap
	} = State,
	DeleteZones = lists:usort(maps:keys(RezoneMap)),
	{
		NewRankInfoMap, NewYestRankMap, NewTmpRankMap, NewTemUpMap
	} = lists:foldl(fun(ZoneId, {TMap1, TMap2, TMap3, TMap4}) ->
		{
			maps:remove(ZoneId, TMap1),
			maps:remove(ZoneId, TMap2),
			maps:remove(ZoneId, TMap3),
			maps:remove(ZoneId, TMap4)
		}
	                end,
		{RankInfoMap, YestRankMap, TmpRankMap, TemUpMap},
		DeleteZones
	),
	NewState = State#pet_power_rank_center_state{
		rank_info_map       = NewRankInfoMap
		,yest_rank_info_map = NewYestRankMap
		,tmp_rank_info_map  = NewTmpRankMap
		,tmp_up_servers     = NewTemUpMap
		,rezone_info_map    = #{}
	},
	spawn(fun() -> db_delete_zone_rank(DeleteZones) end),
	NewState.

%% 更新跨服榜单长度到游戏服
midnight_update_center_rank_len(YestRankMap) ->
	MapF = fun(ZoneId, RankList, TMap) ->
		maps:put(ZoneId, erlang:length(RankList), TMap)
	    end,
	LenMap = maps:fold(MapF, #{}, YestRankMap),
	MFA = {mod_pet_support_mgr, set_center_rank_len, [LenMap]},
	ForF = fun(ZoneId) ->
		czone:zone_apply_cast(ZoneId, MFA)
	end,
	lists:map(ForF, maps:keys(YestRankMap)),
	ok.

%%update_center_rank_len(NewZones, LenMap) ->
%%	MFA = {mod_pet_support_mgr, set_center_rank_len, [LenMap]},
%%	LFun = fun(ZoneId) ->
%%		czone:zone_apply_cast(ZoneId, MFA)
%%	end,
%%	lists:map(LFun, NewZones),
%%	ok.

batch_save_power_rank(PowerRankMap) ->
	NowTime = utime:unixtime(),
	ZoneF = fun(ZoneId, RankList) ->
		F = fun(PetRankInfo, TList) ->
			#pet_power_rank_info{
				pet_id        = PetId,
				player_id     = RoleId,
				player_name   = RoleName,
				sex           = Sex,
				server_num    = ServerNum,
				vip           = Vip,
				sup_vip       = SupVip,
				dress_id      = DressId,
				photo_id      = PhotoId,
				sum_power     = SumPower
			} = PetRankInfo,
			Sql = util:fmt_bin(?SQL_SAVE_PET_POWER_CENTER_RANK_VALUE,
				[
					ZoneId, PetId, RoleId, Sex, RoleName, ServerNum,
					Vip, SupVip, DressId, PhotoId, SumPower, NowTime
				]
			),
			[Sql|TList]
			end,
		SqlList = lists:foldl(F, [], RankList),
		if
			SqlList =/= [] ->
				NewValues = util:join_bitstring(SqlList, <<", ">>),
				FinalSql = io_lib:format(?SQL_SAVE_PET_POWER_CENTER_RANK, [NewValues]),
				db:execute(FinalSql);
			true -> skip
		end end,
	maps:map(ZoneF, PowerRankMap),
	ok.

db_delete_zone_rank(DeleteZones) ->
	F = fun(ZoneId) ->
		Sql = io_lib:format(<<"delete from pet_power_center_rank where zone_id = ~p">>, [ZoneId]),
		db:execute(Sql)
	end,
	lists:map(F, DeleteZones),
	ok.