//描述整个游戏，提供接口给外部使用，挂了，就全部挂
/**
## 游戏中的情况下，用户退出
使用延迟退出机制，游戏中的用户，不处理退出的事件，等这盘游戏结束后处理
如果没有游戏中的情况，则马上处理

*/

package qznn

import (
	"errors"
	"fmt"
	"math/rand"
	"runtime/debug"
	"strconv"
	"strings"
	"time"

	"github.com/bwmarrin/snowflake"
	"github.com/sasha-s/go-deadlock"
	"github.com/shopspring/decimal"
	"github.com/sirupsen/logrus"
	"gogs.daxia.dev/huanan/pkg.daxia.dev/db"
	"gorm.io/gorm"
	"nn.daxia.dev/gameproto"
	"nn.daxia.dev/model"
	"nn.daxia.dev/nxd"
	"nn.daxia.dev/online"
)

type kkMap map[string]interface{}
type PlayerStatus int
type RoomStatus int

var ErrNotFound = errors.New("record not found")
var ErrPwd = errors.New("password not correct")

const (
	RoomIDEmpty = int32(0)

	PlayerStatusWaitReady    PlayerStatus = 1 //等待准备
	PlayerStatusReady        PlayerStatus = 2 //已经准备
	PlayerStatusChooseMaster PlayerStatus = 3 //已经选择庄
	PlayerStatusChooseMul    PlayerStatus = 4 //已经选择倍数
	PlayerStatusOpen         PlayerStatus = 5 //已经开奖

	RoomStatusWaitReady    RoomStatus = 1 //等待准备
	RoomStatusReady        RoomStatus = 2 //已经有两个准备好,等待其他玩家准备
	RoomStatusChooseMaster RoomStatus = 3 //正在选择庄家
	RoomStatusChooseMul    RoomStatus = 4 //正在选择倍数
	RoomStatusOpen         RoomStatus = 5 //正在开奖
)

type Chair struct {
	ID       int32 //索引，1-5
	PlayerID int32 //用户ID
}

type Room struct {
	ID              int32           //房间ID
	Name            string          //名称
	Passwd          string          //密码
	RoomType        int32           //房间类型
	ChairList       []Chair         //桌子
	BaseAmount      decimal.Decimal //底金
	Issue           string          //当前期号
	Status          RoomStatus      //房间当前状态
	StatusStartTime int64           //房间当前状态开始的时间
	ReadyCh         chan bool       //当房间全部用户准备好，触发
	ChooseMasterCh  chan bool       //全部选择完庄，触发
	ChooseMulCh     chan bool       //全部选择完倍数，触发
	OpenCh          chan bool       //全部开牌完毕，触发
}

type Player struct {
	ID       int32
	Name     string
	HeadImg  string
	Balance  decimal.Decimal //余额
	Credits  decimal.Decimal //游戏积分
	RoomID   int32           //当前所在房间ID
	JoinTime time.Time       //加入时间
	Status   PlayerStatus

	IsMaster  bool       //是否为庄家
	MasterMul int        //抢庄的倍数
	Mul       int        //倍数
	CardList  NNCardList //牌
}

type Game struct {
	RoomMap   map[int32]Room
	PlayerMap map[int32]Player
	IDGener   *snowflake.Node
	LogID     int64
	//nextDelPlayerIDList *list.List //下一轮游戏开始会删，可能离线了
	locker deadlock.Mutex
}

var gameInstance *Game

func GetInstance() *Game {
	if gameInstance != nil {
		return gameInstance
	}

	node, err := snowflake.NewNode(1)
	if err != nil {
		logrus.Error(err)
		return nil
	}

	gameInstance = &Game{
		RoomMap:   map[int32]Room{},
		PlayerMap: map[int32]Player{},
		IDGener:   node,
		//nextDelPlayerIDList: list.New(),
		locker: deadlock.Mutex{},
	}

	gameInstance.Start()

	return gameInstance
}

func (p *Game) Start() {
	//初始化房间
	chairList := make([]Chair, 0)
	for i := 0; i < 5; i++ {
		chairList = append(chairList, Chair{
			ID:       int32(i),
			PlayerID: 0,
		})
	}

	roomID := 1

	//10个免费房间，10个收费房间
	for i := 0; i < 10; i++ {
		room := Room{
			ID:             int32(roomID),
			RoomType:       int32(model.RoomTypeFree),
			ChairList:      make([]Chair, 5),
			BaseAmount:     decimal.NewFromInt(3),
			Status:         RoomStatusWaitReady,
			ReadyCh:        make(chan bool),
			ChooseMasterCh: make(chan bool),
			ChooseMulCh:    make(chan bool),
			OpenCh:         make(chan bool),
		}

		copy(room.ChairList, chairList)

		p.RoomMap[int32(roomID)] = room

		roomID++
	}

	for i := 0; i < 10; i++ {
		room := Room{
			ID:             int32(roomID),
			RoomType:       int32(model.RoomTypeNormal),
			ChairList:      make([]Chair, 5),
			BaseAmount:     decimal.NewFromInt(3),
			Status:         RoomStatusWaitReady,
			ReadyCh:        make(chan bool),
			ChooseMasterCh: make(chan bool),
			ChooseMulCh:    make(chan bool),
			OpenCh:         make(chan bool),
		}

		copy(room.ChairList, chairList)

		p.RoomMap[int32(roomID)] = room

		roomID++
	}
}

func (p *Game) Connect(playerID int32) error {
	//处理房间
	userModel := model.User{}
	err := userModel.GetUserByID(uint32(playerID))
	if err != nil {
		logrus.Error(err)
		return err
	}

	p.locker.Lock()
	defer p.locker.Unlock()

	p.JoinNoLock(playerID)

	roomList, currPlayerList, logID := gameInstance.GetLogNoLock(int32(playerID))
	roomListProto := make([]*gameproto.Room, 0)
	userIDList := make([]uint32, 0)

	atRoom := false
	var currRoom Room
	for _, roomItem := range roomList {
		baseAmountDeciaml := roomItem.BaseAmount.Mul(decimal.NewFromInt(100))
		baseAmount := float64(baseAmountDeciaml.IntPart()) / 100
		roomListProto = append(roomListProto, &gameproto.Room{
			ID:           uint32(roomItem.ID),
			Name:         roomItem.Name,
			RoomType:     gameproto.RoomType(roomItem.RoomType),
			NeedPwd:      roomItem.Passwd != "",
			BaseAmount:   baseAmount,
			ChairUserID1: uint32(roomItem.ChairList[0].PlayerID),
			ChairUserID2: uint32(roomItem.ChairList[1].PlayerID),
			ChairUserID3: uint32(roomItem.ChairList[2].PlayerID),
			ChairUserID4: uint32(roomItem.ChairList[3].PlayerID),
			ChairUserID5: uint32(roomItem.ChairList[4].PlayerID),
		})

		for i := 0; i < 5; i++ {
			if roomItem.ChairList[i].PlayerID == 0 {
				continue
			}

			if roomItem.ChairList[i].PlayerID == int32(userModel.ID) {
				atRoom = true
				currRoom = roomItem
			}

			userIDList = append(userIDList, uint32(roomItem.ChairList[i].PlayerID))
		}
	}

	var roomDetail gameproto.RoomDetail
	if atRoom {
		roomItem := currRoom
		baseAmountDeciaml := roomItem.BaseAmount.Mul(decimal.NewFromInt(100))
		baseAmount := float64(baseAmountDeciaml.IntPart()) / 100

		roomDetail = gameproto.RoomDetail{
			ID:              uint32(roomItem.ID),
			Name:            roomItem.Name,
			RoomType:        gameproto.RoomType(roomItem.RoomType),
			NeedPwd:         roomItem.Passwd != "",
			BaseAmount:      baseAmount,
			ChairUserID1:    uint32(roomItem.ChairList[0].PlayerID),
			ChairUserID2:    uint32(roomItem.ChairList[1].PlayerID),
			ChairUserID3:    uint32(roomItem.ChairList[2].PlayerID),
			ChairUserID4:    uint32(roomItem.ChairList[3].PlayerID),
			ChairUserID5:    uint32(roomItem.ChairList[4].PlayerID),
			Status:          uint32(currRoom.Status),
			StatusStartTime: roomItem.StatusStartTime,
			RoomUserList:    make([]*gameproto.RoomUser, 0),
		}

		for _, player := range currPlayerList {
			balanceFloat, _ := player.Balance.Round(2).Float64()
			var cardList []uint32
			if player.Status == PlayerStatusOpen {
				cardList = GetNNCardList(player.CardList)
			}

			if player.ID == int32(userModel.ID) {
				//如果准备好了，就显示3张牌
				if player.Status != PlayerStatusWaitReady {
					cardList = GetNNCardListLimit(player.CardList, 3)
				}

				//如果选择了倍数，就返回5张
				if player.Status == PlayerStatusChooseMul {
					cardList = GetNNCardList(player.CardList)
				}
			}

			roomDetail.RoomUserList = append(roomDetail.RoomUserList, &gameproto.RoomUser{
				UserID:    uint32(player.ID),
				CardList:  cardList,
				IsMaster:  player.IsMaster,
				MasterMul: uint32(player.MasterMul),
				Mul:       uint32(player.Mul),
				Status:    uint32(player.Status),
				Balance:   balanceFloat,
			})
		}
	}

	balance, _ := strconv.ParseFloat(userModel.Balance.StringFixed(2), 64)

	logrus.Debugf("join game user id:%d", userModel.ID)

	var userListProto []*gameproto.User

	if len(userIDList) != 0 {
		var err error

		userListModel, err := (model.User{}).GetUserByIDList(userIDList)
		if err != nil {
			logrus.Error("failed:", err)
			return err
		}

		userListProto = make([]*gameproto.User, 0)
		for _, userModel := range userListModel {
			userListProto = append(userListProto, &gameproto.User{
				UserID: uint32(userModel.ID),
				Name:   userModel.Name,
				Img:    userModel.HeadImg,
			})
		}
	}

	connectProto := gameproto.Connect{
		UserID:     uint32(userModel.ID),
		LogID:      logID,
		Name:       userModel.Nickname,
		Img:        userModel.HeadImg,
		Balance:    balance,
		RoomList:   roomListProto,
		UserList:   userListProto,
		RoomDetail: &roomDetail,
	}

	UserActive(userModel.ID)
	nxd.SendMsgToUserK(uint32(userModel.ID), gameproto.NotifyTypeEnum_NotifyTypeConnect, &connectProto)

	logrus.Debugf("send connect user id:%d", userModel.ID)

	return nil
}

func (p *Game) Join(playerID int32) error {
	p.locker.Lock()
	defer p.locker.Unlock()

	return p.JoinNoLock(playerID)
}

func (p *Game) JoinNoLock(playerID int32) error {
	userModel := model.User{}
	err := userModel.GetUserByID(uint32(playerID))
	if err != nil {
		logrus.Error(err)
		return err
	}

	_, exists := p.PlayerMap[playerID]
	if exists {
		return nil
	}

	roomID := RoomIDEmpty
	room, _, err := p.getUserRoomID(playerID)
	if err == nil {
		roomID = room.ID
	}

	player := Player{
		ID:       playerID,
		Name:     userModel.Name,
		HeadImg:  userModel.HeadImg,
		Balance:  userModel.Balance,
		Credits:  decimal.NewFromInt(0),
		RoomID:   roomID,
		Status:   PlayerStatusWaitReady,
		JoinTime: time.Now(),
	}

	p.PlayerMap[playerID] = player

	p.EventInfo(playerID, "加入游戏", "加入游戏")

	return nil
}

func (p *Game) Leave(playerID int32) error {
	p.locker.Lock()
	defer p.locker.Unlock()

	playerItem, exists := p.PlayerMap[playerID]
	if !exists {
		p.EventInfo(playerID, "退出房间", "用户不存在")
		return nil
	}

	if playerItem.Status != PlayerStatusWaitReady {
		p.EventInfo(playerID, "退出房间", fmt.Sprintf("用户不是等待开始状态,无法退出,状态:%d", playerItem.Status))
		return nil
	}

	delete(p.PlayerMap, playerID)

	for _, roomItem := range p.RoomMap {
		for index, chairItem := range roomItem.ChairList {
			if chairItem.PlayerID != playerID {
				continue
			}

			chairItem.PlayerID = 0
			roomItem.ChairList[index] = chairItem

			p.EventInfo(playerID, "退出房间", "清理桌子信息")

			//发信息，表示离开
			nxd.SendMsgToAllUserK(gameproto.NotifyTypeEnum_NotifyTypeLeaveRoom, &gameproto.LeaveRoom{
				RoomID:  uint32(roomItem.ID),
				ChairID: uint32(chairItem.ID),
				UserID:  uint32(playerID),
			})

			return nil
		}
	}

	p.EventInfo(playerID, "退出房间", "清理用户信息")

	return nil
}

func (p *Game) ChooseRndChair(playerID int32) error {
	p.locker.Lock()

	p.EventInfo(playerID, "随机选桌子", "开始")

	maxRoomID := -1
	maxChairID := -1
	maxUserCount := -1
	for _, roomItem := range p.RoomMap {
		if roomItem.Passwd != "" {
			continue
		}

		userCount := 0
		for _, chairItem := range roomItem.ChairList {
			if chairItem.PlayerID == 0 {
				continue
			}

			userCount++
		}

		if userCount == 4 {
			continue
		}

		if userCount > maxUserCount {
			maxUserCount = userCount
			maxRoomID = int(roomItem.ID)
			for _, chairItem := range roomItem.ChairList {
				if chairItem.PlayerID != 0 {
					continue
				}

				maxChairID = int(chairItem.ID)
				break
			}
		}
	}

	if maxRoomID != -1 && maxChairID != -1 {
		p.EventInfo(playerID, "随机选桌子", fmt.Sprintf("找到桌子:%d-%d", maxRoomID, maxChairID))

		p.locker.Unlock()
		return p.ChooseChair(playerID, int32(maxRoomID), int32(maxChairID), "")
	}

	p.EventInfo(playerID, "随机选桌子", "没有找到桌子")

	p.locker.Unlock()

	return ErrNotFound
}

func (p *Game) ChooseChair(playerID int32, roomID int32, chairID int32, passwd string) error {
	p.locker.Lock()
	defer p.locker.Unlock()

	p.EventInfo(playerID, "选桌子", "开始")

	err := p.ChooseChairNoLock(playerID, roomID, chairID, passwd)
	if err != nil {
		p.EventInfo(playerID, "选桌子", "选择失败:"+err.Error())
		return err
	}

	p.EventInfo(playerID, "选桌子", "选取成功")
	return nil
}

func (p *Game) ChooseChairNoLock(playerID int32, roomID int32, chairID int32, passwd string) error {
	logrus.Info("ChooseChair...")

	playerItem, exists := p.PlayerMap[playerID]
	if !exists {
		p.EventInfo(playerID, "选桌子-inner", "用户不存在")
		return errors.New("用户不存在")
	}

	//离开房间
	if roomID == 0 {
		p.EventInfo(playerID, "选桌子-inner", "退出房间")

		for rKey, room := range p.RoomMap {
			for cKey, chairItem := range room.ChairList {
				if chairItem.PlayerID != playerID {
					continue
				}

				//设置为0
				room.ChairList[cKey].PlayerID = 0
				p.RoomMap[rKey] = room

				playerItem.RoomID = 0
				playerItem.Status = PlayerStatusWaitReady
				playerItem.IsMaster = false
				playerItem.MasterMul = 1
				playerItem.Mul = 1

				p.PlayerMap[playerID] = playerItem

				p.EventInfo(playerID, "选桌子-inner", "退出完成")

				//通知
				nxd.SendMsgToAllUserK(gameproto.NotifyTypeEnum_NotifyTypeLeaveRoom, &gameproto.LeaveRoom{
					RoomID:  uint32(room.ID),
					ChairID: uint32(chairItem.ID),
					UserID:  uint32(playerID),
					User: &gameproto.User{
						UserID: uint32(playerItem.ID),
						Name:   playerItem.Name,
						Img:    playerItem.HeadImg,
					},
				})
				return nil
			}
		}
		return nil
	}

	p.EventInfo(playerID, "选桌子-inner", fmt.Sprintf("寻找桌子，房间ID:%d", roomID))

	//同一个房间，切换，不影响
	if playerItem.RoomID != 0 {
		if playerItem.RoomID != roomID && playerItem.RoomID != RoomIDEmpty {
			p.EventInfo(playerID, "选桌子-inner", fmt.Sprintf("没退出房间，就进入新的房间，不允许，之前房间:%d，进入房间:%d", playerItem.RoomID, roomID))

			logrus.Error("没退出房间，就进入新的房间，不允许")
			return errors.New("没退出房间，就进入新的房间，不允许")
		}
	}

	roomItem, exists := p.RoomMap[roomID]
	if !exists {
		p.EventInfo(playerID, "选桌子-inner", fmt.Sprintf("房间不存，房间ID:%d", roomID))
		return ErrNotFound
	}

	if roomItem.Passwd != passwd {
		p.EventInfo(playerID, "选桌子-inner", fmt.Sprintf("房间密码不对，输入密码:%s", passwd))
		return errors.New("密码不正确")
	}

	//椅子不确定，选一个
	if chairID == -1 {
		p.EventInfo(playerID, "选桌子-inner", "随机选取桌子")

		//选一次，判断是否已经在房间，是的话，直接返回之前的桌子
		for _, chair := range roomItem.ChairList {
			if chair.PlayerID == playerItem.ID {
				chairID = chair.ID
				break
			}
		}

		if chairID == -1 {
			for _, chair := range roomItem.ChairList {
				if chair.PlayerID == 0 {
					chairID = chair.ID
					break
				}
			}
		}
	}

	p.EventInfo(playerID, "选桌子-inner", fmt.Sprintf("选取的桌子:%d", chairID))

	if chairID == -1 {
		p.EventInfo(playerID, "选桌子-inner", "房间已经满人了")
		return errors.New("房间已经满人了")
	}

	if int(chairID) >= len(roomItem.ChairList) {
		p.EventInfo(playerID, "选桌子-inner", fmt.Sprintf("chair id 超过范围:%d", chairID))
		return errors.New("chair id 超过范围")
	}

	//够底注才能进
	if playerItem.Balance.LessThan(roomItem.BaseAmount) {
		p.EventInfo(playerID, "选桌子-inner", fmt.Sprintf("余额不足,无法进入房间,余额:%v,限额:%v", playerItem.Balance, roomItem.BaseAmount))

		return errors.New("余额不足,无法进入房间")
	}

	chairItem := roomItem.ChairList[chairID]
	if chairItem.PlayerID == playerID {
		p.EventInfo(playerID, "选桌子-inner", "已经在桌子上")
		return nil
	}

	if chairItem.PlayerID != 0 {
		p.EventInfo(playerID, "选桌子-inner", fmt.Sprintf("桌子上有其他人:%d", chairItem.PlayerID))
		return errors.New("有人了")
	}

	roomItem.ChairList[chairID] = Chair{
		ID:       chairID,
		PlayerID: playerID,
	}

	playerItem.RoomID = roomID
	playerItem.Status = PlayerStatusWaitReady
	playerItem.IsMaster = false
	playerItem.MasterMul = 1
	playerItem.Mul = 1
	p.PlayerMap[playerID] = playerItem

	p.EventInfo(playerID, "选桌子-inner", fmt.Sprintf("选完桌子:%d", chairID))

	//通知,房间外的用户收到用户进入信息，房间内的，会带上房间详情
	userIDList := online.GetAllUser()
	roomUserIDlist := make([]int32, 0)
	notInRoomUserIDlist := make([]int32, 0)
	for _, userID := range userIDList {
		currPlayer, exists := p.PlayerMap[int32(userID)]
		if !exists {
			continue
		}

		if currPlayer.RoomID == roomID {
			roomUserIDlist = append(roomUserIDlist, int32(userID))
		} else {
			notInRoomUserIDlist = append(notInRoomUserIDlist, int32(userID))
		}
	}

	p.EventInfo(playerID, "选桌子-inner", "通知房间外的用户，有人进入房间")

	nxd.SendMsgToUserList(notInRoomUserIDlist, gameproto.NotifyTypeEnum_NotifyTypeJoinRoom, &gameproto.JoinRoom{
		RoomID:     uint32(roomID),
		ChairID:    uint32(chairID),
		UserID:     uint32(playerID),
		GameStatus: uint32(roomItem.Status),
		User:       &gameproto.User{UserID: uint32(playerItem.ID), Name: playerItem.Name, Img: playerItem.HeadImg},
	})

	baseAmountDeciaml := roomItem.BaseAmount.Mul(decimal.NewFromInt(100))
	baseAmount := float64(baseAmountDeciaml.IntPart()) / 100

	roomDetail := gameproto.RoomDetail{
		ID:              uint32(roomItem.ID),
		Name:            roomItem.Name,
		RoomType:        gameproto.RoomType(roomItem.RoomType),
		NeedPwd:         roomItem.Passwd != "",
		BaseAmount:      baseAmount,
		ChairUserID1:    uint32(roomItem.ChairList[0].PlayerID),
		ChairUserID2:    uint32(roomItem.ChairList[1].PlayerID),
		ChairUserID3:    uint32(roomItem.ChairList[2].PlayerID),
		ChairUserID4:    uint32(roomItem.ChairList[3].PlayerID),
		ChairUserID5:    uint32(roomItem.ChairList[4].PlayerID),
		Status:          uint32(roomItem.Status),
		StatusStartTime: roomItem.StatusStartTime,
		RoomUserList:    make([]*gameproto.RoomUser, 0),
	}

	currPlayerList := make([]Player, 0)
	for _, chairItem := range roomItem.ChairList {
		if chairItem.PlayerID == 0 {
			continue
		}

		currPlayer, exists := p.PlayerMap[chairItem.PlayerID]
		if !exists {
			continue
		}

		currPlayerList = append(currPlayerList, currPlayer)
	}

	for _, player := range currPlayerList {
		balanceFloat, _ := player.Balance.Round(2).Float64()

		var cardList []uint32
		if player.Status == PlayerStatusOpen {
			p.EventInfo(player.ID, "选桌子-inner", "玩家开牌状态,带上牌的信息")

			cardList = GetNNCardList(player.CardList)
		}

		roomDetail.RoomUserList = append(roomDetail.RoomUserList, &gameproto.RoomUser{
			UserID:    uint32(player.ID),
			CardList:  cardList,
			IsMaster:  player.IsMaster,
			MasterMul: uint32(player.MasterMul),
			Mul:       uint32(player.Mul),
			Status:    uint32(player.Status),
			Balance:   balanceFloat,
		})
	}

	p.EventInfo(playerID, "选桌子-inner", "通知房间内的用户，有人进入房间，带上房间详情")

	nxd.SendMsgToUserList(roomUserIDlist, gameproto.NotifyTypeEnum_NotifyTypeJoinRoom, &gameproto.JoinRoom{
		RoomID:     uint32(roomID),
		ChairID:    uint32(chairID),
		UserID:     uint32(playerID),
		GameStatus: uint32(roomItem.Status),
		User:       &gameproto.User{UserID: uint32(playerItem.ID), Name: playerItem.Name, Img: playerItem.HeadImg},
		RoomDetail: &roomDetail,
	})

	return nil
}

func (p *Game) CreateRoom(playerID int32, roomType int32, passwd, name string, baseAmount int) (*Room, error) {
	if roomType != int32(model.RoomTypeFree) && roomType != int32(model.RoomTypeNormal) {
		return nil, errors.New("参数错误,房间类型不对")
	}

	if len(passwd) < 3 {
		return nil, errors.New("参数错误,密码长度不能少于3")
	}

	p.locker.Lock()
	defer p.locker.Unlock()

	p.EventInfo(playerID, "创建房间", "开始")

	baseAmountDecimal := decimal.NewFromInt(int64(baseAmount))

	playerItem := p.PlayerMap[playerID]
	roomID := len(p.RoomMap) + 1

	if playerItem.Balance.LessThan(baseAmountDecimal) {
		p.EventInfo(playerID, "创建房间", fmt.Sprintf("余额小于房间准入金额, %v-%v", playerItem.Balance, baseAmountDecimal))

		return nil, errors.New("参数错误,余额小于房间准入金额")
	}

	room := Room{
		ID:              int32(roomID),
		RoomType:        roomType,
		Name:            name,
		Passwd:          passwd,
		ChairList:       make([]Chair, 5),
		BaseAmount:      baseAmountDecimal,
		Status:          RoomStatusWaitReady,
		StatusStartTime: 0,
		ReadyCh:         make(chan bool),
		ChooseMasterCh:  make(chan bool),
		ChooseMulCh:     make(chan bool),
		OpenCh:          make(chan bool),
	}

	chairList := make([]Chair, 0)
	for i := 0; i < 5; i++ {
		chairList = append(chairList, Chair{
			ID:       int32(i),
			PlayerID: 0,
		})
	}

	copy(room.ChairList, chairList)

	p.RoomMap[int32(roomID)] = room

	p.EventInfo(playerID, "创建房间", "通知创建房间成功")

	//通知
	nxd.SendMsgToAllUserK(gameproto.NotifyTypeEnum_NotifyTypeCreateRoom, &gameproto.CreateRoom{
		ID:           uint32(room.ID),
		CreatorID:    uint32(playerID),
		Name:         room.Name,
		NeedPwd:      passwd != "",
		RoomType:     gameproto.RoomType(room.RoomType),
		BaseAmount:   float64(room.BaseAmount.Mul(decimal.NewFromInt(100)).IntPart()) / 100,
		ChairUserID1: 0,
		ChairUserID2: 0,
		ChairUserID3: 0,
		ChairUserID4: 0,
		ChairUserID5: 0,
	})

	p.EventInfo(playerID, "创建房间", "选择桌子")

	err := p.ChooseChairNoLock(playerID, int32(roomID), 0, passwd)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}

	return &room, nil
}

func (p *Game) Ready(playerID int32) error {
	p.locker.Lock()
	defer p.locker.Unlock()

	playerItem := p.PlayerMap[playerID]
	if playerItem.RoomID == RoomIDEmpty {
		p.EventInfo(playerID, "准备", "用户不在房间")

		return errors.New("没进入房间")
	}

	if playerItem.Status != PlayerStatusWaitReady {
		p.EventInfo(playerID, "准备", fmt.Sprintf("用户状态不对:%d", playerItem.Status))

		return errors.New("状态不对")
	}

	roomItem := p.RoomMap[playerItem.RoomID]
	if roomItem.Status != RoomStatusWaitReady && roomItem.Status != RoomStatusReady {
		p.EventInfo(playerID, "准备", fmt.Sprintf("房间状态不对:%d", roomItem.Status))

		return errors.New("房间状态不对")
	}

	playerItem.Status = PlayerStatusReady
	p.PlayerMap[playerID] = playerItem

	//通知
	userIDList, err := p.getRoomUserIDList(roomItem.ID)
	if err != nil {
		logrus.Error(err)
		return err
	}

	p.EventInfo(playerID, "准备", "通知房间用户，已经准备好")

	nxd.SendMsgToUserList(userIDList, gameproto.NotifyTypeEnum_NotifyTypeReady, &gameproto.Ready{
		RoomID: uint32(roomItem.ID),
		UserID: uint32(playerID),
	})

	readyUserCount := 0
	allReady := true
	for _, chairItem := range roomItem.ChairList {
		if chairItem.PlayerID == 0 {
			continue
		}

		chairPlayer := p.PlayerMap[chairItem.PlayerID]

		if chairPlayer.Status != PlayerStatusReady && chairPlayer.Status != PlayerStatusWaitReady {
			logrus.Panicf("-状态不对:%d", chairPlayer.Status)
		}

		if chairPlayer.Status != PlayerStatusReady {
			allReady = false
			continue
		}

		readyUserCount++
	}

	if readyUserCount <= 1 {
		p.EventInfo(playerID, "准备", "已经准备的用户<=1")
		return nil
	}

	//两个人以上的情况，判断是否全部准备好
	if allReady {
		p.EventInfo(playerID, "准备", "全部准备好，发送通知")
		go func() {
			roomItem.ReadyCh <- true
		}()
	}

	if roomItem.Status == RoomStatusReady {
		p.EventInfo(playerID, "准备", "房间已经是准备状态")

		return nil
	}

	//有两个玩家等待，房间状态切换为准备状态，不能取消了
	issue := genIssue(playerItem.RoomID)
	chairID, err := p.getChairID(playerID, roomItem)
	if err != nil {
		logrus.Error(err)
		return err
	}

	roomItem.Issue = issue
	roomItem.Status = RoomStatusReady
	roomItem.StatusStartTime = time.Now().Unix()

	p.RoomMap[playerItem.RoomID] = roomItem

	p.EventInfo(playerID, "准备", "开始游戏,期号:"+issue)
	nxd.SendMsgToUserList(userIDList, gameproto.NotifyTypeEnum_NotifyTypeWaitReady, &gameproto.WaitReady{
		RoomID: uint32(roomItem.ID),
	})

	go func() {
		defer func() {
			if err := recover(); err != nil {
				logrus.Error(err)
				fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
			}
		}()

		if !allReady {
			p.locker.Lock()
			{
				p.EventInfo(playerID, "准备", fmt.Sprintf("等待房间准备:%d", roomItem.ID))
			}
			p.locker.Unlock()

			p.WaitReady(roomItem)

			p.locker.Lock()
			{
				p.EventInfo(playerID, "准备", "等待房间完毕")
			}
			p.locker.Unlock()
		}

		func() {
			p.locker.Lock()
			defer p.locker.Unlock()

			userIDList, err = p.getRoomUserIDList(roomItem.ID)
			if err != nil {
				logrus.Error(err)
				return
			}
		}()

		err = db.GetDB().Transaction(func(tx *gorm.DB) error {
			for _, userID := range userIDList {
				openNumCardList := getOpenNumList(issue, roomItem.ID, chairID)
				openNumCardStrList := make([]string, 0)
				for _, item := range openNumCardList {
					openNumCardStrList = append(openNumCardStrList, fmt.Sprintf("%d", item.Num))
				}

				userItem := p.PlayerMap[userID]
				userItem.CardList = CardList2NNCardList(openNumCardList)

				p.PlayerMap[userID] = userItem

				gameOrderModel := model.GameOrder{
					UserID:     uint(userID),
					RoomID:     uint(roomItem.ID),
					Issue:      issue,
					OpenNumber: strings.Join(openNumCardStrList, ","),
					BaseAmount: roomItem.BaseAmount,
					IsDraw:     0,
					DrawTime:   nil,
					IsMaster:   0,
					MasterMul:  0,
					Mul:        1,
					IsWin:      0,
					WinAmount:  decimal.NewFromInt(0),
					BetIP:      "",
					GameID:     1,
				}

				err = tx.Model(gameOrderModel).Create(&gameOrderModel).Error
				if err != nil {
					logrus.Error(err)
					return err
				}
			}
			return nil
		})

		if err != nil {
			p.locker.Lock()
			{
				p.EventInfo(playerID, "准备", "生成订单成功")
			}
			p.locker.Unlock()

			logrus.Error(err)
			return
		}

		p.Run(allReady, roomItem, issue)
	}()

	return nil
}

func (p *Game) UnReady(playerID int32) error {
	p.locker.Lock()
	defer p.locker.Unlock()

	playerItem := p.PlayerMap[playerID]
	if playerItem.RoomID == RoomIDEmpty {
		return errors.New("没进入房间")
	}

	if playerItem.Status != PlayerStatusReady {
		return errors.New("状态不对")
	}

	roomItem := p.RoomMap[playerItem.RoomID]
	if roomItem.Status != RoomStatusWaitReady {
		return errors.New("状态不对")
	}

	readyUserCount := 0
	for _, chairItem := range roomItem.ChairList {
		if chairItem.PlayerID == 0 {
			continue
		}

		chairPlayer := p.PlayerMap[chairItem.PlayerID]
		if chairPlayer.Status != PlayerStatusReady {
			continue
		}

		readyUserCount++
	}

	//设置用户
	playerItem.Status = PlayerStatusWaitReady
	p.PlayerMap[playerID] = playerItem

	//通知
	userIDList, err := p.getRoomUserIDList(roomItem.ID)
	if err != nil {
		logrus.Error(err)
		return err
	}

	nxd.SendMsgToUserList(userIDList, gameproto.NotifyTypeEnum_NotifyTypeUnReady, &gameproto.UnReady{
		RoomID: uint32(roomItem.ID),
		UserID: uint32(playerID),
	})

	return nil
}

func (p *Game) ChooseMaster(playerID int32, mul int32) error {
	p.locker.Lock()
	defer p.locker.Unlock()

	playerItem := p.PlayerMap[playerID]
	if playerItem.RoomID == RoomIDEmpty {
		return errors.New("没进入房间")
	}

	if playerItem.Status != PlayerStatusReady {
		return errors.New("状态不对")
	}

	roomItem := p.RoomMap[playerItem.RoomID]
	if roomItem.Status != RoomStatusChooseMaster {
		return errors.New("状态不对")
	}

	//更新用户信息
	playerItem.Status = PlayerStatusChooseMaster
	playerItem.MasterMul = int(mul)

	p.PlayerMap[playerID] = playerItem

	allChoose := true
	for _, chairItem := range roomItem.ChairList {
		if chairItem.PlayerID == 0 {
			continue
		}

		chairPlayer := p.PlayerMap[chairItem.PlayerID]
		if chairPlayer.Status == PlayerStatusReady {
			allChoose = false
			break
		}
	}

	err := db.GetDB().Model(model.GameOrder{}).Where("user_id = ? and issue = ?", playerID, roomItem.Issue).Update("master_mul", mul).Error
	if err != nil {
		logrus.Error(err)
		return err
	}

	//通知
	userIDList, err := p.getRoomUserIDList(roomItem.ID)
	if err != nil {
		logrus.Error(err)
		return err
	}

	for _, userID := range userIDList {
		nxd.SendMsgToUserK(uint32(userID), gameproto.NotifyTypeEnum_NotifyTypeChooseMaster, &gameproto.ChooseMaster{
			UserID: uint32(playerID),
			Mul:    uint32(mul),
			RoomID: uint32(roomItem.ID),
		})
	}

	//全部选完，就进入下一阶段
	if allChoose {
		go func() {
			roomItem.ChooseMasterCh <- true
		}()
	}

	return nil
}

func (p *Game) ChooseMul(playerID int32, mul int32) error {
	p.locker.Lock()
	defer p.locker.Unlock()

	playerItem := p.PlayerMap[playerID]
	if playerItem.RoomID == RoomIDEmpty {
		return errors.New("没进入房间")
	}

	if playerItem.Status != PlayerStatusChooseMaster {
		return errors.New("状态不对")
	}

	roomItem := p.RoomMap[playerItem.RoomID]
	if roomItem.Status != RoomStatusChooseMul {
		return errors.New("状态不对")
	}

	//更新用户
	playerItem.Status = PlayerStatusChooseMul
	playerItem.Mul = int(mul)
	p.PlayerMap[playerID] = playerItem

	allChoose := true
	for _, chairItem := range roomItem.ChairList {
		if chairItem.PlayerID == 0 {
			continue
		}

		chairPlayer := p.PlayerMap[chairItem.PlayerID]
		if chairPlayer.Status == PlayerStatusChooseMaster {
			allChoose = false
			break
		}
	}

	err := db.GetDB().Model(model.GameOrder{}).Where("user_id = ? and issue = ?", playerID, roomItem.Issue).Update("mul", mul).Error
	if err != nil {
		logrus.Error(err)
		return err
	}

	//通知
	userIDList, err := p.getRoomUserIDList(roomItem.ID)
	if err != nil {
		logrus.Error(err)
		return err
	}

	for _, sendToUserID := range userIDList {
		var cardList []uint32
		if playerID == sendToUserID {
			cardList = GetNNCardList(playerItem.CardList)
		}

		nxd.SendMsgToUserK(uint32(sendToUserID), gameproto.NotifyTypeEnum_NotifyTypeChooseMul, &gameproto.ChooseMul{
			UserID:   uint32(playerID),
			Mul:      uint32(mul),
			RoomID:   uint32(roomItem.ID),
			CardList: cardList,
		})
	}

	//全部选完，就进入下一阶段
	if allChoose {
		go func() {
			roomItem.ChooseMulCh <- true
		}()
	}

	return nil
}

func (p *Game) OpenCard(playerID int32) error {
	p.locker.Lock()
	defer p.locker.Unlock()

	playerItem := p.PlayerMap[playerID]
	if playerItem.RoomID == RoomIDEmpty {
		return errors.New("没进入房间")
	}

	if playerItem.Status != PlayerStatusChooseMul {
		return errors.New("状态不对")
	}

	roomItem := p.RoomMap[playerItem.RoomID]
	if roomItem.Status != RoomStatusOpen {
		return errors.New("状态不对")
	}

	userIDList, err := p.getRoomUserIDList(roomItem.ID)
	if err != nil {
		logrus.Error(err)
		return err
	}

	nxd.SendMsgToUserList(userIDList, gameproto.NotifyTypeEnum_NotifyTypeOpen, &gameproto.Open{
		RoomID:   uint32(roomItem.ID),
		UserID:   uint32(playerID),
		CardList: GetNNCardList(playerItem.CardList),
	})

	//更新用户
	playerItem.Status = PlayerStatusOpen
	p.PlayerMap[playerID] = playerItem

	allOpen := true
	for _, chairItem := range roomItem.ChairList {
		if chairItem.PlayerID == 0 {
			continue
		}

		chairPlayer := p.PlayerMap[chairItem.PlayerID]
		if chairPlayer.Status == PlayerStatusChooseMul {
			allOpen = false
			break
		}
	}

	//全部选完，就进入下一阶段
	if allOpen {
		go func() {
			roomItem.OpenCh <- true
		}()
	}

	return nil
}

func (p *Game) getUserRoomID(playerID int32) (*Room, int32, error) {
	for _, roomItem := range p.RoomMap {
		for _, chairItem := range roomItem.ChairList {
			if chairItem.PlayerID != playerID {
				continue
			}

			return &roomItem, chairItem.ID, nil
		}
	}

	return nil, 0, ErrNotFound
}

func (p *Game) getChairID(playerID int32, room Room) (int32, error) {
	for i, chairID := range room.ChairList {
		if playerID == chairID.PlayerID {
			return int32(i), nil
		}
	}

	return 0, ErrNotFound
}

func (p *Game) GetLogNoLock(playerID int32) ([]Room, []Player, int64) {
	roomList := make([]Room, 0)
	playerList := make([]Player, 0)

	for _, roomItem := range p.RoomMap {
		roomList = append(roomList, roomItem)

		found := false
		for _, chairItem := range roomItem.ChairList {
			if chairItem.PlayerID != playerID {
				continue
			}

			found = true
			break
		}

		if !found {
			continue
		}

		for _, chairItem := range roomItem.ChairList {
			if chairItem.PlayerID == 0 {
				continue
			}

			player := p.PlayerMap[chairItem.PlayerID]
			playerList = append(playerList, player)
		}
	}

	return roomList, playerList, p.LogID
}

func genIssue(roomID int32) string {
	return fmt.Sprintf("%d-%d", roomID, time.Now().Unix())
}

func getOpenNumList(issue string, roomID, chairID int32) []Card {
	cardList := make([]Card, 0)
	for c := 0; c < 5; c++ {
		cardList = append(cardList, Card{
			Suit: rand.Intn(4) + 1,
			Num:  rand.Intn(13) + 1,
		})
	}

	return cardList
}
