package main

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

	"mongo.games.com/game/common"
	"mongo.games.com/game/model"
	"mongo.games.com/goserver/core/basic"
	"mongo.games.com/goserver/core/module"
	"mongo.games.com/goserver/core/netlib"
	"mongo.games.com/goserver/core/task"
)

const (
	LoginStateLogin        int = iota // 登录中
	LoginStateLoginFinish             // 登录完成
	LoginStateLogout                  // 登出中
	LoginStateLogoutFinish            // 登出完成
)

var LoginStateMgrSington = &LoginStateMgr{
	statesByName:   make(map[string]*AccLoginState),
	statesByAccId:  make(map[string]*AccLoginState),
	statesByPlayer: make(map[string]*AccLoginState),
	statesBySid:    make(map[int64]*SessionState),
}

// SessionState 客户端连接状态
type SessionState struct {
	userName     string                 // 玩家唯一标识
	sid          int64                  // 连接唯一标识
	state        int                    // 登录状态
	gateSess     *netlib.Session        // 客户端所在gatesrv
	als          *AccLoginState         // 账号状态
	clog         *model.ClientLoginInfo // 登录日志
	startLoginTs int64                  // 登录时间
}

type AccLoginState struct {
	acc          *model.Account
	lss          map[int64]*SessionState // sid
	lastLoginTs  int64                   //最后登录时间
	lastLogoutTs int64                   //最后登出时间（只是用来判断清理内存缓存）
}

// LoginStateMgr 玩家登录信息
// 保存登录中和已经登录的玩家账号信息
// 玩家登出或断开连接只会清除连接信息，短时间内重复登录会使用之前的账号缓存数据不会频繁访问数据库
// 玩家长时间离开才会清除缓存信息
// 注意：玩家登录次数，登录时间，登出时间记录并没有准确同步到数据库
type LoginStateMgr struct {
	statesByName   map[string]*AccLoginState // key:玩家唯一标识，玩家登录状态
	statesByAccId  map[string]*AccLoginState // key:accountid，所有人登录状态
	statesByPlayer map[string]*AccLoginState // key:accountid，真人登录状态
	statesBySid    map[int64]*SessionState   // key:连接唯一标识，连接状态
}

func UserKey(userName, platform string, tagkey int32) string {
	return fmt.Sprintf("%v_%v_%v", userName, platform, tagkey)
}

func (this *LoginStateMgr) IsLogining(userName, platform string, tagkey int32) bool {
	userName = UserKey(userName, platform, tagkey)
	if v, exist := this.statesByName[userName]; exist {
		if len(v.lss) == 0 {
			return false
		}
		for _, ls := range v.lss {
			if ls.state == LoginStateLogin {
				return true
			}
		}
	}
	return false
}

func (this *LoginStateMgr) IsLoginFinish(userName, platform string, tagkey int32) bool {
	userName = UserKey(userName, platform, tagkey)
	if v, exist := this.statesByName[userName]; exist {
		if len(v.lss) == 0 {
			return false
		}
		for _, ls := range v.lss {
			if ls.state == LoginStateLoginFinish {
				return true
			}
		}
	}
	return false
}

func (this *LoginStateMgr) GetLoginStateByName(userName string) *AccLoginState {
	if v, exist := this.statesByName[userName]; exist {
		return v
	}
	return nil
}

func (this *LoginStateMgr) GetLoginStateByAccId(accId string) *AccLoginState {
	if v, exist := this.statesByAccId[accId]; exist {
		return v
	}
	return nil
}

func (this *LoginStateMgr) GetLoginStateByTelAndPlatform(tel, platform string) *AccLoginState {
	for _, als := range this.statesByPlayer {
		if als != nil && als.acc != nil {
			if als.acc.Tel == tel && als.acc.Platform == platform {
				return als
			}
		}
	}

	return nil
}

// GetLoginStateBySid 连接是否登录中
func (this *LoginStateMgr) GetLoginStateBySid(sid int64) *SessionState {
	if v, exist := this.statesBySid[sid]; exist {
		return v
	}
	return nil
}

// StartLogin 开始登录
func (this *LoginStateMgr) StartLogin(userName, platform string, sid int64, s *netlib.Session, clog *model.ClientLoginInfo, tagkey int32) bool {
	//注意此处的坑，这个地方要增加平台id
	userName = UserKey(userName, platform, tagkey)
	ts := time.Now().Unix()
	if als, exist := this.statesByName[userName]; !exist {
		als = &AccLoginState{
			lss:         make(map[int64]*SessionState),
			lastLoginTs: ts,
		}
		ls := &SessionState{
			userName:     userName,
			sid:          sid,
			gateSess:     s,
			state:        LoginStateLogin,
			als:          als,
			startLoginTs: ts,
			clog:         clog,
		}
		this.statesByName[userName] = als
		als.lss[sid] = ls
		this.statesBySid[sid] = ls
		return true
	} else {
		ls := &SessionState{
			userName:     userName,
			sid:          sid,
			gateSess:     s,
			state:        LoginStateLogin,
			als:          als,
			startLoginTs: ts,
			clog:         clog,
		}
		als.lss[sid] = ls
		this.statesBySid[sid] = ls
		if als.acc != nil {
			return false
		}
		return true
	}
}

// LoginFinish 玩家登录完成
func (this *LoginStateMgr) LoginFinish(userName, platform string, sid int64, acc *model.Account, tagkey int32) (oldState map[int64]*SessionState) {
	userName = UserKey(userName, platform, tagkey)
	if v, exist := this.statesByName[userName]; exist {
		v.acc = acc
		v.lastLoginTs = time.Now().Unix()
		if acc != nil {
			this.statesByAccId[acc.AccountId.Hex()] = v
			if acc.Platform != common.Platform_Rob {
				this.statesByPlayer[acc.AccountId.Hex()] = v
			}
		}
		if len(v.lss) > 0 {
			oldState = make(map[int64]*SessionState)
			for k, v := range v.lss {
				if k != sid {
					oldState[k] = v
				}
			}
		}
	}

	if v, exist := this.statesBySid[sid]; exist {
		v.state = LoginStateLoginFinish
	}

	return
}

// Logout 删除连接状态
// 连接断开，正常登出
func (this *LoginStateMgr) Logout(s *SessionState) {
	if s != nil {
		delete(this.statesBySid, s.sid)
		//缓存下,避免对DB造成冲击
		s.state = LoginStateLogoutFinish
		s.als.lastLogoutTs = time.Now().Unix()
		if s.als != nil && s.als.acc != nil {
			s.als.acc.LastLogoutTime = time.Now()
		}
		delete(s.als.lss, s.sid)
	}
}

// LogoutBySid 登录失败，删除连接状态
func (this *LoginStateMgr) LogoutBySid(sid int64) {
	if s, exist := this.statesBySid[sid]; exist {
		this.Logout(s)
	}
}

func (this *LoginStateMgr) LogoutByAccount(accId string) {
	if als, exist := this.statesByAccId[accId]; exist {
		if als != nil {
			for _, s := range als.lss {
				this.Logout(s)
			}
		}
	}
}

// LogoutAllBySession gatesrv掉线，则上面的所有玩家掉线
func (this *LoginStateMgr) LogoutAllBySession(session *netlib.Session) {
	for sid, s := range this.statesBySid {
		if s.gateSess == session {
			this.Logout(s)
			p := PlayerMgrSington.GetPlayer(sid)
			if p != nil {
				p.DropLine()
			}
		}
	}
}

// DelAccountByAccid 特殊处理，负责从statesByName删除，为了处理账号的删除问题
// 目前的一个使用功能，机器人账号会被删除，但是这里是删除真人账号数据？
func (this *LoginStateMgr) DelAccountByAccid(accid string) {
	for name, s := range this.statesByPlayer {
		if s != nil && s.acc != nil && s.acc.AccountId.Hex() == accid {
			this.DeleteAccount(name, s)
		}
	}
}

func (this *LoginStateMgr) DeleteAccount(name string, s *AccLoginState) {
	if s != nil && s.acc != nil {
		//注意此处的坑，这个地方要增加平台id,为了和上面的匹配相同
		userName := UserKey(s.acc.UserName, s.acc.Platform, s.acc.TagKey)
		delete(this.statesByName, userName)
		userName = UserKey(s.acc.Tel, s.acc.Platform, s.acc.TagKey)
		delete(this.statesByName, userName)
		acc := s.acc
		if acc != nil {
			delete(this.statesByAccId, acc.AccountId.Hex())
		}
		delete(this.statesByPlayer, name)
	}
}

func (this *LoginStateMgr) ModuleName() string {
	return "LoginStateMgr"
}

func (this *LoginStateMgr) Init() {
	// 预加载机器人数据，是为了让机器人登录时不再访问数据库吗？
	if model.GameParamData.PreLoadRobotCount > 0 {
		task.New(nil, task.CallableWrapper(func(o *basic.Object) interface{} {
			tsBeg := time.Now()
			accounts := model.GetRobotAccounts(model.GameParamData.PreLoadRobotCount)
			tsEnd := time.Now()
			logger.Logger.Tracef("GetRobotAccounts take:%v total:%v", tsEnd.Sub(tsBeg), len(accounts))
			return accounts
		}), task.CompleteNotifyWrapper(func(data interface{}, t task.Task) {
			if accounts, ok := data.([]model.Account); ok {
				if accounts != nil {
					ts := time.Now().Add(time.Hour).Unix() // 这里加了一小时，延迟数据清理？
					for i := 0; i < len(accounts); i++ {
						userName := UserKey(accounts[i].UserName, accounts[i].Platform, accounts[i].TagKey)
						if _, exist := this.statesByName[userName]; !exist {
							als := &AccLoginState{
								acc:          &accounts[i],
								lss:          make(map[int64]*SessionState),
								lastLoginTs:  ts,
								lastLogoutTs: ts,
							}
							this.statesByName[userName] = als
							this.statesByAccId[accounts[i].AccountId.Hex()] = als
						}
					}
				}
			}
		}), "GetAllRobotAccounts").Start()
	}

	PlayerMgrSington.LoadRobots()
}

func (this *LoginStateMgr) Update() {
	// 定时清理已经登出的账号数据
	curTs := time.Now().Unix()
	for name, s := range this.statesByPlayer {
		if len(s.lss) == 0 && curTs-s.lastLogoutTs > int64(model.GameParamData.LoginStateCacheSec) {
			if s != nil && s.acc != nil {
				this.DeleteAccount(name, s)
			}
		}
	}
}

func (this *LoginStateMgr) Shutdown() {
	for _, s := range this.statesByName {
		if s.acc != nil && s.acc.Platform != common.Platform_Rob {
			model.LogoutAccount(s.acc) // 更新登录次数和登出时间
		}
	}
	module.UnregisteModule(this)
}

func init() {
	module.RegisteModule(LoginStateMgrSington, time.Minute, 0)
}
