package fishing

import (
	"fmt"
	"mongo.games.com/game/common"
	"mongo.games.com/game/gamerule/fishing"
	"mongo.games.com/game/gamesrv/base"
	"mongo.games.com/game/model"
	"mongo.games.com/game/proto"
	server_proto "mongo.games.com/game/protocol/server"
	"mongo.games.com/game/srvdata"
	"mongo.games.com/goserver/core/basic"
	"mongo.games.com/goserver/core/task"
	"os"
	"strconv"
	"time"
)

type FishingPlayerEvent struct {
	FishId    int32   // 唯一标识 PolicyId*1000000 + int32(value.GetId())*100 + int32(index+1)
	Event     int32   // 鱼触发的事件id
	Power     int32   // 炮台倍率
	Ts        int64   // 时间戳
	DropCoin  int32   // 鱼掉落金币
	ExtFishId []int32 // 相关鱼的鱼标识
}

type FishingPlayerData struct {
	*base.Player
	scene            *base.Scene                    // 玩家当前所在场景
	LastFireTime     time.Time                      // 用来计时，3分钟不在线踢出房间
	power            int32                          // 炮台倍率
	CoinCache        int64                          // 实时余额
	bullet           map[int32]*Bullet              // 子弹
	BulletLimit      [BULLETLIMIT]int64             // 检测开枪频率过大
	fishEvent        map[string]*FishingPlayerEvent // 事件标识：事件
	LostCoin         int64                          // 进场之后消耗的金币（子弹消耗）
	LostCoinCache    int64                          // 保存游戏记录时的消耗金币，保存一次刷新一次，用来计算上次保存记录后消耗了多少金币
	AgentParam       int32                          // 机器人该字段表示代理玩家的id；玩家该字段表示其代理的其中一个机器人的id
	RobotSnIds       []int32                        // 真实玩家绑定的机器人数组
	AutoFishing      int32                          // 自动标记
	SelTarget        int32                          // 瞄准数据？
	TargetFish       int32                          // 瞄准的鱼标识？
	FireRate         int32                          // 开炮频率
	taxCoin          float64                        // 开炮税收
	sTaxCoin         float64                        // 开炮税收（保存游戏记录时清零）
	enterTime        time.Time                      // 玩家入场时间
	SelVip           int32                          // 玩家选择的vip炮等级
	powerType        int32                          // 玩家炮台的类型
	FreePowerNum     int32                          // 免费炮台炮弹的数量
	winCoin          int64                          // 入场后总赢分
	realOdds         int                            // 玩家的实际赔率？
	logBulletHitNums int32                          // 命中计数，保存记录时清零
	logFishCount     map[int64]*model.FishCoinNum   // key:鱼id和子弹倍率；游戏记录
	EnterCoin        int64                          // 进场金币 统计使用

	lockFishCount map[int32]int32 // 鱼id对应打死的数量（天天捕鱼）
	jackpotCoin   float64         // 用来收集不足1的金币（天天捕鱼）
	Prana         float64         // 蓄能能量（天天捕鱼）
	PranaPercent  int32           // 蓄能能量百分比（天天捕鱼）
	MaxCoin       int64           // 游戏场最大金币（天天捕鱼）
	MinCoin       int64           // 游戏场最少金币（天天捕鱼）
	ExtraCoin     int64           // 除捕鱼外额外获得的金币（天天捕鱼）
	TestHitNum    int64           // 测试碰撞次数（天天捕鱼）
	SkillCd       map[int32]int64 //技能CD
	SkillGCD      map[int32]int64 //技能公共CD
	//SkillMutex    sync.Mutex
}

/*
更新免费炮台相关状态
*/
func (this *FishingPlayerData) UpdateFreePowerState() {
	this.powerType = FreePowerType
	this.FreePowerNum = 100
}

/*
更新成普通炮台相关状态
*/
func (this *FishingPlayerData) UpdateNormalPowerState() {
	this.powerType = NormalPowerType
	this.FreePowerNum = 0
}

/*
更新成钻头炮台相关状态
*/
func (this *FishingPlayerData) UpdateBitPowerState() {
	this.powerType = BitPowerType
}

func (this *FishingPlayerData) MakeLogKey(fishTemplateId, power int32) int64 {
	return common.MakeI64(fishTemplateId, power)
}

func (this *FishingPlayerData) SplitLogKey(key int64) (int32, int32) {
	return common.LowI32(key), common.HighI32(key)
}

func (this *FishingPlayerData) init(s *base.Scene) {
	//this.Player.extraData = this
	this.Player.SetExtraData(this)
	this.LostCoin = 0
	this.LostCoinCache = 0
	this.power = s.GetDBGameFree().GetIntuseCannonMin()
	this.AgentParam = 0
	this.bullet = make(map[int32]*Bullet)
	this.BulletLimit = [BULLETLIMIT]int64{}
	this.fishEvent = make(map[string]*FishingPlayerEvent)
	this.logBulletHitNums = 0
	this.lockFishCount = make(map[int32]int32)
	this.logFishCount = make(map[int64]*model.FishCoinNum)
	this.jackpotCoin = 0.0
	this.TestHitNum = 0
	this.scene = s
	this.SkillGCD = make(map[int32]int64)
	this.SkillCd = make(map[int32]int64)
	this.LastFireTime = time.Now()
	if this.GDatas == nil {
		this.GDatas = make(map[string]*model.PlayerGameInfo)
	}
	if !s.GetTesting() && !this.IsRob {
		key := s.KeyGamefreeId
		var pgd *model.PlayerGameInfo
		if data, exist := this.GDatas[key]; !exist {
			pgd = new(model.PlayerGameInfo)
			this.GDatas[key] = pgd
		} else {
			pgd = data
		}
		if pgd != nil {
			//参数确保
			for i := len(pgd.Data); i < GDATAS_HPFISHING_MAX; i++ {
				pgd.Data = append(pgd.Data, 0)
			}
			this.Prana = float64(pgd.Data[GDATAS_HPFISHING_PRANA])
			//this.SelVip = int32(pgd.Data[GDATAS_FISHING_SELVIP])
		}
		////////////////////////
		var npgd *model.PlayerGameInfo
		if ndata, exist := this.GDatas[s.KeyGameId]; !exist {
			npgd = new(model.PlayerGameInfo)
			this.GDatas[s.KeyGameId] = npgd
		} else {
			npgd = ndata
		}
		if npgd != nil {
			//参数确保
			for i := len(npgd.Data); i < GDATAS_HPFISHING_MAX; i++ {
				npgd.Data = append(npgd.Data, 0)
			}
			this.SelVip = int32(npgd.Data[GDATAS_FISHING_SELVIP])
		}
	}
	this.Clean()
}

func (this *FishingPlayerData) SetSelVip(keyGameId string) {
	if pgd, ok := this.GDatas[keyGameId]; ok {
		pgd.Data[GDATAS_FISHING_SELVIP] = int64(this.SelVip)
	}
}

func (this *FishingPlayerData) Clean() {
	this.bullet = make(map[int32]*Bullet)
	this.BulletLimit = [BULLETLIMIT]int64{}
	this.fishEvent = make(map[string]*FishingPlayerEvent)
	this.logBulletHitNums = 0
	this.bullet = make(map[int32]*Bullet)
	this.logFishCount = make(map[int64]*model.FishCoinNum)
}

func (this *FishingPlayerData) CoinCheck(power int32) bool {
	return this.CoinCache >= int64(power)
}

func (this *FishingPlayerData) CurrentCoin() int64 {
	return this.CoinCache
}

func (this *FishingPlayerData) GetTodayGameData(gameId string) *model.PlayerGameStatics {
	if this.TodayGameData == nil {
		this.TodayGameData = model.NewPlayerGameCtrlData()
	}
	if this.TodayGameData.CtrlData == nil {
		this.TodayGameData.CtrlData = make(map[string]*model.PlayerGameStatics)
	}
	if _, ok := this.TodayGameData.CtrlData[gameId]; !ok {
		this.TodayGameData.CtrlData[gameId] = model.NewPlayerGameStatics()
	}
	return this.TodayGameData.CtrlData[gameId]
}

/*
设置当天数据
*/
func (this *FishingPlayerData) SetTodayGameDate(gameId string, playerGameStatics *model.PlayerGameStatics) {
	this.TodayGameData.CtrlData[gameId] = playerGameStatics
}

/*
获取昨日得当天数据集合
*/
func (this *FishingPlayerData) GetYestDayGameData(gameId string) *model.PlayerGameStatics {
	if this.YesterdayGameData == nil {
		this.YesterdayGameData = &model.PlayerGameCtrlData{}
	}
	if this.YesterdayGameData.CtrlData == nil {
		this.YesterdayGameData.CtrlData = make(map[string]*model.PlayerGameStatics)
	}
	if _, ok := this.YesterdayGameData.CtrlData[gameId]; !ok {
		this.YesterdayGameData.CtrlData[gameId] = model.NewPlayerGameStatics()
	}
	return this.YesterdayGameData.CtrlData[gameId]
}

func (this *FishingPlayerData) SaveDetailedLog(s *base.Scene) {
	if len(this.logFishCount) == 0 {
		return
	}
	if sceneEx, ok := s.GetExtraData().(*FishingSceneData); ok {
		if sceneEx.GetTesting() && this.IsRob {
			this.logFishCount = make(map[int64]*model.FishCoinNum)
			return
		}
		totalin := int64(0)
		totalout := int64(0)
		var fd model.FishDetiel
		logBulletCount := make(map[int32]int32)
		fcn := make([]model.FishCoinNum, 0)
		for k, v := range this.logFishCount {
			FishTemplateId, power := this.SplitLogKey(k)
			fcn = append(fcn, model.FishCoinNum{ID: FishTemplateId, Power: power, Num: v.Num, Coin: v.Coin, HitNum: v.HitNum})
			totalout += int64(v.Coin)
			logBulletCount[power] += v.HitNum
		}
		fd.HitInfo = &fcn
		bt := make([]model.BulletLevelTimes, 0)
		for k, v := range logBulletCount {
			bt = append(bt, model.BulletLevelTimes{Level: k, Times: v})
			totalin += int64(k * v)
		}
		fd.BulletInfo = &bt
		fp := &model.FishPlayerData{
			UserId:   this.SnId,
			UserIcon: this.Head,
			TotalIn:  totalin,
			TotalOut: totalout,
			CurrCoin: this.CoinCache,
		}
		// 捕鱼不需要个人信息里的战绩
		//win := totalout - totalin
		//var isWin int32
		//if win > 0 {
		//	isWin = 1
		//} else if win < 0 {
		//	isWin = -1
		//}
		//sceneEx.SaveFriendRecord(this.SnId, isWin)
		fd.PlayData = fp
		info, err := model.MarshalGameNoteByFISH(&fd)
		if err == nil {
			logid, _ := model.AutoIncGameLogId()
			validFlow := totalin + totalout
			validBet := common.AbsI64(totalin - totalout)
			param := base.GetSaveGamePlayerListLogParam(this.Platform, this.Channel, this.BeUnderAgentCode, this.PackageID, logid,
				this.InviterId, totalin, totalout, int64(this.sTaxCoin), 0, totalin,
				totalout, validFlow, validBet, sceneEx.IsPlayerFirst(this.Player), false)
			sceneEx.SaveGamePlayerListLog(this.SnId, param)
			sceneEx.SaveGameDetailedLog(logid, info, &base.GameDetailedParam{})
		}

		pack := &server_proto.GWFishRecord{
			GameFreeId: proto.Int32(sceneEx.GetDBGameFree().GetId()),
			SnId:       proto.Int32(this.SnId),
		}
		for _, v := range this.logFishCount {
			fishRecord := &server_proto.FishRecord{
				FishId: proto.Int32(v.ID),
				Count:  proto.Int32(v.Num),
			}
			pack.FishRecords = append(pack.FishRecords, fishRecord)
		}
		if len(pack.FishRecords) > 0 {
			this.SendToWorld(int(server_proto.SSPacketID_PACKET_GW_FISHRECORD), pack)
		}

		diffLostCoin := this.LostCoin - this.LostCoinCache
		this.LostCoinCache = this.LostCoin
		gain := totalout - totalin
		this.Statics(s.KeyGameId, s.KeyGamefreeId, gain, true)
		if diffLostCoin > 0 {
			playerBet := &server_proto.PlayerData{
				SnId:       proto.Int32(this.SnId),
				Bet:        proto.Int64(totalin),
				Gain:       proto.Int64(gain),
				Tax:        proto.Int64(int64(this.sTaxCoin)),
				Coin:       proto.Int64(this.Coin),
				GameCoinTs: proto.Int64(this.GameCoinTs),
			}
			gwPlayerData := &server_proto.GWPlayerData{
				SceneId:    proto.Int(sceneEx.SceneId),
				GameFreeId: proto.Int32(sceneEx.GetDBGameFree().GetId()),
			}
			gwPlayerData.Datas = append(gwPlayerData.Datas, playerBet)
			proto.SetDefaults(gwPlayerData)
			sceneEx.SendToWorld(int(server_proto.SSPacketID_PACKET_GW_PLAYERDATA), gwPlayerData)
		}
	}
	this.sTaxCoin = 0
	this.logBulletHitNums = 0
	this.logFishCount = make(map[int64]*model.FishCoinNum)
}

func (this *FishingPlayerData) SetMaxCoin() {
	if this.CoinCache > this.MaxCoin {
		this.MaxCoin = this.CoinCache
	}
}

func (this *FishingPlayerData) SetMinCoin() {
	if this.CoinCache < this.MinCoin || this.MinCoin == 0 {
		this.MinCoin = this.CoinCache
	}
}

func (this *FishingPlayerData) SaveFishingLog(curCoin int64, gameid string) {
	data := this.GDatas[gameid]
	log := fmt.Sprintf("%v,%v,%v,%v,%v\n", this.CurrentCoin(), data.Statics.TotalIn, data.Statics.TotalOut, curCoin, base.GetCoinPoolMgr().GetTax())
	task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
		fileName := fmt.Sprintf("fishdata-%v.csv", this.SnId)
		file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
		defer file.Close()
		if err != nil {
			file, err = os.Create(fileName)
			if err != nil {
				return err
			}
		}
		file.WriteString(log)
		return nil
	}), nil, "SaveFishingLog").StartByFixExecutor("SaveFishingLog")
}

// NewStatics .b
func (this *FishingPlayerData) NewStatics(betCoin, gain int64) {
	if this.scene == nil || this.scene.GetTesting() || this.IsRob { //测试场和机器人不统计
		return
	}
	if betCoin == 0 && gain == 0 {
		return
	}
	// start 如果当前处于免费炮的情况,NewStatics 记录消费为0
	if this.powerType == FreePowerType {
		betCoin = 0
	}
	// end

	//黑白名单不参与投入产出统计，影响自己和他人体验
	if this.WhiteLevel != 0 || this.WhiteFlag != 0 || this.BlackLevel != 0 || this.GMLevel > 0 {
		return
	}

	fishlogger.Tracef("============snid=%v betCoin=%v  gain=%v ", this.SnId, betCoin, gain)

	keyGlobal := fmt.Sprintf("%v_%v", this.scene.GetPlatform(), this.scene.GetGameFreeId())
	if base.SysProfitCoinMgr.SysPfCoin != nil {
		if base.SysProfitCoinMgr.SysPfCoin.ProfitCoin == nil {
			base.SysProfitCoinMgr.SysPfCoin.ProfitCoin = make(map[string]*model.SysCoin)
		}
		var syscoin *model.SysCoin
		if data, exist := base.SysProfitCoinMgr.SysPfCoin.ProfitCoin[keyGlobal]; !exist {
			syscoin = new(model.SysCoin)
			base.SysProfitCoinMgr.SysPfCoin.ProfitCoin[keyGlobal] = syscoin
		} else {
			syscoin = data
		}
		syscoin.PlaysBet += betCoin
		syscoin.SysPushCoin += gain
		fishlogger.Tracef("============SysProfitCoinMgr key=%v PlaysBet:= %v SysPushCoin= %v ", keyGlobal, syscoin.PlaysBet, syscoin.SysPushCoin)
	}

	keyPlayer := strconv.Itoa(int(this.scene.GetGameFreeId()))
	var pgd *model.PlayerGameInfo
	if d, exist := this.GDatas[keyPlayer]; exist {
		FishGDataLen(len(d.Data), d)
		pgd = d
	} else {
		pgd = &model.PlayerGameInfo{
			Data: make([]int64, GDATAS_HPFISHING_MAX, GDATAS_HPFISHING_MAX),
		}
		this.GDatas[keyPlayer] = pgd
	}

	low := pgd.Data[GDATAS_HPFISHING_ALLBET]
	high := pgd.Data[GDATAS_HPFISHING_ALLBET64]
	allBet := common.MakeI64(int32(low), int32(high)) + betCoin

	pgd.Data[GDATAS_HPFISHING_ALLBET], pgd.Data[GDATAS_HPFISHING_ALLBET64] = common.LowAndHighI64(allBet)
	pgd.Data[GDATAS_HPFISHING_CHANGEBET] += int64(gain)

	fishlogger.Tracef("============snid=%v total fish betCoin:= %v  gain=%v ", this.SnId, allBet,
		pgd.Data[GDATAS_HPFISHING_CHANGEBET])
}

// GetAllBet .
func (this *FishingPlayerData) GetAllBet(key string) int64 {
	ret := int64(1)
	if d, exist := this.GDatas[key]; exist {
		FishGDataLen(len(d.Data), d)
		ret = common.MakeI64(int32(d.Data[GDATAS_HPFISHING_ALLBET]), int32(d.Data[GDATAS_HPFISHING_ALLBET64]))
	}
	return ret
}

// GetAllChangeBet .
func (this *FishingPlayerData) GetAllChangeBet(key string) int64 {
	ret := int64(0)
	if d, exist := this.GDatas[key]; exist {
		FishGDataLen(len(d.Data), d)
		ret = int64(d.Data[GDATAS_HPFISHING_CHANGEBET])
	}
	return ret
}

// 计算个人赔率和个人限制系数
func (this *FishingPlayerData) GetPlayerOdds(gameid string, ctroRate int32, fishlevel int32) (float64, float64) {
	if data, ok := this.GDatas[gameid]; ok {
		//总产出初始值 = 100*（1-调节频率）*100 （分）	初级场
		//总产出初始值 = 1000*（1-调节频率）*100 （分）	中级场
		//总产出初始值 = 10000*（1-调节频率）*100 （分）	高级场
		//总投入初始值 = 100*100 （分）	初级场
		//总投入初始值 = 1000*100 （分）	中级场
		//总投入初始值 = 10000*100 （分）	高级场
		initBaseValue := int64(10000) //1万分
		if fishlevel == 2 {
			initBaseValue = 100000
		} else if fishlevel == 3 {
			initBaseValue = 1000000
		}
		totalInValue := initBaseValue + data.Statics.TotalIn
		totalOutValue := initBaseValue*(10000-int64(ctroRate))/10000 + data.Statics.TotalOut

		//个人限制系数
		ratio := 1.0
		if fishlevel == 1 && totalOutValue-totalInValue >= 20000 {
			ratio = 0.5
		} else if fishlevel == 2 && totalOutValue-totalInValue >= 100000 {
			ratio = 0.5
		} else if fishlevel == 3 && totalOutValue-totalInValue >= 500000 {
			ratio = 0.5
		}

		return float64(totalOutValue) / float64(totalInValue), ratio
	} else {
		fishlogger.Errorf("player.GDatas[%v] is %v", gameid, this.GDatas[gameid])
		return 0, 0
	}

}

var FishGDataLen = func(flen int, pgd *model.PlayerGameInfo) {
	if flen < GDATAS_HPFISHING_MAX {
		for i := flen; i < GDATAS_HPFISHING_MAX; i++ {
			pgd.Data = append(pgd.Data, 0)
		}
	}
}

// 增加技能CD
func (this *FishingPlayerData) AddSkillCD(skillId, skillType, cdTime, gcdTime int32) {
	/*	this.SkillMutex.Lock()
		defer this.SkillMutex.Unlock()*/
	this.SkillCd[skillId] = time.Now().UnixNano()/int64(time.Millisecond) + int64(cdTime*1000)
	this.SkillGCD[skillType] = time.Now().UnixNano()/int64(time.Millisecond) + int64(gcdTime*1000)
}

// 判断技能在不在CD中
func (this *FishingPlayerData) GetSkillCD(skillId, skillType int32) bool {
	_, exists := this.SkillCd[skillId]
	if exists {
		return false
	}
	_, exists = this.SkillGCD[skillType]
	if exists {
		return false
	}
	return true
}

// 玩家升级解锁炮倍
func (this *FishingPlayerData) PlayerEvent(eventType, id int64) {
	switch eventType {
	case fishing.Event_Player_UpLevel:
		//获取解锁的炮倍
		power := srvdata.PlayerExpMgr.GetUnPower(int32(id))
		if power != 0 {
			this.UnPlayerPowerEx(int64(power))
		}
		break
	case fishing.Event_Player_UnMaxPower:
		//id 直接解锁炮倍 后期用
		this.UnPlayerPowerEx(id)
		break
	case fishing.Event_Player_UnPowerList:
		//id 解锁的炮台ID
		this.UnPlayerPowerListEx(int32(id))
		break
	default:
		fishlogger.Errorf("未处理的玩家事件 eventType = %v", eventType)
		break
	}

}
