import data_structures as st
import time
import random
import csv


def generate_records(N):
    records = []

    for i in range(N):
        name = f"User_{i:05d}"
        phone = f"+7{random.randint(10**9, 10**10 - 1)}"
        records.append((name, phone))

    records_shuffled = records[:]
    random.shuffle(records_shuffled)

    records_sorted = sorted(records, key=lambda x: x[0])

    return records_shuffled, records_sorted

def linked_list_build_structure(records):
    head = None

    for name, phone in records:
        head = st.ll_insert(head, name, phone)

    return head

def hash_table_build_structure(records):
    buckets = [None] * 10007

    for name, phone in records:
        buckets = st.ht_insert(buckets, name, phone)

    return buckets

def bst_build_structure(records):
    root = None

    for name, phone in records:
        root = st.bst_insert(root, name, phone)

    return root

def measure_time(func, *args):
    start = time.perf_counter()
    result = func(*args)
    end = time.perf_counter()

    return result, end - start

def prepare_find_names(records):
    existing_names = [name for name, phone in records]

    find_existing = random.sample(existing_names, 100)

    find_missing = []
    for i in range(10):
        find_missing.append(f"None_{i}")
    find_names = find_existing + find_missing
    return find_names


def linked_list_find(head, find_names):
    results = []
    for name in find_names:
        phone = st.ll_find(head, name)
        results.append(phone)
    return results

def hash_table_find(buckets, find_names):
    results = []
    for name in find_names:
        phone = st.ht_find(buckets, name)
        results.append(phone)
    return results

def bst_find(root, find_names):
    results = []
    for name in find_names:
        phone = st.bst_find(root, name)
        results.append(phone)
    return results

def prepare_delete_names(records):
    existing_names = [name for name, phone in records]
    delete_names = random.sample(existing_names, 50)
    return delete_names


def linked_list_delete(head, delete_names):
    for name in delete_names:
        head = st.ll_delete(head, name)
    return head

def hash_table_delete(buckets, delete_names):
    for name in delete_names:
        buckets = st.ht_delete(buckets, name)
    return buckets

def bst_delete(root, delete_names):
    for name in delete_names:
        root = st.bst_delete(root, name)
    return root


def run_one_experiment(records_shuffled, records_sorted, find_names, delete_names):
    one_run_results = []
    #------------------------ создание структур + замер времени заполнения -------------------------
    # ------------------- shuffled -------------------
    head_shuffled, ll_insert_time_shuffled = measure_time(linked_list_build_structure,records_shuffled)

    buckets_shuffled, ht_insert_time_shuffled = measure_time(hash_table_build_structure,records_shuffled)

    root_shuffled, bst_insert_time_shuffled = measure_time(bst_build_structure,records_shuffled)


    # ------------------- sorted -------------------

    head_sorted, ll_insert_time_sorted = measure_time(linked_list_build_structure,records_sorted)

    buckets_sorted, ht_insert_time_sorted = measure_time(hash_table_build_structure,records_sorted)

    root_sorted, bst_insert_time_sorted = measure_time(bst_build_structure,records_sorted)



    # ------------------- поиск в shuffled -------------------

    ll_find_results_shuffled, ll_find_time_shuffled = measure_time(linked_list_find,head_shuffled,find_names)

    ht_find_results_shuffled, ht_find_time_shuffled = measure_time(hash_table_find,buckets_shuffled,find_names)

    bst_find_results_shuffled, bst_find_time_shuffled = measure_time(bst_find,root_shuffled,find_names)


    # ------------------- поиск в sorted -------------------

    ll_find_results_sorted, ll_find_time_sorted = measure_time(linked_list_find,head_sorted,find_names)

    ht_find_results_sorted, ht_find_time_sorted = measure_time(hash_table_find,buckets_sorted,find_names)

    bst_find_results_sorted, bst_find_time_sorted = measure_time(bst_find,root_sorted,find_names)



    # ------------------- удаление в shuffled -------------------

    head_shuffled, ll_delete_time_shuffled = measure_time(linked_list_delete,head_shuffled,delete_names)

    buckets_shuffled, ht_delete_time_shuffled = measure_time(hash_table_delete,buckets_shuffled,delete_names)

    root_shuffled, bst_delete_time_shuffled = measure_time(bst_delete,root_shuffled,delete_names)

    # ------------------- удаление в sorted -------------------

    head_sorted, ll_delete_time_sorted = measure_time(linked_list_delete,head_sorted,delete_names)

    buckets_sorted, ht_delete_time_sorted = measure_time(hash_table_delete,buckets_sorted,delete_names)

    root_sorted, bst_delete_time_sorted = measure_time(bst_delete,root_sorted,delete_names)
    
    one_run_results.append(["LinkedList", "shuffled", "insert", ll_insert_time_shuffled])
    one_run_results.append(["HashTable", "shuffled", "insert", ht_insert_time_shuffled])
    one_run_results.append(["BST", "shuffled", "insert", bst_insert_time_shuffled])

    one_run_results.append(["LinkedList", "shuffled", "find", ll_find_time_shuffled])
    one_run_results.append(["HashTable", "shuffled", "find", ht_find_time_shuffled])
    one_run_results.append(["BST", "shuffled", "find", bst_find_time_shuffled])

    one_run_results.append(["LinkedList", "shuffled", "delete", ll_delete_time_shuffled])
    one_run_results.append(["HashTable", "shuffled", "delete", ht_delete_time_shuffled])
    one_run_results.append(["BST", "shuffled", "delete", bst_delete_time_shuffled])

    one_run_results.append(["LinkedList", "sorted", "insert", ll_insert_time_sorted])
    one_run_results.append(["HashTable", "sorted", "insert", ht_insert_time_sorted])
    one_run_results.append(["BST", "sorted", "insert", bst_insert_time_sorted])

    one_run_results.append(["LinkedList", "sorted", "find", ll_find_time_sorted])
    one_run_results.append(["HashTable", "sorted", "find", ht_find_time_sorted])
    one_run_results.append(["BST", "sorted", "find", bst_find_time_sorted])

    one_run_results.append(["LinkedList", "sorted", "delete", ll_delete_time_sorted])
    one_run_results.append(["HashTable", "sorted", "delete", ht_delete_time_sorted])
    one_run_results.append(["BST", "sorted", "delete", bst_delete_time_sorted])

    return one_run_results


N = 10000
REPEATS = 5
records_shuffled, records_sorted = generate_records(N)
find_names = prepare_find_names(records_sorted)
delete_names = prepare_delete_names(records_sorted)

results = [["Запуск", "Структура", "Режим", "Операция", "Время (сек)"]]

for run in range(1, REPEATS + 1):
    print("Запуск эксперимента:", run)

    one_run_results = run_one_experiment(records_shuffled,records_sorted,find_names,delete_names)

    for row in one_run_results:
        structure = row[0]
        mode = row[1]
        operation = row[2]
        elapsed = row[3]

        results.append([run, structure, mode, operation, elapsed])
        
groups = {}

for row in results[1:]:
    structure = row[1]
    mode = row[2]
    operation = row[3]
    elapsed = row[4]

    key = (structure, mode, operation)

    if key not in groups:
        groups[key] = []

    groups[key].append(elapsed)


for key, times in groups.items():
    structure, mode, operation = key
    avg_time = sum(times) / len(times)

    results.append(["average", structure, mode, operation, avg_time])


with open("results.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(results)

print("Результаты сохранены в results.csv")    

