package hash_table

import (
	"fmt"
	ds "source/pkg/data_struct"
)

// HashTable - хеш-таблица с цепочками
type HashTable struct {
	buckets    []*bucket
	size       int
	capacity   int
	loadFactor float64
}

type bucket struct {
	head *elementHT
}

type elementHT struct {
	data ds.MyData
	next *elementHT
}

// NewHashTable - создает новую хеш-таблицу
func NewHashTable(capacity int, loadFactor float64) *HashTable {

	buckets := make([]*bucket, capacity)

	for i := 0; i < capacity; i++ {
		buckets[i] = &bucket{}
	}

	return &HashTable{
		buckets:    buckets,
		size:       0,
		capacity:   capacity,
		loadFactor: loadFactor,
	}
}

// func (h HashTable) getIndex(name string) int {
// 	return GetHashString(name) % h.size
// }

// func (h HashTable) Add(name string) {

// }

func (ht *HashTable) GetIndex(name string) int {
	hash := GetHashString(name)
	return hash % ht.capacity
}

func (ht *HashTable) Len() int {
	return ht.size
}

// func (ht *HashTable) getIndex(hash int) int {
// 	return hash % ht.capacity
// }

func (h *HashTable) Insert(new ds.MyData) {

	if h.size >= int(float64(h.capacity)*h.loadFactor) {
		h.resize()
	}

	ind := h.GetIndex(new.Name)

	buck := h.buckets[ind]

	current := buck.head

	for current != nil {
		if current.data.Name == new.Name {
			current.data.Phone = new.Phone
			return
		}

		current = current.next
	}

	newHead := &elementHT{
		data: new,
		next: buck.head,
	}

	buck.head = newHead
	h.size++
}

func (ht *HashTable) InsertAll(data []ds.MyData) {
	for _, el := range data {
		ht.Insert(el)
	}
}

func (h *HashTable) Search(name string) (phone string, status bool) {
	ind := h.GetIndex(name)

	buck := h.buckets[ind]

	current := buck.head

	for current != nil {
		if current.data.Name == name {
			return current.data.Phone, true
		}

		current = current.next
	}

	return "", false
}

// func pressEnterToContinue() {
// 	fmt.Print("Нажмите Enter для продолжения...")
// 	bufio.NewReader(os.Stdin).ReadBytes('\n')
// }

// resize - увеличивает размер таблицы
func (ht *HashTable) resize() {

	// fmt.Printf("Resize table!\n elements: %d(%.3f%%)\n old capacity: %d\n new capacity: %d\n", ht.size, float64(ht.size)/float64(ht.capacity), ht.capacity, 2*ht.capacity)

	// ht.Print()

	// pressEnterToContinue()

	newCapacity := ht.capacity * 2
	newHT := NewHashTable(newCapacity, ht.loadFactor)

	for _, b := range ht.buckets {
		current := b.head
		for current != nil {
			newHT.Insert(current.data)
			current = current.next
		}
	}

	ht.buckets = newHT.buckets
	ht.capacity = newCapacity
}

func (ht *HashTable) Delete(name string) bool {
	ind := ht.GetIndex(name)

	buck := ht.buckets[ind]

	if buck.head == nil {
		return false
	}

	if buck.head.data.Name == name {
		buck.head = buck.head.next
		ht.size--
		return true
	}

	prev := buck.head
	current := buck.head.next

	for current != nil {
		if current.data.Name == name {
			prev.next = current.next
			ht.size--
			return true
		}
		prev = current
		current = current.next
	}

	return false
}

func (ht *HashTable) Contains(name string) bool {
	_, ok := ht.Search(name)
	return ok
}

func (elem *elementHT) ToString() string {
	if elem == nil {
		return "nil"
	}

	return elem.data.ToString()
}

func (ht *HashTable) Print() {
	for ind := 0; ind < ht.capacity; ind++ {
		buck := ht.buckets[ind]
		current := buck.head

		bucketsStr := ""

		for current != nil {
			bucketsStr += " --> " + current.ToString()
			current = current.next
		}
		fmt.Printf("[%d]: %s\n", ind, bucketsStr)
	}
}

func (ht *HashTable) listAll() []ds.MyData {
	data := make([]ds.MyData, ht.size)

	index := 0

	for ind := 0; ind < ht.capacity; ind++ {
		buck := ht.buckets[ind]
		current := buck.head

		for current != nil {
			data[index] = current.data
			index++
			// fmt.Println(current.name, current.phone)
			current = current.next
		}
	}

	return data
}

func (ht *HashTable) ListAll() []ds.MyData {
	// fmt.Printf("Size: %d, Capacity: %d\n", ht.size, ht.capacity)
	data := ht.listAll()

	data = ds.QSort(data, 0, len(data)-1)

	// for i, el := range data {
	// 	fmt.Printf("[%d]: \"%s\", %d\n", i, el.name, el.phone)
	// }
	return data
}

// func (ht *HashTable) PrintMostPopularnames(phone int) {
// 	// fmt.Printf("Size: %d, Capacity: %d\n", ht.size, ht.capacity)
// 	data := ht.listAll()

// 	data = QSortElementsHT(data, 0, len(data)-1)

// 	for i := 0; i < phone; i++ {
// 		fmt.Printf("[%d]: %3d : %s\n", i, ht.GetIndex(data[len(data)-i-1].name), data[len(data)-i-1].ToString())
// 	}
// }
