package main

import (
	"mongo.games.com/goserver/core/logger"

	"mongo.games.com/game/model"
	"mongo.games.com/game/proto"
	gamehall_proto "mongo.games.com/game/protocol/gamehall"
	server_proto "mongo.games.com/game/protocol/server"
	"mongo.games.com/game/srvdata"
)

// CoinScenePool 房间池
type CoinScenePool struct {
	platform   string                    // 平台id
	groupId    int32                     // 组id
	id         int32                     // 场次id
	dbGameFree *server_proto.DB_GameFree // 场次配置
	dbGameRule *server_proto.DB_GameRule // 场次配置
	scenes     map[int]*Scene            // 所有房间,房间id
	players    map[int32]struct{}        // 玩家id

	// 扩展数据
	extraData interface{}
	// 匹配规则
	policy ICoinScenePool
}

func NewCoinScenePool(platform string, groupId int32, dbGameFree *server_proto.DB_GameFree) *CoinScenePool {
	if dbGameFree == nil {
		return nil
	}

	dbGameRule := srvdata.PBDB_GameRuleMgr.GetData(dbGameFree.GetGameRule())
	if dbGameRule == nil {
		logger.Logger.Errorf("Coin scene pool init failed,%v game rule data not find.", dbGameFree.GetGameRule())
		return nil
	}

	csp := &CoinScenePool{
		platform:   platform,
		groupId:    groupId,
		id:         dbGameFree.GetId(),
		dbGameFree: dbGameFree,
		dbGameRule: dbGameRule,
		scenes:     make(map[int]*Scene),
		players:    make(map[int32]struct{}),
		policy:     new(BaseCoinScenePool), // 默认方式
	}

	policy := GetCoinScenePool(dbGameFree.GetGameId())
	if policy != nil {
		csp.policy = policy
		csp.extraData = policy.New()
	}
	return csp
}

func (csp *CoinScenePool) GetPlayerNum() int32 {
	return int32(len(csp.players))
}

func (csp *CoinScenePool) GetFakePlayerNum() int32 {
	//if csp.dbGameFree != nil {
	//	correctNum := csp.dbGameFree.GetCorrectNum()
	//	correctRate := csp.dbGameFree.GetCorrectRate()
	//	count := csp.GetPlayerNum()
	//	return correctNum + count*correctRate/100 + csp.numDeviation
	//}
	return 0
}

// GetSceneType 获取场次id
func (csp *CoinScenePool) GetSceneType() int {
	if csp.dbGameFree != nil {
		return int(csp.dbGameFree.GetSceneType())
	}
	return 0
}

// CanInviteRob 能否邀请机器人
func (csp *CoinScenePool) CanInviteRob() bool {
	if csp.dbGameFree != nil {
		return csp.dbGameFree.GetBot() != 0
	}
	return false
}

// CanEnter 检查入场条件
func (csp *CoinScenePool) CanEnter(p *Player) gamehall_proto.OpResultCode {
	if csp.dbGameFree == nil || p == nil {
		return gamehall_proto.OpResultCode_OPRC_Error
	}

	//检测房间状态是否开启
	gps := PlatformMgrSingleton.GetGameFree(p.Platform, csp.id)
	if gps == nil || !gps.Status {
		return gamehall_proto.OpResultCode_OPRC_RoomHadClosed
	}

	dbGameFree := csp.dbGameFree
	if dbGameFree == nil {
		return gamehall_proto.OpResultCode_OPRC_RoomHadClosed
	}

	//检查游戏次数限制
	if !p.IsRob {
		todayData, _ := p.GetDaliyGameData(int(dbGameFree.GetId()))
		if dbGameFree.GetPlayNumLimit() != 0 &&
			todayData != nil &&
			todayData.GameTimes >= int64(dbGameFree.GetPlayNumLimit()) {
			return gamehall_proto.OpResultCode_OPRC_RoomGameTimes
		}
	}

	return csp.policy.CanEnter(csp, p)
}

// CanAudienceEnter 检查观众入场条件
func (csp *CoinScenePool) CanAudienceEnter(p *Player) gamehall_proto.OpResultCode {
	if csp.dbGameFree == nil || p == nil {
		return gamehall_proto.OpResultCode_OPRC_Error
	}

	//检测房间状态是否开启
	gps := PlatformMgrSingleton.GetGameFree(p.Platform, csp.id)
	if gps == nil {
		return gamehall_proto.OpResultCode_OPRC_RoomHadClosed
	}

	dbGameFree := csp.dbGameFree
	if dbGameFree == nil {
		return gamehall_proto.OpResultCode_OPRC_RoomHadClosed
	}

	return csp.policy.CanAudienceEnter(csp, p)
}

// PlayerEnter 玩家进入房间池
func (csp *CoinScenePool) PlayerEnter(p *Player, roomId int32, exclude []int32, ischangeroom bool) gamehall_proto.OpResultCode {
	if ret := csp.CanEnter(p); ret != gamehall_proto.OpResultCode_OPRC_Sucess {
		logger.Logger.Warnf("(csp *CoinScenePool) PlayerEnter find snid:%v csp.CanEnter coin:%v ret:%v id:%v", p.SnId,
			p.Coin, ret, csp.dbGameFree.GetId())
		return ret
	}

	if p.scene != nil {
		logger.Logger.Warnf("(csp *CoinScenePool) PlayerEnter[p.scene != nil] find snid:%v in scene:%v gameId:%v", p.SnId, p.scene.sceneId, p.scene.gameId)
		return gamehall_proto.OpResultCode_OPRC_Error
	}

	var scene *Scene
	// 进入房间
	if roomId != 0 && (p.IsRob || p.GMLevel > 0 || csp.dbGameFree.GetCreateRoomNum() != 0) {
		if s, ok := csp.scenes[int(roomId)]; ok {
			if s != nil && !s.deleting { //指定房间id进入，那么忽略掉排除id
				if s.IsFull() {
					return gamehall_proto.OpResultCode_OPRC_RoomIsFull
				}
				if sp, ok := s.sp.(*ScenePolicyData); ok {
					if !s.starting || sp.EnterAfterStart {
						scene = s
					} else {
						logger.Logger.Warnf("(csp *CoinScenePool) PlayerEnter[!s.starting || sp.EnterAfterStart] snid:%v sceneid:%v starting:%v EnterAfterStart:%v", p.SnId, s.sceneId, s.starting, sp.EnterAfterStart)
						return gamehall_proto.OpResultCode_OPRC_Error
					}
				}
			}
		} else {
			logger.Logger.Warnf("(csp *CoinScenePool) PlayerEnter(robot:%v,roomid:%v, exclude:%v, ischangeroom:%v) no found scene", p.SnId, roomId, exclude, ischangeroom)
			return gamehall_proto.OpResultCode_OPRC_Error
		}
	}

	if scene == nil {
		var ret gamehall_proto.OpResultCode
		ret, scene = csp.policy.PlayerEnter(csp, p, exclude, ischangeroom)
		if ret != gamehall_proto.OpResultCode_OPRC_Sucess {
			return ret
		}
	}

	if scene == nil {
		scene = csp.policy.NewScene(csp, p)
		if scene != nil {
			csp.scenes[scene.sceneId] = scene
			scene.csp = csp
		} else {
			logger.Logger.Errorf("Create %v scene failed.", csp.id)
		}
	}

	if scene != nil {
		if p.EnterScene(scene, ischangeroom, -1) {
			csp.OnPlayerEnter(p, scene)
			return gamehall_proto.OpResultCode_OPRC_Sucess
		}
	}
	logger.Logger.Warnf("(csp *CoinScenePool) PlayerEnter snid:%v not found scene", p.SnId)
	return gamehall_proto.OpResultCode_OPRC_SceneServerMaintain
}

// AudienceEnter 观众入场
func (csp *CoinScenePool) AudienceEnter(p *Player, roomId int32, exclude []int32, ischangeroom bool) gamehall_proto.OpResultCode {
	if ret := csp.CanAudienceEnter(p); ret != gamehall_proto.OpResultCode_OPRC_Sucess {
		logger.Logger.Warnf("(csp *CoinScenePool) AudienceEnter find snid:%v csp.CanEnter coin:%v ret:%v id:%v", p.SnId, p.Coin, ret, csp.dbGameFree.GetId())
		return ret
	}

	if p.scene != nil {
		logger.Logger.Warnf("(csp *CoinScenePool) AudienceEnter[p.scene != nil] find snid:%v in scene:%v gameId:%v", p.SnId, p.scene.sceneId, p.scene.gameId)
		return gamehall_proto.OpResultCode_OPRC_Error
	}

	var scene *Scene
	if roomId != 0 {
		if s, ok := csp.scenes[int(roomId)]; ok {
			if s != nil && !s.deleting /*&& s.sceneId != int(exclude)*/ {
				scene = s
			} else {
				logger.Logger.Warnf("(csp *CoinScenePool) AudienceEnter[!s.starting || sp.EnterAfterStart] snid:%v sceneid:%v starting:%v EnterAfterStart:%v", p.SnId, s.sceneId, s.starting)
			}
		}
	}

	if scene == nil {
		var ret gamehall_proto.OpResultCode
		ret, scene = csp.policy.AudienceEnter(csp, p, exclude, ischangeroom)
		if ret != gamehall_proto.OpResultCode_OPRC_Sucess {
			return ret
		}
	}

	if scene == nil {
		return gamehall_proto.OpResultCode_OPRC_NoFindDownTiceRoom
	}

	if scene != nil {
		// 预创建房间检查观众数量
		if scene.IsPreCreateScene() && scene.GetAudienceCnt() >= model.GameParamData.MaxAudienceNum {
			return gamehall_proto.OpResultCode_OPRC_RoomIsFull
		}
		if scene.AudienceEnter(p, ischangeroom) {
			csp.OnPlayerEnter(p, scene)
			return gamehall_proto.OpResultCode_OPRC_Sucess
		}
	}
	logger.Logger.Warnf("(csp *CoinScenePool) PlayerEnter snid:%v not found scene", p.SnId)
	return gamehall_proto.OpResultCode_OPRC_NoFindDownTiceRoom
}

// OnPlayerEnter 玩家进入房间完成
func (csp *CoinScenePool) OnPlayerEnter(p *Player, scene *Scene) {
	csp.players[p.SnId] = struct{}{}
	csp.policy.OnPlayerEnter(csp, p, scene)
}

// PlayerLeave 玩家离开房间
func (csp *CoinScenePool) PlayerLeave(p *Player, reason int) bool {
	if p.scene == nil {
		return true
	}
	if p.scene.csp != csp && p.scene == csp.scenes[p.scene.sceneId] {
		logger.Logger.Error("bug")
	}
	if p.scene.csp != csp {
		return false
	}
	if p.scene != csp.scenes[p.scene.sceneId] {
		logger.Logger.Error("bug")
	}
	s, ok := csp.scenes[p.scene.sceneId]
	if !ok || s == nil {
		return false
	}
	if !s.HasPlayer(p) {
		return false
	}

	if !csp.policy.PlayerLeave(csp, p, reason) {
		return false
	}

	s.PlayerLeave(p, reason)
	logger.Logger.Tracef("(csp *CoinScenePool) PlayerLeave snid:%v in scene:%v", p.SnId, s.sceneId)

	csp.policy.OnPlayerLeave(csp, s, p)

	csp.OnPlayerLeave(s, p)
	return true
}

// AudienceLeave 观众离开房间
func (csp *CoinScenePool) AudienceLeave(p *Player, reason int) bool {
	if p.scene == nil {
		return true
	}
	if p.scene.csp != csp {
		return false
	}
	s, ok := csp.scenes[p.scene.sceneId]
	if !ok || s == nil {
		return false
	}
	if !s.HasAudience(p) {
		return false
	}

	if !csp.policy.AudienceLeave(csp, p, reason) {
		return false
	}

	s.AudienceLeave(p, reason)
	logger.Logger.Tracef("(csp *CoinScenePool) AudienceLeave snid:%v in scene:%v", p.SnId, s.sceneId)

	csp.policy.OnPlayerLeave(csp, s, p)

	csp.OnPlayerLeave(s, p)
	return true
}

// OnPlayerLeave 离开房间完成
func (csp *CoinScenePool) OnPlayerLeave(s *Scene, p *Player) {
	if s == nil || p == nil {
		return
	}
	delete(csp.players, p.SnId)

	// 玩家离开结算空房间的私人房
	if s.IsPrivateScene() {
		if s.IsEmpty() {
			s.ForceDelete(false)
		}
		return
	}

	// 解散空房间并且房间数量大于预创建房间数量
	if s.IsPreCreateScene() {
		if s.IsEmpty() {
			var hasCnt int
			for _, scene := range csp.scenes {
				if s.limitPlatform.IdStr == scene.limitPlatform.IdStr {
					hasCnt++
				}
			}
			if hasCnt > int(csp.dbGameFree.GetCreateRoomNum()) {
				s.ForceDelete(false)
			}
		}
	}
}

// OnDestroyScene 解散房间
// todo 是否需要优化
func (csp *CoinScenePool) OnDestroyScene(sceneId int) {
	scene, ok := csp.scenes[sceneId]
	if !ok {
		return
	}

	for id := range scene.players {
		player := PlayerMgrSington.GetPlayerBySnId(id)
		if player != nil {
			if !player.IsRob {
				ctx := scene.GetPlayerGameCtx(player.SnId)
				if ctx != nil {
					//发送一个探针,等待ack后同步金币
					player.TryRetrieveLostGameCoin(sceneId)
				}
			}
		}
	}
	for id := range scene.audiences {
		player := PlayerMgrSington.GetPlayerBySnId(id)
		if player != nil {
			if !player.IsRob {
				ctx := scene.GetPlayerGameCtx(player.SnId)
				if ctx != nil {
					//发送一个探针,等待ack后同步金币
					player.TryRetrieveLostGameCoin(sceneId)
				}
			}
		}
	}

	csp.policy.OnDestroyScene(csp, sceneId)
	scene.csp = nil // 解除关联
	delete(csp.scenes, sceneId)
}

// PreCreateRoom 预创建房间
func (csp *CoinScenePool) PreCreateRoom() {
	if csp.platform == DefaultPlatform {
		return
	}
	preCreateNum := int(csp.dbGameFree.GetCreateRoomNum())
	if preCreateNum == 0 || model.GameParamData.ClosePreCreateRoom {
		return
	}
	if p := PlatformMgrSingleton.GetPlatform(csp.platform); p == nil || p.Disable {
		return
	}
	var num int
	for _, scene := range csp.scenes {
		if scene.limitPlatform.IdStr == csp.platform {
			num++
		}
	}
	if num < preCreateNum {
		inc := preCreateNum - num
		for i := 0; i < inc; i++ {
			scene := csp.policy.NewPreCreateScene(csp)
			if scene != nil {
				csp.scenes[scene.sceneId] = scene
				scene.csp = csp
			}
		}
	}
}

// ListRoom 房间列表
func (csp *CoinScenePool) ListRoom(p *Player) bool {
	if p.scene != nil {
		logger.Logger.Warnf("(csp *CoinScenePool) PlayerListRoom[p.scene != nil] find snid:%v in scene:%v gameId:%v", p.SnId, p.scene.sceneId, p.scene.gameId)
		return false
	}

	csp.PreCreateRoom()

	if len(csp.scenes) == 0 {
		return false
	}

	pack := &gamehall_proto.SCCoinSceneListRoom{
		Id:             csp.dbGameFree.Id,
		LimitCoin:      csp.dbGameFree.LimitCoin,
		MaxCoinLimit:   csp.dbGameFree.MaxCoinLimit,
		BaseScore:      csp.dbGameFree.BaseScore,
		MaxScore:       csp.dbGameFree.MaxChip,
		OtherIntParams: csp.dbGameFree.OtherIntParams,
	}

	maxPlayerNum := 0
	for sceneId, s := range csp.scenes {
		data := &gamehall_proto.CoinSceneInfo{
			SceneId:   proto.Int(sceneId),
			PlayerNum: proto.Int(len(s.players)),
		}
		pack.Datas = append(pack.Datas, data)
		maxPlayerNum = s.playerNum
	}
	pack.MaxPlayerNum = proto.Int(maxPlayerNum)
	proto.SetDefaults(pack)
	p.SendToClient(int(gamehall_proto.CoinSceneGamePacketID_PACKET_SC_COINSCENE_LISTROOM), pack)
	return true
}

// GetHasTruePlayerSceneCnt 有真人的房间数量
func (csp *CoinScenePool) GetHasTruePlayerSceneCnt() int {
	cnt := 0
	for _, s := range csp.scenes {
		if s.GetTruePlayerCnt() != 0 {
			cnt++
		}
	}
	return cnt
}
