package jsonvalue

import (
	"encoding"
	"encoding/base64"
	"encoding/json"
	"reflect"
	"sync/atomic"
)

var internal = struct {
	b64 *base64.Encoding

	defaultMarshalOption *Opt

	predict struct {
		bytesPerValue uint64
		calcStorage   uint64 // upper 32 bits - size; lower 32 bits - value count
	}

	types struct {
		JSONMarshaler reflect.Type
		TextMarshaler reflect.Type
	}
}{}

func init() {
	internal.b64 = base64.StdEncoding
	internal.defaultMarshalOption = emptyOptions()
	internalAddPredictSizePerValue(16, 1)

	internal.types.JSONMarshaler = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
	internal.types.TextMarshaler = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
}

func internalLoadPredictSizePerValue() int {
	if n := atomic.LoadUint64(&internal.predict.bytesPerValue); n > 0 {
		return int(n)
	}

	v := atomic.LoadUint64(&internal.predict.calcStorage)
	total := v >> 32
	num := v & 0xFFFFFFFF
	return int(total / num)
}

func internalAddPredictSizePerValue(total, num int) {
	v := atomic.LoadUint64(&internal.predict.calcStorage)
	preTotal := v >> 32
	preNum := v & 0xFFFFFFFF

	nextTotal := uint64(total) + preTotal
	nextNum := uint64(num) + preNum

	if nextTotal < 0x7FFFFFFF {
		v := (nextTotal << 32) + nextNum
		atomic.StoreUint64(&internal.predict.calcStorage, v)
		return
	}

	per := nextTotal / nextNum
	atomic.StoreUint64(&internal.predict.bytesPerValue, per)
	atomic.StoreUint64(&internal.predict.calcStorage, (per<<32)+1)
}

type pool interface {
	get() *V
}

type globalPool struct{}

func (globalPool) get() *V {
	return &V{}
}

type poolImpl struct {
	pool []V

	count  int
	actual int // actual counted values

	rawSize int
}

func newPool(rawSize int) *poolImpl {
	per := internalLoadPredictSizePerValue()
	cnt := rawSize / per

	p := &poolImpl{
		pool:    make([]V, cnt),
		count:   cnt,
		actual:  0,
		rawSize: rawSize,
	}

	return p
}

func (p *poolImpl) get() *V {
	if p.actual < p.count {
		v := &p.pool[p.actual]
		p.actual++
		return v
	}

	p.actual++
	return globalPool{}.get()
}

func (p *poolImpl) release() {
	internalAddPredictSizePerValue(p.rawSize, p.actual)
}
