package memcache

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

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

type GenericCache[T interface{}] struct {
	AllPlayers *cach2.Cache
	tokens     otherutils.RoutineTokens
	loadFilter otherutils.FilterInt64
	skeleton   *module.Skeleton
	name       string

	Load      func(id int64) (*T, error)
	New       func(playerId int64) *T
	PrePocess func(*T)
}

func NewCache[T interface{}](skeleton *module.Skeleton) *GenericCache[T] {

	c := new(GenericCache[T])
	c.skeleton = skeleton
	var tmp *T
	val := reflect.TypeOf(tmp)
	if val != nil && val.Kind() == reflect.Ptr {
		c.name = val.Elem().Name()
	}
	return c
}

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

func (c *GenericCache[T]) Init(load func(id int64) (*T, error),
	newObj func(playerId int64) *T, preProcess func(*T)) {
	c.AllPlayers = cach2.New(30*time.Minute, 10*time.Minute)
	c.tokens.Init(10)

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

func (c *GenericCache[T]) GetCacheData(playerID int64) *T {
	k := strconv.FormatInt(playerID, 10)
	if v, ok := c.AllPlayers.Get(k); ok {
		if v2, ok := v.(*T); ok {
			return v2
		}
	}
	return nil
}

func (c *GenericCache[T]) SetCacheData(playerID int64, v *T) {
	k2 := strconv.FormatInt(playerID, 10)
	c.AllPlayers.SetDefault(k2, v)
}

func (c *GenericCache[T]) LoadAndProcess(playerId int64, fprocess func(p *T), fErrProcess func(playerId int64, errorCode int)) {
	//找到玩家
	st := time.Now()
	p := c.GetCacheData(playerId)
	if p != nil {
		c.PrePocess(p)
		c.SetCacheData(playerId, p)
		fprocess(p)
		monitor.GoLoadTimeoutWarn(fmt.Sprintf("%s.LoadAndProcess", c.name), playerId, st)
		return
	}

	//正在加载
	if c.loadFilter.IsExist(playerId) {
		t := new(callBackFun[T])
		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[T])
				if err != nil {
					cft.fErrProcess(playerId, 11)
				} else {
					cft.fprocess(p)
				}
			}
			monitor.GoLoadTimeoutWarn(fmt.Sprintf("%s.LoadAndProcess1", c.name), playerId, st)
		}()

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

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

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

func (c *GenericCache[T]) LoadMultiAndProcess(playerId1 int64, playerId2 int64, fprocess func(p1 *T, p2 *T), fErrProcess func(errorCode int)) {

	c.LoadAndProcess(playerId1, func(p1 *T) {

		c.LoadAndProcess(playerId2, func(p2 *T) {
			fprocess(p1, p2)
		}, func(playerId int64, errorCode int) {
			fErrProcess(errorCode)
		})

	}, func(playerId int64, errorCode int) {
		fErrProcess(errorCode)
	})
}
