package utils

import (
	"bytes"
	"os"
	"runtime"

	"encoding/json"
	"fmt"
	"mongo.games.com/goserver/core/logger"
	"sync"
	"sync/atomic"
	"time"
)

var _panicStackMgr = &PanicStackMgr{
	items: make(map[string]*PanicStackInfo),
}

type PanicStackMgr struct {
	sync.RWMutex
	items map[string]*PanicStackInfo
}

type PanicStackInfo struct {
	FirstTime time.Time
	LastTime  time.Time
	Times     int64
	ErrorMsg  string
	StackBuf  string
}

func DumpStackIfPanic(f string) {
	if err := recover(); err != nil {
		defer func() { //防止二次panic
			if err := recover(); err != nil {
				logger.Logger.Error(f, " panic.panic,error=", err)
			}
		}()
		logger.Logger.Error(f, " panic,error=", err)
		errMsg := fmt.Sprintf("%v", err)
		var buf [4096]byte
		n := runtime.Stack(buf[:], false)
		logger.Logger.Error("stack--->", string(buf[:n]))
		stk := make([]uintptr, 32)
		m := runtime.Callers(0, stk[:])
		stk = stk[:m]
		if len(stk) > 0 {
			d, err := json.Marshal(stk)
			if err == nil && len(d) > 0 {
				key := string(d)
				_panicStackMgr.Lock()
				defer _panicStackMgr.Unlock()
				tNow := time.Now()
				if ps, exist := _panicStackMgr.items[key]; exist {
					atomic.AddInt64(&ps.Times, 1)
					ps.LastTime = tNow
				} else {
					ps = &PanicStackInfo{
						ErrorMsg:  errMsg,
						Times:     1,
						StackBuf:  string(buf[:n]),
						FirstTime: tNow,
						LastTime:  tNow,
					}
					_panicStackMgr.items[key] = ps
				}
			}
		}
	}
}

func DumpStack(f string) {
	logger.Logger.Error(f)
	var buf [4096]byte
	len := runtime.Stack(buf[:], false)
	logger.Logger.Error("stack--->", string(buf[:len]))
}

func GetPanicStats() map[string]PanicStackInfo {
	stats := make(map[string]PanicStackInfo)
	_panicStackMgr.RLock()
	defer _panicStackMgr.RUnlock()
	for k, v := range _panicStackMgr.items {
		stats[k] = *v
	}
	return stats
}

var RecoverPanicFunc func(args ...interface{})

func init() {
	bufPool := &sync.Pool{
		New: func() interface{} {
			return &bytes.Buffer{}
		},
	}
	RecoverPanicFunc = func(args ...interface{}) {
		if r := recover(); r != nil {
			buf := bufPool.Get().(*bytes.Buffer)
			defer bufPool.Put(buf)
			buf.Reset()
			buf.WriteString(fmt.Sprintf("panic: %v\n", r))
			for _, v := range args {
				buf.WriteString(fmt.Sprintf("%v\n", v))
			}
			pcs := make([]uintptr, 10)
			n := runtime.Callers(3, pcs)
			frames := runtime.CallersFrames(pcs[:n])
			for f, again := frames.Next(); again; f, again = frames.Next() {
				buf.WriteString(fmt.Sprintf("%v:%v %v\n", f.File, f.Line, f.Function))
			}
			fmt.Fprint(os.Stderr, buf.String())
		}
	}
}
