// Copyright 1999-2020 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package base

import (
	"sync/atomic"

	"github.com/alibaba/sentinel-golang/core/base"
	"github.com/alibaba/sentinel-golang/logging"
	"github.com/pkg/errors"
)

// MetricBucket represents the entity to record metrics per minimum time unit (i.e. the bucket time span).
// Note that all operations of the MetricBucket are required to be thread-safe.
type MetricBucket struct {
	// Value of statistic
	counter        [base.MetricEventTotal]int64
	minRt          int64
	maxConcurrency int32
}

func NewMetricBucket() *MetricBucket {
	mb := &MetricBucket{
		minRt:          base.DefaultStatisticMaxRt,
		maxConcurrency: 0,
	}
	return mb
}

// Add statistic count for the given metric event.
func (mb *MetricBucket) Add(event base.MetricEvent, count int64) {
	if event >= base.MetricEventTotal || event < 0 {
		logging.Error(errors.Errorf("Unknown metric event: %v", event), "")
		return
	}
	if event == base.MetricEventRt {
		mb.AddRt(count)
		return
	}
	mb.addCount(event, count)
}

func (mb *MetricBucket) addCount(event base.MetricEvent, count int64) {
	atomic.AddInt64(&mb.counter[event], count)
}

// Get current statistic count of the given metric event.
func (mb *MetricBucket) Get(event base.MetricEvent) int64 {
	if event >= base.MetricEventTotal || event < 0 {
		logging.Error(errors.Errorf("Unknown metric event: %v", event), "")
		return 0
	}
	return atomic.LoadInt64(&mb.counter[event])
}

func (mb *MetricBucket) reset() {
	for i := 0; i < int(base.MetricEventTotal); i++ {
		atomic.StoreInt64(&mb.counter[i], 0)
	}
	atomic.StoreInt64(&mb.minRt, base.DefaultStatisticMaxRt)
	atomic.StoreInt32(&mb.maxConcurrency, int32(0))
}

func (mb *MetricBucket) AddRt(rt int64) {
	mb.addCount(base.MetricEventRt, rt)
	if rt < atomic.LoadInt64(&mb.minRt) {
		// Might not be accurate here.
		atomic.StoreInt64(&mb.minRt, rt)
	}
}

func (mb *MetricBucket) MinRt() int64 {
	return atomic.LoadInt64(&mb.minRt)
}

func (mb *MetricBucket) UpdateConcurrency(concurrency int32) {
	cc := concurrency
	if cc > atomic.LoadInt32(&mb.maxConcurrency) {
		// Might not be accurate here.
		atomic.StoreInt32(&mb.maxConcurrency, cc)
	}
}

func (mb *MetricBucket) MaxConcurrency() int32 {
	return atomic.LoadInt32(&mb.maxConcurrency)
}
