package util

import (
	"bytes"
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

var (
	ChunkSizes = []int{
		1 << 4,  // index 0, 16 bytes, inclusive
		1 << 6,  // index 1, 64 bytes
		1 << 8,  // index 2, 256 bytes
		1 << 10, // index 3, 1K bytes
		1 << 12, // index 4, 4K bytes
		1 << 14, // index 5, 16K bytes
		1 << 16, // index 6, 64K bytes
		1 << 18, // index 7, 256K bytes
		1 << 20, // index 8, 1M bytes
		1 << 22, // index 9, 4M bytes
		1 << 24, // index 10, 16M bytes
		1 << 26, // index 11, 64M bytes
		1 << 28, // index 12, 128M bytes
	}

	_DEBUG = false
)

type BytesPool struct {
	chunkPools []*byteChunkPool
}

func NewBytesPool() *BytesPool {
	var bp BytesPool
	for _, size := range ChunkSizes {
		bp.chunkPools = append(bp.chunkPools, newByteChunkPool(size))
	}
	ret := &bp
	if _DEBUG {
		t := time.NewTicker(10 * time.Second)
		go func() {
			for {
				println("buffer:", ret.String())
				<-t.C
			}
		}()
	}
	return ret
}

func (m *BytesPool) String() string {
	var buf bytes.Buffer
	for index, size := range ChunkSizes {
		if m.chunkPools[index].count > 0 {
			buf.WriteString(fmt.Sprintf("size:%d count:%d\n", size, m.chunkPools[index].count))
		}
	}
	return buf.String()
}

func findChunkPoolIndex(size int) int {
	if size <= 0 {
		return -1
	}
	size = (size - 1) >> 4
	ret := 0
	for size > 0 {
		size = size >> 2
		ret = ret + 1
	}
	if ret >= len(ChunkSizes) {
		return -1
	}
	return ret
}

func (m *BytesPool) Get(size int) []byte {
	index := findChunkPoolIndex(size)
	// println("get index:", index)
	if index < 0 {
		return make([]byte, size)
	}
	return m.chunkPools[index].Get()
}

func (m *BytesPool) Put(b []byte) {
	index := findChunkPoolIndex(len(b))
	// println("put index:", index)
	if index < 0 {
		return
	}
	m.chunkPools[index].Put(b)
}

// a pool of fix-sized []byte chunks. The pool size is managed by Go GC
type byteChunkPool struct {
	sync.Pool
	chunkSizeLimit int
	count          int64
}

var count int

func newByteChunkPool(chunkSizeLimit int) *byteChunkPool {
	var m byteChunkPool
	m.chunkSizeLimit = chunkSizeLimit
	m.Pool.New = func() interface{} {
		count++
		// println("creating []byte size", m.chunkSizeLimit, "new", count, "count", m.count)
		return make([]byte, m.chunkSizeLimit)
	}
	return &m
}

func (m *byteChunkPool) Get() []byte {
	// println("before get size:", m.chunkSizeLimit, "count:", m.count)
	atomic.AddInt64(&m.count, 1)
	return m.Pool.Get().([]byte)
}

func (m *byteChunkPool) Put(b []byte) {
	atomic.AddInt64(&m.count, -1)
	// println("after put get size:", m.chunkSizeLimit, "count:", m.count)
	m.Pool.Put(b)
}