%%%-------------------------------------------------------------------
%%% @doc    经验海 - 功能逻辑文件
%%%         功能上类似星域幻魔，分层，多堆怪
%%% Module  : exp_sea_logic.erl
%%% Created : 2021-07-27
%%% @end
%%% @author : tyl
%%%-------------------------------------------------------------------
-module(exp_sea_logic).

-include("exp_sea.hrl").
-include("common.hrl").
-include("server.hrl").
-include("scene.hrl").
-include("role.hrl").
-include("figure.hrl").
-include("goods.hrl").
-include("skill.hrl").
-include("def_goods.hrl").
-include("def_fun.hrl").
-include("def_module.hrl").
-include("rec_event.hrl").
-include("def_event.hrl").
-include("predefine.hrl").
-include("errcode.hrl").

%% API
-export([
	login/1
	,reconnect/1
	,get_in_scene_time/1    %% 获取在场景内的时间
	,get_exp_sea_info/1     %% 界面信息
	,enter_exp_sea/1        %% 进入场景
	,exit_exp_sea/2         %% 退出场景
	,timeout_kick/1         %% 超时踢出场景
	,logout/1               %% 离线处理
	,change_scene_callback/4
	,get_drop_reward_list/1 %% 挂机收益
	,get_strength_info/1    %% 强化信息
	,click_strength/2       %% 点击强化
	,handle_event/2
	,daily_refresh/0        %% 4点重置经验海时间
	,do_refresh_player/1

	,is_exp_sea_scene/1
	,mon_drop/2             %% 记录击杀怪物掉落
	,record_mon_drop/2
	,add_exp/2              %% 记录杀怪经验
	,record_add_exp/2

	,create_mons/3          %% 创建怪物

	,gm_reset_exp_sea_time/1
]).

login(Player) ->
	case db_select_exp_sea(Player#player_status.id) of
		[Time, StrengthBin] -> Strengths = util:bitstring_to_term(StrengthBin);
		_ -> Time = 0, Strengths = []
	end,
	NewPlayer = Player#player_status{role_exp_sea = #role_exp_sea{time = Time, strength = Strengths}},
	NewPlayer.

daily_refresh() ->
	spawn(fun() ->
		timer:sleep(1000),
		Sql = io_lib:format(<<"UPDATE role_exp_sea SET `time`=~p, `strength_times`='~ts'">>,[0, util:term_to_string([])]),
		db:execute(Sql),
		Pids = lib_player:get_online_pids(),
		[lib_player:apply_cast(Pid, ?APPLY_CAST_SAVE, ?MODULE, do_refresh_player, []) || Pid <- Pids]
	end).

do_refresh_player(Player) ->
	#player_status{id = RoleId, scene = SceneId} = Player,
	%% 如果还在场景内，先把玩家踢出去
	case is_exp_sea_scene(SceneId) of
		true -> {ok, Player1} = exit_exp_sea(Player, ?NORMAL_EXIT);
		_ -> Player1 = Player
	end,
	%% 最后再清理一下数据
	db_save_exp_sea(RoleId, 0, []),
	NewPlayer = Player1#player_status{role_exp_sea = #role_exp_sea{}},
	get_exp_sea_info(NewPlayer),
	{ok, NewPlayer}.

reconnect(Player) ->
	#player_status{scene = Scene, role_exp_sea = RoleExpSea} = Player,
	IsInExpSea = is_exp_sea_scene(Scene),
	if
		IsInExpSea ->
			%% 1. 旧的玩家进程已经销毁，只需要修正场景；
			%% 2. 玩家进程还在，数据未修改的情况(顶号等)
			case RoleExpSea of
				#role_exp_sea{scene_id = SceneId} ->
					case is_exp_sea_scene(SceneId) of
						true ->
							{ok, NewPlayer} = exit_exp_sea(Player, ?NORMAL_EXIT);
						_ ->
							NewPlayer = lib_scene:change_default_scene(Player#player_status{action_lock = free}, [])
					end;
				_ ->
					NewPlayer = lib_scene:change_default_scene(Player#player_status{action_lock = free}, [])
			end,
			{ok, NewPlayer};
		true ->
			{next, Player}
	end.

%% ------------------------------------------------------------------------
%% @doc 获取在场景内时间段 @end
%% ------------------------------------------------------------------------
get_in_scene_time(Player) ->
	#player_status{role_exp_sea = RoleExpSea, scene = Scene
	} = Player,
	#role_exp_sea{time = Time, enter_time = EnterTime} = RoleExpSea,
	%% 层数区分在场景内/外，场景外推送推荐层数
	InSceneTime =
	case is_exp_sea_scene(Scene) of
		false ->
			Time;
		_ ->
			LastTime = max(0, utime:unixtime() - EnterTime),
			Time + LastTime
	end,
	InSceneMin = InSceneTime div 60,
	InSceneMin.

%% ------------------------------------------------------------------------
%% @doc 界面信息 @end
%% ------------------------------------------------------------------------
get_exp_sea_info(Player) ->
	#player_status{id = RoleId, role_exp_sea = RoleExpSea,
		figure = Figure, scene = Scene
	} = Player,
	#role_exp_sea{floor = Floor, time = Time, enter_time = EnterTime} = RoleExpSea,
	TimeLimit = data_exp_sea:get_kv(time_limit),
	%% 层数区分在场景内/外，场景外推送推荐层数
	case is_exp_sea_scene(Scene) of
		false ->
			LeftTime = max(0, TimeLimit - Time),
			Floor1 = get_recommend_floor(Figure#figure.lv);
		_ ->
			LastTime = max(0, utime:unixtime() - EnterTime),
			LeftTime = max(0, TimeLimit - Time - LastTime),
			Floor1 = Floor
	end,
	lib_server_send:send_to_uid(RoleId, pt_463, 46300, [Floor1, LeftTime]),
	ok.

%% ------------------------------------------------------------------------
%% @doc 进入场景 @end
%% ------------------------------------------------------------------------
enter_exp_sea(Player) ->
	#player_status{id = RoleId, role_exp_sea = RoleExpSea,
		figure = Figure, scene = SceneId
	} = Player,
	#role_exp_sea{time = Time} = RoleExpSea,
	%% 进入前检查
	case check_can_enter([
		{fun check_time/1, [Time]},
		{fun check_in_scene/1, [SceneId]},
		{fun check_action_lock/1, [Player]}
	]) of
		true ->
			case enter_scene_logic(RoleExpSea, RoleId, Figure#figure.lv) of
				{true, NewRoleExpSea, Floor, _SceneId, PoolId} ->
					mod_exp_sea:enter_scene(RoleId, Floor, PoolId),
					lib_server_send:send_to_uid(RoleId, pt_463, 46305, [?SUCCESS]),
					NewPlayer = Player#player_status{role_exp_sea = NewRoleExpSea};
				{false, ErrCode} ->
					lib_server_send:send_to_uid(RoleId, pt_463, 46305, [ErrCode]),
					NewPlayer = Player
			end;
		{false, Err} ->
			lib_server_send:send_to_uid(RoleId, pt_463, 46305, [Err]),
			NewPlayer = Player
	end,
	{ok, NewPlayer}.

%% ------------------------------------------------------------------------
%% @doc 离开场景 @end
%% ------------------------------------------------------------------------
exit_exp_sea(Player, Type) ->
	#player_status{id = RoleId, role_exp_sea = RoleExpSea} = Player,
	#role_exp_sea{floor = Floor, pool_id = PoolId,
		time = Time, enter_time = EnterTime, exp = SumExp,
		strength = Strengths, ref = Ref, reward_list = RewardList
	} = RoleExpSea,
	util:cancel_timer(Ref),
	%% 计算持续时间
	LastTime = max(0, utime:unixtime() - EnterTime),
	NewLasTime = LastTime + Time,
	NewRoleExpSea = RoleExpSea#role_exp_sea{
		floor = 0, scene_id = 0, pool_id = 0, enter_time = 0,
		time = NewLasTime, reward_list = [], exp = 0, ref = []
	},
	TimeLimit = data_exp_sea:get_kv(time_limit),
	NewLasTime == TimeLimit andalso
		lib_activitycalen_api:role_success_end_activity(Player#player_status.id, ?MOD_EXP_SEA, 0),
	db_save_exp_sea(RoleId, NewLasTime, Strengths),
	mod_exp_sea:exit_scene(RoleId, Floor, PoolId),
	RewardList1 =
	case RewardList of
		[{_, _, _}|_] -> [{RewardId, Num}||{_, RewardId, Num} <- RewardList];
		_ -> []
	end,
	lib_server_send:send_to_uid(RoleId, pt_463, 46306, [?SUCCESS]),
	lib_server_send:send_to_uid(RoleId, pt_463, 46304, [LastTime, SumExp, RewardList1]),
	Player1 = Player#player_status{role_exp_sea = NewRoleExpSea},
	{ok, Player2} = lib_skill_buff:clean_buff(Player1, ?SP_SKILL_BUFF_ENCOURAGE),
	%% 正常退出的时候才需要切换场景
	NewPlayer = case Type of
		          ?NORMAL_EXIT -> lib_scene:change_default_scene(Player2#player_status{action_lock = free}, []);
		          _ -> Player2
	          end,
	{ok, NewPlayer}.

%% 超时踢出场景
timeout_kick(Player) ->
	{ok, NewPlayer} = exit_exp_sea(Player, ?NORMAL_EXIT),
	{ok, NewPlayer}.

%% 正常离线踢出场景，但是player_state表还是会保存着场景数据，需要上线修复
logout(Player) ->
	#player_status{role_exp_sea = RoleExpSea} = Player,
	#role_exp_sea{scene_id = SceneId} = RoleExpSea,
	{ok, NewPlayer} = case is_exp_sea_scene(SceneId) of
		                  true -> exit_exp_sea(Player, ?NORMAL_EXIT);
		                  _ -> {ok, Player}
	                  end,
	NewPlayer.

handle_event(Player, #event_callback{type_id = ?EVENT_FIN_CHANGE_SCENE}) when is_record(Player, player_status)->
	#player_status{role_exp_sea = RoleExpSea, scene = Scene} = Player,
	case is_exp_sea_scene(Scene) of
		true ->
			#role_exp_sea{strength = Strengths} = RoleExpSea,
			SumTime = lists:foldl(fun(StrengthInfo, Calc) ->
				case StrengthInfo of
					{?EXP_SEA_STRENGTH_NORMAL, TTimes} -> Calc + TTimes;
					{?EXP_SEA_STRENGTH_PAY, TTimes} -> Calc + 2*TTimes;
					_ -> Calc
				end end, 0, Strengths),
			SumTime > 0 andalso lib_skill_buff:add_buff(Player, ?SP_SKILL_BUFF_ENCOURAGE, SumTime);
		_ ->
			ignore
	end,
	{ok, Player};
handle_event(Player, _) ->
	{ok, Player}.

%% 切换场景后清除buff，退出场景时前端未发起协议的保底处理
change_scene_callback(Player, OldSceneInfo, _OldSceneData, _NewSceneData) ->
	case OldSceneInfo of
		{OldSceneId, _, _, _, _} when OldSceneId == ?EXP_SEA_SCENE ->
			case Player#player_status.role_exp_sea of
				#role_exp_sea{scene_id = SceneId} when SceneId == ?EXP_SEA_SCENE ->
					{ok, NewPlayer} = exit_exp_sea(Player, ?SPECIAL_EXIT);
				_ ->
					NewPlayer = Player
			end;
		_ ->
			NewPlayer = Player
	end,
	{ok, NewPlayer}.

%% ------------------------------------------------------------------------
%% @doc 鼓舞信息 @end
%% ------------------------------------------------------------------------
get_strength_info(Player) ->
	#player_status{id = RoleId, role_exp_sea = RoleExpSea} = Player,
	#role_exp_sea{strength = Strengths} = RoleExpSea,
	SendStrengths = send_strength_info(Strengths),
	lib_server_send:send_to_uid(RoleId, pt_463, 46301, [SendStrengths]),
	ok.

send_strength_info(Strengths) ->
	F = fun(StrengthInfo, TList) ->
		case StrengthInfo of
			{Type, Times} ->
				case get_limit_times_and_cost(Type) of
					{LimitTimes, _} -> [{Type, max(0, LimitTimes - Times)}|TList];
					_ -> TList
				end;
			_ -> TList
		end end,
	SendStrengths = lists:foldl(F, [], Strengths),
	SendStrengths.

%% ------------------------------------------------------------------------
%% @doc 点击鼓舞 @end
%% ------------------------------------------------------------------------
click_strength(Player, Type) ->
	#player_status{id = RoleId, role_exp_sea = RoleExpSea, scene = SceneId} = Player,
	#role_exp_sea{strength = Strengths, time = Time} = RoleExpSea,
	%% 判断是否有剩余次数
	case get_limit_times_and_cost(Type) of
		{LimitTimes, CostList} ->
			CheckTimes =
			case lists:keyfind(Type, 1, Strengths) of
				{_, Times} when Times < LimitTimes -> true;
				{_, _} -> false;
				_ -> true
			end,
			CheckCost  = CostList =/= [],
			CheckScene = is_exp_sea_scene(SceneId),
			if
				not CheckScene ->
					lib_server_send:send_to_uid(RoleId, pt_463, 46302,
						[?ERRCODE(err463_not_exp_sea_scene), Strengths]
					),
					{ok, Player};
				not CheckTimes ->
					lib_server_send:send_to_uid(RoleId, pt_463, 46302,
						[?ERRCODE(err463_times_not_enough), Strengths]
					),
					{ok, Player};
				not CheckCost ->
					lib_server_send:send_to_uid(RoleId, pt_463, 46302,
						[?ERRCODE(err463_strength_cost_err), Strengths]
					),
					{ok, Player};
				true ->
					case lib_goods_api:cost_object_list_with_check(Player, CostList, exp_sea_strength, "") of
						{true, Player1, _} ->
							case lists:keyfind(Type, 1, Strengths) of
								{_, OldTimes} -> ok;
								_ -> OldTimes = 0
							end,
							NewTime = OldTimes + 1,
							NewStrengths = lists:keystore(Type, 1, Strengths, {Type, NewTime}),
							NewRoleExpSea = RoleExpSea#role_exp_sea{strength = NewStrengths},
							SumTime = lists:foldl(fun(StrengthInfo, Calc) ->
										case StrengthInfo of
											{?EXP_SEA_STRENGTH_NORMAL, TTimes} -> Calc + TTimes;
											{?EXP_SEA_STRENGTH_PAY, TTimes} -> Calc + 2*TTimes;
											_ -> Calc
										end end, 0, NewStrengths),
							lib_skill_buff:add_buff(Player, ?SP_SKILL_BUFF_ENCOURAGE, SumTime),
							db_save_exp_sea(RoleId, Time, NewStrengths),
							SendStrengths = send_strength_info(NewStrengths),
							lib_server_send:send_to_uid(RoleId, pt_463, 46302, [?SUCCESS, SendStrengths]),
							{ok, Player1#player_status{role_exp_sea = NewRoleExpSea}};
						{false, Res, _} ->
							lib_server_send:send_to_uid(RoleId, pt_463, 46302,
								[Res, Strengths]
							),
							{ok, Player}
					end
			end;
		_ ->
			lib_server_send:send_to_uid(RoleId, pt_463, 46302,
				[?ERRCODE(err463_strength_cost_err), Strengths]
			),
			{ok, Player}
	end.

get_limit_times_and_cost(Type) ->
	CostList =
	case Type of
		?EXP_SEA_STRENGTH_NORMAL ->
			data_exp_sea:get_kv(common_encourage_cost);
		_ ->
			data_exp_sea:get_kv(senior_encourage_cost)
	end,
	CostList.

%% ------------------------------------------------------------------------
%% @doc 挂机收益信息 @end
%% ------------------------------------------------------------------------
get_drop_reward_list(Player) ->
	#player_status{id = RoleId, role_exp_sea = RoleExpSea} = Player,
	#role_exp_sea{reward_list = RewardList, exp = Exp} = RoleExpSea,
	RewardList1 =
		case RewardList of
			[{_, _, _}|_] -> [{RewardId, Num}||{_, RewardId, Num} <- RewardList];
			_ -> []
		end,
	%% 加上挂机收益
	lib_server_send:send_to_uid(RoleId, pt_463, 46303, [Exp, RewardList1]),
	ok.

%% ------------------------------------------------------------------------
%% 怪物击杀，记录掉落
is_exp_sea_scene(SceneId) ->
	SceneId == ?EXP_SEA_SCENE.

mon_drop(RoleId, RewardList) ->
	lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, record_mon_drop, [RewardList]),
	ok.

record_mon_drop(Player, DropList) ->
	#player_status{role_exp_sea = RoleExpSea} = Player,
	#role_exp_sea{reward_list = RewardList} = RoleExpSea,
	NewRewardList = ulists:object_list_merge(DropList ++ RewardList),
	NewRoleExpSea = RoleExpSea#role_exp_sea{reward_list = NewRewardList},
	{ok, Player#player_status{role_exp_sea = NewRoleExpSea}}.

%% 怪物击杀，记录经验
add_exp(RoleId, AddExp) ->
	lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, record_add_exp, [AddExp]),
	ok.

record_add_exp(Player, AddExp) ->
	#player_status{role_exp_sea = RoleExpSea} = Player,
	#role_exp_sea{exp = OldExp} = RoleExpSea,
	NewExp = OldExp + AddExp,
	NewRoleExpSea = RoleExpSea#role_exp_sea{exp = NewExp},
	{ok, Player#player_status{role_exp_sea = NewRoleExpSea}}.

%% ------------------------- builtin func --------------------------------------
enter_scene_logic(RoleExpSea, RoleId, RoleLv) ->
	case get_scene_born_xy(RoleLv) of
		{Floor, SceneId, X, Y} when Floor > 0, SceneId > 0 ->
			#role_exp_sea{time = Time, ref = OldRef} = RoleExpSea,
			% 单人本才采用这种方法, 副本Id用层数
			% PoolId = scene_mgr:manual_divide_pool(SceneId, [RoleId]),
			case catch mod_exp_sea:get_floor_pool(Floor) of
				PoolId when is_integer(PoolId) ->
					Args = [{action_lock, ?ERRCODE(err463_has_in_scene)}],
					lib_scene:change_player_scene(RoleId, SceneId, PoolId, 0, X, Y, false, Args),
					NowTime = utime:unixtime(),
					Msg = {'mod', ?MODULE, timeout_kick, []},
					TimeLimit = data_exp_sea:get_kv(time_limit),
					LeftTime = max(0, TimeLimit - Time),
					Ref = util:send_after(OldRef, LeftTime *1000, self(), Msg),
					NewRoleExp = RoleExpSea#role_exp_sea{
						floor = Floor, scene_id = SceneId, pool_id = PoolId,
						reward_list = [], enter_time = NowTime, ref = Ref
					},
					{true, NewRoleExp, Floor, SceneId, PoolId};
				_ ->
					{false, ?ERRCODE(err463_floor_to_many_people)}
			end;
		_ ->
			{false, ?ERRCODE(err463_floor_cfg_err)}
	end.

get_scene_born_xy(RoleLv) ->
	case data_exp_sea:get_lv_floor(RoleLv) of
		Floor when is_integer(Floor) ->
			case data_exp_sea:get_floor(Floor) of
				#base_exp_sea_floor{scene = SceneId} ->
					case data_exp_sea:get_recommend_mon(RoleLv, Floor) of
						MonId when is_integer(MonId), MonId > 0 ->
							case data_exp_sea:get_mon(MonId) of
								#base_exp_sea_mon{born_xys = [{X, Y}|_]} -> {Floor, SceneId, X, Y};
								_ -> {0, 0, 0, 0}
							end;
						%% 找不到怪物堆就传送到出生点
						_ ->
							case data_scene:get(SceneId) of
								#ets_scene{x = X, y = Y} ->
									{Floor, SceneId, X, Y};
								_ ->
									{0, 0, 0, 0}
							end
					end;
				_ ->
					{0, 0, 0, 0}
			end;
		_ ->
			{0, 0, 0, 0}
	end.

create_mons(SceneId, PoolId, CopyId) ->
	Floor = PoolId div 2000,
	case data_exp_sea:get_floor_mons(Floor) of
		[_|_] = MonList ->
			do_create_mon(SceneId, PoolId, CopyId, MonList);
		_ -> igonre
	end.

do_create_mon(_, _, _, []) -> ok;
do_create_mon(SceneId, PoolId, CopyId, [MonId|T]) ->
	case data_exp_sea:get_mon(MonId) of
		#base_exp_sea_mon{born_xys = BornXys} ->
			F = fun(BornXy) ->
				case BornXy of
					{X, Y} ->
						lib_mon:async_create_mon(
							MonId, SceneId, PoolId, X, Y, 1, CopyId, 1, []
						);
					_ ->
						ignore
				end end,
			lists:map(F, BornXys);
		_ ->
			ignore
	end,
	do_create_mon(SceneId, PoolId, CopyId, T).

%% 获取推荐层数
get_recommend_floor(RoleLv) ->
	case data_exp_sea:get_lv_floor(RoleLv) of
		Floor when is_integer(Floor) ->
			Floor;
		_ ->
			0
	end.

%% ------------------------- check func -----------------------------------
check_can_enter([{Fun, Args}|T]) ->
	case erlang:apply(Fun, Args) of
		true ->
			check_can_enter(T);
		Error ->
			Error
	end;
check_can_enter([]) -> true.

check_time(Time) ->
	TimeLimit = data_exp_sea:get_kv(time_limit),
	case Time < TimeLimit of
		true -> true;
		_ -> {false, ?ERRCODE(err463_time_err)}
	end.

check_in_scene(SceneId) ->
	case is_exp_sea_scene(SceneId) of
		true -> {false, ?ERRCODE(err463_has_in_scene)};
		_ -> true
	end.

check_action_lock(Player) ->
	case lib_player_check:check_all(Player) of
		true -> true;
		Err -> Err
	end.

%% ------------------------- db func -----------------------------------
db_save_exp_sea(RoleId, Time, Strengths) ->
	Sql = io_lib:format(?SQL_INSERT_EXP_SEA, [RoleId, Time, util:term_to_string(Strengths)]),
	db:execute(Sql).

db_select_exp_sea(RoleId) ->
	Sql = io_lib:format(?SQL_SELECT_EXP_SEA, [RoleId]),
	db:get_row(Sql).

%% ------------------------- gm func -----------------------------------
gm_reset_exp_sea_time(Player) ->
	#player_status{id = RoleId} = Player,
	%% 存库
	db_save_exp_sea(RoleId, 0, []),
	NewPlayer = Player#player_status{role_exp_sea = #role_exp_sea{}},
	get_exp_sea_info(NewPlayer),
	NewPlayer.