package main

import (
	"fmt"
	"math/rand"
	csvwriter "source/pkg/csv_writer"
	ds "source/pkg/data_struct"
	dg "source/pkg/gen_data"
	bst "source/pkg/structures/bin_search_tree"
	ht "source/pkg/structures/hash_table"
	ll "source/pkg/structures/linked_list"

	// csv "source/pkg/csv_ri"

	"time"
)

const (
	countUsers           = 20_000
	countRepeat          = 20
	countRandomSearch    = 1000
	countNotExitstSearch = 500
	countDeletes         = 1000
)

type TestData struct {
	Items       []ds.MyData // все записи
	ItemsSorted []ds.MyData // все записи отсортированные
	Search      []ds.MyData // для поиска (существующие и несуществующие)
	ToDelete    []ds.MyData // для удаления
	UniqueItems []ds.MyData // Уникальные элементы для тестов
}

type DataStructure interface {
	Insert(data ds.MyData)
	InsertAll(data []ds.MyData)
	Search(name string) (string, bool)
	Delete(name string) bool
	Len() int
}

// Создатели структур
type StructureFactory func() DataStructure

func NewLinkedList() DataStructure {
	return ll.NewLinkedList()
}

func NewHashTable() DataStructure {
	return ht.NewHashTable(256, 0.75)
}

func NewBinSearchTree() DataStructure {
	return bst.NewBinSearchTree()
}

func uniqueElements(data []ds.MyData) []ds.MyData {
	res := make([]ds.MyData, 0, len(data))

	for _, el := range data {
		isUnique := true
		for _, resEl := range res {
			if el == resEl {
				isUnique = false
				break
			}
		}
		if isUnique {
			res = append(res, el)
		}
	}

	return res
}

func GenerateTestData() TestData {
	items := dg.RecordsShuffled(countUsers)
	// fmt.Println("isSorted:", isSorted(items))
	itemsSort := ds.QSort(items, 0, len(items)-1)

	uniqueItems := uniqueElements(items)
	existing := make([]ds.MyData, countRandomSearch)
	// notExisting := [countNotExitstSearch]ds.MyData{}
	notExisting := make([]ds.MyData, countNotExitstSearch)
	toDelete := make([]ds.MyData, countDeletes)

	countUniq := len(uniqueItems)
	for i := 0; i < countRandomSearch; i++ {
		// randInd := rand.Intn(countUsers)
		randInd := rand.Intn(countUniq)
		existing[i] = uniqueItems[randInd]
		// fmt.Println(randInd)
	}

	for i := 0; i < countNotExitstSearch; i++ {
		// randInd := rand.Intn(countUsers)
		randInd := rand.Intn(10)
		name := fmt.Sprintf("User_%d", randInd)
		notExisting[i] = *ds.NewData(name, "")
		// fmt.Println(randInd)
	}

	for _, el := range notExisting {
		existing = append(existing, el)
	}

	// toDelete = make([]ds.MyData, countDeletes)
	usedIndices := make(map[int]bool)
	for i := 0; i < countDeletes; i++ {
		var randInd int
		for {
			randInd = rand.Intn(countUniq)
			if !usedIndices[randInd] {
				usedIndices[randInd] = true
				break
			}
		}
		toDelete[i] = uniqueItems[randInd]
	}

	return TestData{
		Items:       items,
		ItemsSorted: itemsSort,
		Search:      existing,
		ToDelete:    toDelete,
		UniqueItems: uniqueItems,
	}
}

// Тест вставки массива данных (один раз)
func testOnesInsert(structure DataStructure, data []ds.MyData) float64 {
	start := time.Now()

	for _, item := range data {
		structure.Insert(item)
	}

	return time.Since(start).Seconds()
}

// Тест поиска массива данных (один раз)
func testOnesSearch(structure DataStructure, data []ds.MyData) float64 {
	start := time.Now()

	// flag := true

	for _, item := range data {
		structure.Search(item.Name)
		// p, ok := structure.Search(item.Name)

		// if flag {
		// 	flag = ((p == item.Phone) == ok)
		// }
	}

	// fmt.Println(flag)

	return time.Since(start).Seconds()
}

// Тест удаления массива данных (один раз)
func testOnesDelete(structure DataStructure, data []ds.MyData) float64 {
	start := time.Now()

	for _, item := range data {
		structure.Delete(item.Name)
	}

	return time.Since(start).Seconds()
}

func testForData(nameStruct, mode string, factory StructureFactory, data_insert, data_search, data_delete []ds.MyData) {
	BenchRes := make([]csvwriter.BenchmarkResult, 0, countRepeat*3+3) // Массив строк отчёта

	averageTimeInsert := 0.
	averageTimeSearch := 0.
	averageTimeDelete := 0.

	for iteration := 0; iteration < countRepeat; iteration++ {

		structure := factory()

		insertTime := testOnesInsert(structure, data_insert)
		averageTimeInsert += insertTime

		// Отладочная информация для бинарного дерева (проверка на вырождение)
		if bst, ok := structure.(*bst.BinSearchTree); ok {
			fmt.Printf(
				"Высота дерева: %d, элементов: %d\n",
				bst.Height(), bst.Len(),
			)
		}

		BenchRes = append(BenchRes, csvwriter.BenchmarkResult{
			Structure: nameStruct,
			Mode:      mode,
			Operation: "Вставка",
			Time:      insertTime,
		})

		searchTime := testOnesSearch(structure, data_search)
		averageTimeSearch += searchTime

		BenchRes = append(BenchRes, csvwriter.BenchmarkResult{
			Structure: nameStruct,
			Mode:      mode,
			Operation: "Поиск",
			Time:      searchTime,
		})

		deleteTime := testOnesDelete(structure, data_delete)
		averageTimeDelete += deleteTime

		BenchRes = append(BenchRes, csvwriter.BenchmarkResult{
			Structure: nameStruct,
			Mode:      mode,
			Operation: "Удаление",
			Time:      deleteTime,
		})
		fmt.Printf("%s | Вставка  | %s | Время: %f\n", nameStruct, mode, insertTime)
		fmt.Printf("%s | Поиск    | %s | Время: %f\n", nameStruct, mode, searchTime)
		fmt.Printf("%s | Удаление | %s | Время: %.9f\n", nameStruct, mode, deleteTime)
	}

	averageTimeInsert /= countRepeat
	averageTimeSearch /= countRepeat
	averageTimeDelete /= countRepeat

	BenchRes = append(BenchRes, csvwriter.BenchmarkResult{
		Structure: nameStruct,
		Mode:      mode,
		Operation: "Вставка (среднее)",
		Time:      averageTimeInsert,
	})
	BenchRes = append(BenchRes, csvwriter.BenchmarkResult{
		Structure: nameStruct,
		Mode:      mode,
		Operation: "Поиск (среднее)",
		Time:      averageTimeSearch,
	})
	BenchRes = append(BenchRes, csvwriter.BenchmarkResult{
		Structure: nameStruct,
		Mode:      mode,
		Operation: "Удаление (среднее)",
		Time:      averageTimeDelete,
	})

	fmt.Printf("%s | Вставка  | %s | Время (среднее): %f\n", nameStruct, mode, averageTimeInsert)
	fmt.Printf("%s | Поиск    | %s | Время (среднее): %f\n", nameStruct, mode, averageTimeSearch)
	fmt.Printf("%s | Удаление | %s | Время (среднее): %f\n", nameStruct, mode, averageTimeDelete)

	csvwriter.AppendRaw(BenchRes)
}

func isSorted(data []ds.MyData) bool {
	for i := 0; i < len(data)-1; i++ {
		if data[i].Name > data[i+1].Name {
			return false
		}
	}
	return true
}

func Test(nameStruct string, factory StructureFactory) {
	data := GenerateTestData()

	// fmt.Println("items", isSorted(data.Items))
	// fmt.Println("items sort", isSorted(data.ItemsSorted))

	testForData(nameStruct, "Случайный", factory, data.Items, data.Search, data.ToDelete)

	testForData(nameStruct, "Отсортированный", factory, data.ItemsSorted, data.Search, data.ToDelete)

}

func main() {

	csvwriter.CreateEmptyCSV("results", "benchmarks.csv")

	fmt.Println("============= Начало тестов =============")

	Test("Связный список", NewLinkedList)
	Test("Хеш таблица", NewHashTable)
	Test("Бинарное дерево поиска", NewBinSearchTree)

	// fmt.Println("User_0001" < "User_00100")
	// fmt.Println(isSorted(dg.RecordsShuffled(10000)))
}
