package hashmap

import (
	"474420502.top/eson/structure/compare"
)

type HashCode func(key interface{}) uint

type HashMap struct {
	growfactor     uint
	slimmingfactor uint

	table *Table

	GetHash HashCode
	Compare compare.Compare

	pool     *nodePool
	growsize uint
	size     uint
}

func HashInt(key interface{}) uint {
	thekey := uint(key.(int))
	hbit := thekey & 0xffffffff
	lbit := (thekey & 0xffffffff00000000) >> 32
	return lbit ^ hbit
}

func New(hcode HashCode, comp compare.Compare) *HashMap {
	hm := &HashMap{}
	hm.growfactor = 2
	hm.GetHash = hcode
	hm.Compare = comp
	hm.pool = newNodePool()
	// initcap := uint(8)
	hm.table = newTable()
	hm.countNextGrow()
	return hm
}

func (hm *HashMap) countNextGrow() {
	hm.growsize = hm.table.Cap() << 1
}

func (hm *HashMap) grow() {
	if hm.size >= hm.growsize {
		newsize := hm.size << 1
		// newtable := make([]*avlTree, newsize, newsize)
		hm.table.Grow(int(newsize - hm.size))
		nodelist := make([]*avlNode, hm.size, hm.size)
		i := 0
		hm.table.Traversal(func(cur *avlTree) {
			if cur != nil {
				cur.Traversal(func(node *avlNode) bool {
					if node != nil {
						nodelist[i] = node
						i++
					}
					return true
				})
				cur.Clear()
			}
		})

		var hash, index uint
		for _, node := range nodelist {
			hash = hm.GetHash(node.key)
			index = hash % hm.table.cap
			//bkt := newtable[index]
			bkt := hm.table.GetWithNilSet(index, func(data []*avlTree, idx uint) {
				data[idx] = avlNew(hm.Compare)
			})
			bkt.PutNode(node)
		}

		hm.countNextGrow()
	}
}

func (hm *HashMap) Put(key, value interface{}) {
	hash := hm.GetHash(key)

	tlen := hm.table.Cap()
	index := hash % tlen

	bkt := hm.table.GetWithNilSet(index, func(data []*avlTree, idx uint) {
		data[idx] = avlNew(hm.Compare)
	})

	n := &avlNode{key: key, value: value}
	if bkt.PutNode(n) {
		hm.size++
		hm.grow()
	}
}

func (hm *HashMap) Get(key interface{}) (interface{}, bool) {
	hash := hm.GetHash(key)

	tlen := hm.table.Cap()
	index := hash % tlen

	bkt := hm.table.Get(index)
	if bkt != nil {
		return bkt.Get(key)
	}
	return nil, false
}