package readcache

import (
	"fmt"
	"leafstalk/covenant/monitor"
	"leafstalk/module"
	"leafstalk/otherutils"
	"reflect"
	"strconv"
	"time"

	cach2 "github.com/patrickmn/go-cache"
)

type SameStructCache struct {
	Name       string
	AllPlayers *cach2.Cache
	tokens     otherutils.RoutineTokens
	loadFilter otherutils.FilterInt64
	skeleton   *module.Skeleton

	Load      func(id int64) (interface{}, error)
	New       func(playerId int64) interface{}
	PrePocess func(interface{})
}

func NewCache(skeleton *module.Skeleton) *SameStructCache {
	c := new(SameStructCache)
	c.skeleton = skeleton
	return c
}

type callBackFun struct {
	fprocess    func(p interface{})
	fErrProcess func(playerId int64, errorCode int)
}

func (c *SameStructCache) Init(load func(id int64) (interface{}, error), newObj func(playerId int64) interface{}, preProcess func(interface{})) {
	c.AllPlayers = cach2.New(5*time.Minute, 10*time.Minute)
	c.tokens.Init(10)

	c.Load = load
	c.New = newObj
	c.PrePocess = preProcess
}

func (c *SameStructCache) GetCacheData(playerID int64) interface{} {
	k := strconv.FormatInt(playerID, 10)
	if v, ok := c.AllPlayers.Get(k); ok {
		return v
	}
	return nil
}

func (c *SameStructCache) SetCacheData(playerID int64, v interface{}) {
	k2 := strconv.FormatInt(playerID, 10)
	c.AllPlayers.SetDefault(k2, v)
}

func (c *SameStructCache) LoadAndProcess(playerId int64, fprocess func(p interface{}), fErrProcess func(playerId int64, errorCode int)) {
	st := time.Now()

	//找到玩家
	p := c.GetCacheData(playerId)
	if p != nil {
		c.SetCacheData(playerId, p)
		c.PrePocess(p)
		fprocess(p)
		monitor.GoLoadTimeoutWarn(fmt.Sprintf("%s.LoadAndProcess", c.Name), playerId, st)
		return
	}

	//正在加载
	if c.loadFilter.IsExist(playerId) {
		t := new(callBackFun)
		t.fprocess = fprocess
		t.fErrProcess = fErrProcess
		c.loadFilter.AppendTask(playerId, t)
		return
	}
	c.loadFilter.Add(playerId)

	var err error
	c.skeleton.Go(func() {
		//协程加载
		c.tokens.Get()
		defer c.tokens.Release()

		p, err = c.Load(playerId)
	}, func() {
		defer func() {
			s := c.loadFilter.Remove(playerId)
			for _, v := range s {
				cft := v.(*callBackFun)
				if err != nil {
					cft.fErrProcess(playerId, 11)
				} else {
					cft.fprocess(p)
				}
			}
			monitor.GoLoadTimeoutWarn(fmt.Sprintf("%s.LoadAndProcess", c.Name), playerId, st)
		}()

		if err != nil {
			fErrProcess(playerId, 11)
			return
		}

		if IsNil(p) || p == nil {
			p = c.New(playerId)
		}
		c.SetCacheData(playerId, p)
		c.PrePocess(p)
		fprocess(p)
	})
}

func IsNil(i interface{}) bool {
	vi := reflect.ValueOf(i)
	if vi.Kind() == reflect.Ptr {
		return vi.IsNil()
	}
	return false
}
