// Copyright The OpenTelemetry Authors
//
// 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 structure // import "github.com/lightstep/go-expohisto/structure"

import "fmt"

// DefaultMaxSize is the default maximum number of buckets per
// positive or negative number range.  The value 160 is specified by
// OpenTelemetry--yields a maximum relative error of less than 5% for
// data with contrast 10**5 (e.g., latencies in the range 1ms to 100s).
// See the derivation here:
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exponential-bucket-histogram-aggregation
const DefaultMaxSize int32 = 160

// MinSize is the smallest reasonable configuration, which is small
// enough to contain the entire normal floating point range at
// MinScale.
const MinSize = 2

// MaximumMaxSize is an arbitrary limit meant to limit accidental use
// of giant histograms.
const MaximumMaxSize = 16384

// Config contains configuration for exponential histogram creation.
type Config struct {
	maxSize int32
}

// Option is the interface that applies a configuration option.
type Option interface {
	// apply sets the Option value of a config.
	apply(Config) Config
}

// WithMaxSize sets the maximum size of each range (positive and/or
// negative) in the histogram.
func WithMaxSize(size int32) Option {
	return maxSize(size)
}

// maxSize is an option to set the maximum histogram size.
type maxSize int32

// apply implements Option.
func (ms maxSize) apply(cfg Config) Config {
	cfg.maxSize = int32(ms)
	return cfg
}

// NewConfig returns an exponential histogram configuration with
// defaults and limits applied.
func NewConfig(opts ...Option) Config {
	var cfg Config
	for _, opt := range opts {
		cfg = opt.apply(cfg)
	}
	return cfg
}

// Validate returns true for valid configurations.
func (c Config) Valid() bool {
	_, err := c.Validate()
	return err == nil
}

// Validate returns the nearest valid Config object to the input and a
// boolean indicating whether the the input was a valid
// configurations.
func (c Config) Validate() (Config, error) {
	if c.maxSize >= MinSize && c.maxSize <= MaximumMaxSize {
		return c, nil
	}
	if c.maxSize == 0 {
		c.maxSize = DefaultMaxSize
		return c, nil
	}
	err := fmt.Errorf("invalid histogram size: %d", c.maxSize)
	if c.maxSize < 0 {
		c.maxSize = DefaultMaxSize
	} else if c.maxSize < MinSize {
		c.maxSize = MinSize
	} else if c.maxSize > MaximumMaxSize {
		c.maxSize = MaximumMaxSize
	}
	return c, err
}