package counters

import (
	"database/sql"
	"math"
	"time"

	c "github.com/Azareal/Gosora/common"
	qgen "github.com/Azareal/Gosora/query_gen"
	"github.com/pkg/errors"
)

var PerfCounter *DefaultPerfCounter

type PerfCounterBucket struct {
	low  *MutexCounter64Bucket
	high *MutexCounter64Bucket
	avg  *MutexCounter64Bucket
}

// TODO: Track perf on a per route basis
type DefaultPerfCounter struct {
	buckets []*PerfCounterBucket

	insert *sql.Stmt
}

func NewDefaultPerfCounter(acc *qgen.Accumulator) (*DefaultPerfCounter, error) {
	co := &DefaultPerfCounter{
		buckets: []*PerfCounterBucket{
			{
				low:  &MutexCounter64Bucket{counter: math.MaxInt64},
				high: &MutexCounter64Bucket{counter: 0},
				avg:  &MutexCounter64Bucket{counter: 0},
			},
		},
		insert: acc.Insert("perfchunks").Columns("low,high,avg,createdAt").Fields("?,?,?,UTC_TIMESTAMP()").Prepare(),
	}

	c.Tasks.FifteenMin.Add(co.Tick)
	//c.Tasks.Sec.Add(co.Tick)
	c.Tasks.Shutdown.Add(co.Tick)
	return co, acc.FirstError()
}

func (co *DefaultPerfCounter) Tick() error {
	getCounter := func(b *MutexCounter64Bucket) (c int64) {
		b.Lock()
		c = b.counter
		b.counter = 0
		b.Unlock()
		return c
	}
	var low int64
	hTbl := c.GetHookTable()
	for _, b := range co.buckets {
		b.low.Lock()
		low, b.low.counter = b.low.counter, math.MaxInt64
		b.low.Unlock()
		if low == math.MaxInt64 {
			low = 0
		}
		high := getCounter(b.high)
		avg := getCounter(b.avg)
		c.H_counters_perf_tick_row_hook(hTbl, low, high, avg)
		if e := co.insertChunk(low, high, avg); e != nil { // TODO: Bulk insert for speed?
			return errors.Wrap(errors.WithStack(e), "perf counter")
		}
	}
	return nil
}

func (co *DefaultPerfCounter) insertChunk(low, high, avg int64) error {
	if low == 0 && high == 0 && avg == 0 {
		return nil
	}
	c.DebugLogf("Inserting a pchunk with low %d, high %d, avg %d", low, high, avg)
	if c.Dev.LogNewLongRoute && high > (5*1000*1000) {
		c.Logf("pchunk high %d", high)
	}
	_, e := co.insert.Exec(low, high, avg)
	return e
}

func (co *DefaultPerfCounter) Push(dur time.Duration /*,_ bool*/) {
	id := 0
	b := co.buckets[id]
	//c.DebugDetail("buckets ", id, ": ", b)
	micro := dur.Microseconds()
	if micro >= math.MaxInt32 {
		c.LogWarning(errors.New("dur should not be int32 max or higher"))
	}

	low := b.low
	low.Lock()
	if micro < low.counter {
		low.counter = micro
	}
	low.Unlock()

	high := b.high
	high.Lock()
	if micro > high.counter {
		high.counter = micro
	}
	high.Unlock()

	avg := b.avg
	avg.Lock()
	if micro != avg.counter {
		if avg.counter == 0 {
			avg.counter = micro
		} else {
			avg.counter = (micro + avg.counter) / 2
		}
	}
	avg.Unlock()
}