package room

import (
	"game/common/baseroom"
	"game/common/config/game"
	"game/common/proto/pb"
	"game/server/colorgame/config"
	"game/server/colorgame/model"
	"github.com/fox/fox/ksync"
	"github.com/fox/fox/log"
	"github.com/fox/fox/xrand"
	"sort"
	"sync"
	"time"
)

const (
	ReasonBet    = "bet"    // 扣钱原因:投注
	ReasonSettle = "settle" // 扣钱或加钱原因:结算
)

func (rm *ColorRoom) setStatus(status pb.ColorGameStatus) {
	rm.status = status
	rm.statusTime = time.Now().UnixMilli()
}

// 状态结束时间点，毫秒
func (rm *ColorRoom) endTimeStatus() int64 {
	duration := int64(0)
	switch rm.status {
	case pb.ColorGameStatus_CGS_Start:
		duration = rm.timingCfg.Start
	case pb.ColorGameStatus_CGS_Betting:
		duration = rm.timingCfg.Betting
	case pb.ColorGameStatus_CGS_BetEnd:
		duration = rm.timingCfg.EndBetting
	case pb.ColorGameStatus_CGS_OpenThreeDice:
		duration = rm.timingCfg.OpenThreeDice
	case pb.ColorGameStatus_CGS_Settle:
		duration = rm.timingCfg.Settle
	}
	return rm.statusTime + duration
}

// 游戏开始前更新配置
func (rm *ColorRoom) updateConfig() {
	colorConfig := config.GetColorConfig()
	rm.timingCfg = colorConfig.GameTiming
	for _, cfg := range colorConfig.Rooms {
		if rm.RoomType() == cfg.RoomType {
			rm.roomCfg = cfg
			break
		}
	}
}

// 游戏开始时展示每个区域的默认赔率
func (rm *ColorRoom) initEndBetAreaMul() {
	rm.endBetAreaMul = rm.endBetAreaMul[0:0]
	for pos := 0; pos < len(pb.ColorBetArea_name); pos++ {
		prizeArea := pb.ColorPrizeArea_CPA_Single_0
		var mul = rm.roomCfg.WinSingleColorMul[0]
		switch pos / 6 {
		case 1:
			prizeArea = pb.ColorPrizeArea_CPA_Double
			mul = rm.roomCfg.WinDoubleColorMul
		case 2:
			prizeArea = pb.ColorPrizeArea_CPA_Three
			mul = rm.roomCfg.WinThreeColorMul
		}

		rm.endBetAreaMul = append(rm.endBetAreaMul, &pb.ColorBetAreaMul{
			Area:      pb.ColorBetArea(pos),
			PrizeArea: prizeArea,
			PrizeType: pb.ColorPrizeType_CPT_Normal,
			Mul:       mul[0].Mul / config.Hundred,
		})
	}
}

// 随机出某个奖励档位下的赔率数组,档位，赔率数组概率之和
func (rm *ColorRoom) randArrMul(area pb.ColorBetArea) (arrMul []*game.ColorMulRate, prizeArea pb.ColorPrizeArea, sumRate int) {
	switch area / 6 {
	case 0:
		maxWeight := 0
		for _, w := range rm.roomCfg.WinSingleColorWeight {
			maxWeight += w
		}
		weight := xrand.RandRange[int](0, maxWeight)
		for pos, w := range rm.roomCfg.WinSingleColorWeight {
			if weight < w {
				prizeArea = pb.ColorPrizeArea(pos)
				arrMul = rm.roomCfg.WinSingleColorMul[pos]
				break
			}
			weight -= w
		}
	case 1:
		prizeArea = pb.ColorPrizeArea_CPA_Double
		arrMul = rm.roomCfg.WinDoubleColorMul
	case 2:
		prizeArea = pb.ColorPrizeArea_CPA_Three
		arrMul = rm.roomCfg.WinThreeColorMul
	default:
		log.Error("area:%v is not exist")
		return
	}
	for _, mr := range arrMul {
		sumRate += mr.Rate
	}
	return
}

// 下注结束后，更新每个区域的实际赔率
func (rm *ColorRoom) updateEndBetAreaMul() {
	jackpotExist := false
	for pos := range pb.ColorBetArea_name {
		bam := rm.endBetAreaMul[pos]
		arrMul, prizeArea, sumRate := rm.randArrMul(pb.ColorBetArea(pos))
		bam.PrizeArea = prizeArea
		bam.PrizeType = pb.ColorPrizeType_CPT_Normal

		if prizeArea == pb.ColorPrizeArea_CPA_Single_2 {
			// 在单色区域 命中三同色爆奖之后 再随jackpot概率
			// 只会有一个区域有jackpot标签
			if !jackpotExist && xrand.RandRange(0, config.RateBase) < rm.roomCfg.JackpotRate {
				bam.Mul = 0
				bam.PrizeType = pb.ColorPrizeType_CPT_Jackpot
				jackpotExist = true
				continue
			}
		}
		mulPos := 0
		rd := xrand.RandRange[int](0, sumRate)
		for i, mr := range arrMul {
			if rd < mr.Rate {
				mulPos = i
			}
			rd -= mr.Rate
		}
		// 赔率数组里第一个是基础赔率，后面的都是爆奖赔率
		if mulPos > 0 {
			bam.PrizeType = pb.ColorPrizeType_CPT_Big
		}
		bam.Mul = arrMul[mulPos].Mul / config.Hundred
		log.Debug(rm.Log("区域:%v 显示赔率:%v 奖励类型:%v 奖励档位:%v", bam.Area, bam.Mul, bam.PrizeType, bam.PrizeArea))
	}
}

// 获取三个骰子指定颜色中了几个
func (rm *ColorRoom) getDiceColorCount(color []pb.ColorType, c pb.ColorType) (count int) {
	for _, colorType := range color {
		if colorType == c {
			count++
		}
	}
	return
}

// 获取投注区域的颜色
func (rm *ColorRoom) getAreaColor(area pb.ColorBetArea) (color pb.ColorType) {
	switch area {
	case pb.ColorBetArea_CBA_Yellow, pb.ColorBetArea_CBA_Yellow2, pb.ColorBetArea_CBA_Yellow3:
		return pb.ColorType_CT_Yellow
	case pb.ColorBetArea_CBA_White, pb.ColorBetArea_CBA_White2, pb.ColorBetArea_CBA_White3:
		return pb.ColorType_CT_White
	case pb.ColorBetArea_CBA_Pink, pb.ColorBetArea_CBA_Pink2, pb.ColorBetArea_CBA_Pink3:
		return pb.ColorType_CT_Pink
	case pb.ColorBetArea_CBA_Blue, pb.ColorBetArea_CBA_Blue2, pb.ColorBetArea_CBA_Blue3:
		return pb.ColorType_CT_Blue
	case pb.ColorBetArea_CBA_Red, pb.ColorBetArea_CBA_Red2, pb.ColorBetArea_CBA_Red3:
		return pb.ColorType_CT_Red
	case pb.ColorBetArea_CBA_Green, pb.ColorBetArea_CBA_Green2, pb.ColorBetArea_CBA_Green3:
		return pb.ColorType_CT_Green
	}
	return
}

// 获取某个区域的实际中奖赔率
func (rm *ColorRoom) getAreaMul(area pb.ColorBetArea, prizeArea pb.ColorPrizeArea) (mul int64, prizeType pb.ColorPrizeType) {
	bam := rm.endBetAreaMul[area]
	// 奖励档位一样，比如单黄投注区域，显示双色爆奖，赔率为9。此时开出双黄色，则该投注区域赔率为9。
	// 如果开出三黄色，则赔率为CPA_Single_2的赔率组里的第1个赔率(基础赔率)
	// 如果开出单黄色，则赔率为CPA_Single_0的赔率组里的第1个赔率(基础赔率)
	if bam.PrizeArea == prizeArea {
		mul = bam.Mul
		prizeType = bam.PrizeType
	} else {
		arr2Mul := make([][]*game.ColorMulRate, 0, 5)
		arr2Mul = append(arr2Mul, rm.roomCfg.WinSingleColorMul...)
		arr2Mul = append(arr2Mul, rm.roomCfg.WinDoubleColorMul)
		arr2Mul = append(arr2Mul, rm.roomCfg.WinThreeColorMul)
		mul = arr2Mul[prizeArea][0].Mul
		prizeType = pb.ColorPrizeType_CPT_Normal
	}
	return
}

// 检查投掷结果是否匹配某个下注区域
func (rm *ColorRoom) isWinInArea(color []pb.ColorType, area pb.ColorBetArea) (win bool, prizeArea pb.ColorPrizeArea) {
	colorCount := rm.getDiceColorCount(color, rm.getAreaColor(area))
	if colorCount == 0 {
		return false, 0
	}
	// 单色投注区，查看开出几个该颜色
	if area < pb.ColorBetArea_CBA_Yellow2 {
		if colorCount == 1 {
			return true, pb.ColorPrizeArea_CPA_Single_0
		} else if colorCount == 2 {
			return true, pb.ColorPrizeArea_CPA_Single_1
		} else {
			return true, pb.ColorPrizeArea_CPA_Single_2
		}
	} else if area < pb.ColorBetArea_CBA_Yellow3 {
		if colorCount > 1 {
			return true, pb.ColorPrizeArea_CPA_Double
		}
	} else {
		if colorCount > 2 {
			return true, pb.ColorPrizeArea_CPA_Three
		}
	}
	return false, 0
}

// 开3个骰子，并计算出赢钱区域及实际赔率
func (rm *ColorRoom) openDices() {
	for i := 0; i < 3; i++ {
		c := xrand.RandRange(int(pb.ColorType_CT_Yellow), int(pb.ColorType_CT_Green))
		rm.ntfOpenThreeDice.Color = append(rm.ntfOpenThreeDice.Color, pb.ColorType(c))
	}
	rm.ntfOpenThreeDice.AniRouteIndex = 0

	for _, area := range rm.endBetAreaMul {
		isWin, prizeArea := rm.isWinInArea(rm.ntfOpenThreeDice.Color, area.Area)
		mul, prizeType := rm.getAreaMul(area.Area, prizeArea)
		if !isWin {
			continue
		}
		rm.winBetAreaMul = append(rm.winBetAreaMul, &pb.ColorBetAreaMul{
			Area:      area.Area,
			PrizeArea: prizeArea,
			PrizeType: prizeType,
			Mul:       mul,
		})
		rm.ntfOpenThreeDice.WinArea = append(rm.ntfOpenThreeDice.WinArea, area.Area)
	}
}

// 计算 下注区域中奖得分，返回是否有jp奖，jp奖位置，中奖人数
func (rm *ColorRoom) calculateJackpotScore() (pb.ColorBetArea, map[int64]int64) {
	jackpotArea := pb.ColorBetArea(-1)
	for _, winArea := range rm.winBetAreaMul {
		if winArea.PrizeType != pb.ColorPrizeType_CPT_Jackpot {
			continue
		}
		rm.RangePlayer(func(u baseroom.IPlayer) bool {
			user := GetPlayer(u)
			if user.totalBets[winArea.Area] > 0 {
				rm.jackpotMgr.AddJpUser(user.ID, user.totalBets[winArea.Area])
			}
			return true
		})
	}
	rm.jackpotUser, rm.jackpotValue = rm.jackpotMgr.WinJackpot()
	log.Debug(rm.Log("本局是否中jackpot奖:%v, 玩家一起分走jackpot:%v, 当前jackpot值:%v", rm.jackpotValue > 0, rm.jackpotValue, rm.jackpotMgr.GetJackpot()))
	return jackpotArea, rm.jackpotUser
}

// 计算 所有玩家的下注区域中奖得分
func (rm *ColorRoom) calculateAllUserScore() {
	// 赢钱会清空jackpot池
	jpArea, userJackPot := rm.calculateJackpotScore()
	var jackpotUserName []string
	rm.RangePlayer(func(u baseroom.IPlayer) bool {
		user := GetPlayer(u)
		if user.totalBet > 0 {
			// 算分
			jpScore := userJackPot[user.ID]
			rm.calculateUserScore(user, jpArea, jpScore)
			if jpScore > 0 {
				jackpotUserName = append(jackpotUserName, user.Nickname)
				//rm.BroadHitJackpot(user, jpScore)
			}
		}
		return true
	})
}

// 计算 下注区域中奖得分
func (rm *ColorRoom) calculateUserScore(user *ColorPlayer, jpArea pb.ColorBetArea, jpScore int64) {
	msg := user.settleMsg
	for _, winArea := range rm.winBetAreaMul {
		win2 := int64(0) // 税前
		gold := int64(0) // 税后
		if user.totalBets[winArea.Area] <= 0 {
			continue
		}
		if jpArea != winArea.Area {
			odds := winArea.Mul
			// 奖金计算公式 Payouts =( Odds * Bet ) * ( 1 + Bonus) + Bet  odds 是倍率 Bonus是猜中幸运骰子 的加成
			// 本区域赢未扣税 倍率是百分值所以计算时要除以100
			win2 = (odds * user.totalBets[winArea.Area]) / 100
			// 本区域赢扣税
			gold = win2 * (100 - rm.roomCfg.Rate) / 100
			// 算税()
			msg.Tax += win2 - gold
			//// 加回投注本金
			//gold += user.totalBets[winArea.Area]
			msg.TotalWin += gold
			// 统计赢区的下注总额
			msg.TotalWinBaseBet += user.totalBets[winArea.Area]

			log.Debug(rm.UserLog(user.ID, "算分 odds:%v 投注区:%v 税前:%v 税后+本金:%v", odds, winArea.Area, win2, gold))
		} else {
			win2 = jpScore
			// 本区域赢扣税
			gold = jpScore * (100 - rm.roomCfg.Rate) / 100
			// 算税()
			msg.Tax += win2 - gold
			//// 加回投注本金
			//gold += user.totalBets[winArea.Area]
			msg.TotalWin += gold
			// 统计赢区的下注总额
			msg.TotalWinBaseBet += user.totalBets[winArea.Area]

			log.Debug(rm.UserLog(user.ID, "算分 jackpotValue 投注区:%v win2:%v Gold:%v", winArea.Area, win2, gold))
		}
		msg.UserAreaWin = append(msg.UserAreaWin, &pb.NtfColorSettle_UserBetAreaMul{
			AreaMul: &pb.ColorBetAreaMul{
				Area:      winArea.Area,
				PrizeType: winArea.PrizeType,
				PrizeArea: winArea.PrizeArea,
				Mul:       winArea.Mul,
			},
			Bet:     user.totalBets[winArea.Area],
			Win:     win2,
			RealWin: gold,
		})
	}
}

// 和平台做结算
func (rm *ColorRoom) settle() {
	rm.calculateAllUserScore()
	wg := new(sync.WaitGroup)
	rm.RangePlayer(func(u baseroom.IPlayer) bool {
		user := GetPlayer(u)
		// 没有赢分
		if user.settleMsg.TotalWin < 1 {
			return true
		}
		ksync.GroupGo(wg, func() {
			afterGold, ok := rm.rpcPayoutGold(user, user.settleMsg.TotalWin, ReasonSettle)
			if !ok {
				log.Error(rm.UserLog(user.ID, "add gold:%v fail", user.settleMsg.TotalWin))
			} else {
				rm.GetService().RunOnce(func() {
					rm.setGold(user, afterGold)
				})
			}
		})
		return true
	})
	wg.Wait()
}

// 更新大客户
func (rm *ColorRoom) updateBigUsers() {
	if rm.status > pb.ColorGameStatus_CGS_Betting && rm.status < pb.ColorGameStatus_CGS_Settle {
		rm.RangePlayer(func(u baseroom.IPlayer) bool {
			user := GetPlayer(u)
			rm.bigUsers = append(rm.bigUsers, &pb.ColorBigUser{
				NickName: user.Nickname,
				Avatar:   user.AvatarUrl,
				WinChips: user.totalBet,
				AreaId:   user.totalBets,
			})
			return true
		})

	} else if rm.status >= pb.ColorGameStatus_CGS_Settle {
		rm.RangePlayer(func(u baseroom.IPlayer) bool {
			user := GetPlayer(u)
			bigUser := &pb.ColorBigUser{
				NickName: user.Nickname,
				Avatar:   user.AvatarUrl,
				WinChips: user.settleMsg.TotalWin,
			}
			for _, area := range user.settleMsg.UserAreaWin {
				bigUser.AreaId = append(bigUser.AreaId, area.Win)
			}
			return true
		})
	}
	sort.Slice(rm.bigUsers, func(i, j int) bool {
		return rm.bigUsers[i].WinChips > rm.bigUsers[j].WinChips
	})
	if len(rm.bigUsers) > 6 {
		rm.bigUsers = rm.bigUsers[0:6]
	}
}

func (rm *ColorRoom) initGameTrend() [][]pb.ColorType {
	return model.GetTrend()
}

// 更新路途 最多保存100局
func (rm *ColorRoom) updateGameTrend() {
	rm.trend = append(rm.trend, []pb.ColorType{rm.ntfOpenThreeDice.Color[0], rm.ntfOpenThreeDice.Color[1], rm.ntfOpenThreeDice.Color[2]})
	if len(rm.trend) > 100 {
		rm.trend = rm.trend[len(rm.trend)-100 : len(rm.trend)]
	}
	model.SetTrend(rm.trend)
}

// 更新路途 最多保存100局
func (rm *ColorRoom) getNotifyTrend() *pb.NtfColorTrend {
	ntf := &pb.NtfColorTrend{}
	// 只计算30局的概率
	maxCount := 30
	partTrend := rm.trend
	if len(rm.trend) > maxCount {
		partTrend = rm.trend[len(rm.trend)-maxCount : len(rm.trend)]
	}
	for c := pb.ColorType_CT_Yellow; c <= pb.ColorType_CT_Green; c++ {
		cnt := int32(0)
		for _, colors := range partTrend {
			for _, color := range colors {
				if color == c {
					cnt++
				}
			}
		}
		ntf.ColorRate = append(ntf.ColorRate, &pb.NtfColorTrend_ColorRate{
			Color: c,
			Rate:  cnt * 10000 / 100,
		})
	}
	return ntf
}

// 五个档位(single有三个加上double,three两个)的赔率范围
func (rm *ColorRoom) initPrizeAreaRange() {
	rm.prizeAreaRange = rm.prizeAreaRange[0:0]
	arr2Mul := make([][]*game.ColorMulRate, 0, 5)
	arr2Mul = append(arr2Mul, rm.roomCfg.WinSingleColorMul...)
	arr2Mul = append(arr2Mul, rm.roomCfg.WinDoubleColorMul)
	arr2Mul = append(arr2Mul, rm.roomCfg.WinThreeColorMul)
	for prizeType, mul := range arr2Mul {
		rm.prizeAreaRange = append(rm.prizeAreaRange, &pb.ColorPrizeAreaRange{
			Pos:    pb.ColorPrizeArea(prizeType),
			MinMul: mul[0].Mul,
			MaxMul: mul[len(mul)-1].Mul,
		})
	}
}

// 玩家房间配置信息，每个玩家下注档位不一样
func (rm *ColorRoom) getUserRoomConfig(user *ColorPlayer) *pb.ColorRoomConfig {
	cnf := &pb.ColorRoomConfig{
		BetList:        nil,
		MulRangeConfig: rm.prizeAreaRange,
	}
	lv := 0
	for pos, lvBet := range rm.roomCfg.BetLevel {
		if rm.GetGold(user) < lvBet {
			break
		}
		lv = pos
	}
	cnf.BetList = rm.roomCfg.BetList[lv]
	return cnf
}

// 玩家转换成proto格式
func (rm *ColorRoom) getProtoUser(user *ColorPlayer) *pb.ColorUser {
	u := &pb.ColorUser{
		UserID: user.ID,
		Nick:   user.Nickname,
		Head:   user.AvatarUrl,
		Score:  rm.GetGold(user),
	}
	return u
}

// 结束下注，扣除投注玩家的投注金额
func (rm *ColorRoom) endBetPayoutUser() {
	var failer []*ColorPlayer
	var mt sync.Mutex
	wg := &sync.WaitGroup{}
	rm.RangePlayer(func(u baseroom.IPlayer) bool {
		user := GetPlayer(u)
		// 没有下注
		if user.totalBet < 1 {
			return true
		}
		ksync.GroupGo(wg, func() {
			afterGold, ok := rm.rpcPayoutGold(user, -user.totalBet, ReasonBet)
			if !ok {
				mt.Lock()
				failer = append(failer, user)
				mt.Unlock()
			} else {
				rm.GetService().RunOnce(func() {
					rm.setGold(user, afterGold)
				})
			}
		})

		return true
	})
	wg.Wait()

	// 移除扣钱失败玩家的投注信息
	for _, u := range failer {
		u.totalBet = 0
		for pos := range u.totalBets {
			u.totalBets[pos] = 0
		}
		rm.notifyPayoutFail(u, pb.ErrCode_GoldNotEnough)
	}
}
