import time
import random
import csv

def ll_insert(head, name, phone):
    new_node = {'name': name, 'phone': phone, 'next': None}
    if head is None:
        return new_node
    current = head
    while current['next']:
        current = current['next']
    current['next'] = new_node
    return head

def ll_find(head, name):
    current = head
    while current:
        if current['name'] == name:
            return current['phone']
        current = current['next']
    return None

def ll_delete(head, name):
    if head is None:
        return None
    if head['name'] == name:
        return head['next']
    current = head
    while current['next']:
        if current['next']['name'] == name:
            current['next'] = current['next']['next']
            return head
        current = current['next']
    return head

def ll_list_all(head):
    records = []
    current = head
    while current:
        records.append((current['name'], current['phone']))
        current = current['next']
    records.sort(key=lambda x: x[0])
    return records

def ht_insert(buckets, name, phone):
    index = hash(name) % len(buckets)
    buckets[index] = ll_insert(buckets[index], name, phone)
    return buckets

def ht_find(buckets, name):
    index = hash(name) % len(buckets)
    return ll_find(buckets[index], name)

def ht_delete(buckets, name):
    index = hash(name) % len(buckets)
    buckets[index] = ll_delete(buckets[index], name)
    return buckets

def ht_list_all(buckets):
    records = []
    for bucket in buckets:
        current = bucket
        while current:
            records.append((current['name'], current['phone']))
            current = current['next']
    records.sort(key=lambda x: x[0])
    return records

def bst_insert(root, name, phone):
    if root is None:
        return {'name': name, 'phone': phone, 'left': None, 'right': None}
    if name < root['name']:
        root['left'] = bst_insert(root['left'], name, phone)
    elif name > root['name']:
        root['right'] = bst_insert(root['right'], name, phone)
    else:
        root['phone'] = phone
    return root

def bst_find(root, name):
    if root is None:
        return None
    if name == root['name']:
        return root['phone']
    elif name < root['name']:
        return bst_find(root['left'], name)
    else:
        return bst_find(root['right'], name)

def bst_min_node(node):
    current = node
    while current['left']:
        current = current['left']
    return current

def bst_delete(root, name):
    if root is None:
        return None
    if name < root['name']:
        root['left'] = bst_delete(root['left'], name)
    elif name > root['name']:
        root['right'] = bst_delete(root['right'], name)
    else:
        if root['left'] is None:
            return root['right']
        elif root['right'] is None:
            return root['left']
        temp = bst_min_node(root['right'])
        root['name'] = temp['name']
        root['phone'] = temp['phone']
        root['right'] = bst_delete(root['right'], temp['name'])
    return root

def bst_list_all(root):
    records = []
    if root:
        records.extend(bst_list_all(root['left']))
        records.append((root['name'], root['phone']))
        records.extend(bst_list_all(root['right']))
    return records

def generate_records(n):
    records = [(f"User_{i:05d}", f"+7-999-{i:07d}") for i in range(n)]
    records_shuffled = records.copy()
    random.shuffle(records_shuffled)
    records_sorted = sorted(records, key=lambda x: x[0])
    return records_shuffled, records_sorted

def run_experiment(structure_name, records, insert_func, find_func, delete_func, list_all_func, buckets=None):
    if structure_name == "HashTable":
        buckets = [None] * 1000
    
    start = time.perf_counter()
    if structure_name == "HashTable":
        for name, phone in records:
            buckets = insert_func(buckets, name, phone)
    else:
        root_or_head = None
        for name, phone in records:
            if structure_name == "LinkedList":
                root_or_head = insert_func(root_or_head, name, phone)
            else:
                root_or_head = insert_func(root_or_head, name, phone)
    insert_time = time.perf_counter() - start
    
    existing_names = [name for name, _ in records[:100]]
    nonexisting_names = [f"None_{i}" for i in range(10)]
    all_searches = existing_names + nonexisting_names
    random.shuffle(all_searches)
    
    start = time.perf_counter()
    for name in all_searches:
        if structure_name == "HashTable":
            find_func(buckets, name)
        else:
            find_func(root_or_head, name)
    find_time = time.perf_counter() - start
    
    delete_names = [records[i][0] for i in random.sample(range(len(records)), min(50, len(records)))]
    start = time.perf_counter()
    for name in delete_names:
        if structure_name == "HashTable":
            buckets = delete_func(buckets, name)
        else:
            root_or_head = delete_func(root_or_head, name)
    delete_time = time.perf_counter() - start
    
    return insert_time, find_time, delete_time

def main():
    N = 1000
    records_shuffled, records_sorted = generate_records(N)
    
    results = []
    
    for mode, records in [("случайный", records_shuffled), ("отсортированный", records_sorted)]:
        for run in range(5):
            ins_ll, find_ll, del_ll = run_experiment("LinkedList", records, ll_insert, ll_find, ll_delete, ll_list_all)
            results.append(["LinkedList", mode, "вставка", ins_ll, run+1])
            results.append(["LinkedList", mode, "поиск", find_ll, run+1])
            results.append(["LinkedList", mode, "удаление", del_ll, run+1])
            
            ins_ht, find_ht, del_ht = run_experiment("HashTable", records, ht_insert, ht_find, ht_delete, ht_list_all)
            results.append(["HashTable", mode, "вставка", ins_ht, run+1])
            results.append(["HashTable", mode, "поиск", find_ht, run+1])
            results.append(["HashTable", mode, "удаление", del_ht, run+1])
            
            ins_bst, find_bst, del_bst = run_experiment("BST", records, bst_insert, bst_find, bst_delete, bst_list_all)
            results.append(["BST", mode, "вставка", ins_bst, run+1])
            results.append(["BST", mode, "поиск", find_bst, run+1])
            results.append(["BST", mode, "удаление", del_bst, run+1])
    
    with open("results.csv", "w", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow(["Структура", "Режим", "Операция", "Время (сек)", "Повторение"])
        writer.writerows(results)
    
    avg_results = {}
    for row in results:
        key = (row[0], row[1], row[2])
        if key not in avg_results:
            avg_results[key] = []
        avg_results[key].append(row[3])
    
    with open("avg_results.csv", "w", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow(["Структура", "Режим", "Операция", "Среднее время (сек)"])
        for (struct, mode, op), times in avg_results.items():
            writer.writerow([struct, mode, op, sum(times)/len(times)])

if __name__ == "__main__":
    main()