package stats

import (
	"runtime"
	"sync"
	"time"

	"github.com/micro/go-micro/v3/debug/stats"
	"github.com/micro/go-micro/v3/util/ring"
)

type memoryStats struct {
	// used to store past stats
	buffer *ring.Buffer

	sync.RWMutex
	started  int64
	requests uint64
	errors   uint64
}

func (s *memoryStats) snapshot() *stats.Stat {
	s.RLock()
	defer s.RUnlock()

	var mstat runtime.MemStats
	runtime.ReadMemStats(&mstat)

	now := time.Now().Unix()

	return &stats.Stat{
		Timestamp: now,
		Started:   s.started,
		Uptime:    now - s.started,
		Memory:    mstat.Alloc,
		GC:        mstat.PauseTotalNs,
		Threads:   uint64(runtime.NumGoroutine()),
		Requests:  s.requests,
		Errors:    s.errors,
	}
}

func (s *memoryStats) Read() ([]*stats.Stat, error) {
	buf := s.buffer.Get(s.buffer.Size())
	var buffer []*stats.Stat

	// get a value from the buffer if it exists
	for _, b := range buf {
		stat, ok := b.Value.(*stats.Stat)
		if !ok {
			continue
		}
		buffer = append(buffer, stat)
	}

	// get a snapshot
	buffer = append(buffer, s.snapshot())

	return buffer, nil
}

func (s *memoryStats) Write(stat *stats.Stat) error {
	s.buffer.Put(stat)
	return nil
}

func (s *memoryStats) Record(err error) error {
	s.Lock()
	defer s.Unlock()

	// increment the total request count
	s.requests++

	// increment the error count
	if err != nil {
		s.errors++
	}

	return nil
}

// NewStats returns a new in memory stats buffer
// TODO add options
func NewStats() stats.Stats {
	return &memoryStats{
		started: time.Now().Unix(),
		buffer:  ring.New(1),
	}
}