import time
import random
import csv
import os

def create_node(name, phone):
    return {'name': name, 'phone': phone, 'next': None}

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

def ll_find(head, name):
    current = head
    while current is not None:
        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'] is not None:
        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 is not None:
        records.append((current['name'], current['phone']))
        current = current['next']
    
    records.sort(key=lambda x: x[0])
    return records

def hash_function(name, table_size):
    hash_value = 0
    for char in name:
        hash_value = (hash_value * 31 + ord(char)) % table_size
    return hash_value

def create_hash_table(size=1000):
    return [None] * size

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

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

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

def ht_list_all(buckets):
    all_records = []
    for bucket in buckets:
        if bucket is not None:
            records = ll_list_all(bucket)
            all_records.extend(records)
    
    all_records.sort(key=lambda x: x[0])
    return all_records

def bst_create_node(name, phone):
    return {
        'name': name,
        'phone': phone,
        'left': None,
        'right': None
    }

def bst_insert(root, name, phone):
    if root is None:
        return bst_create_node(name, phone)
    
    current = root
    while True:
        if name < current['name']:
            if current['left'] is None:
                current['left'] = bst_create_node(name, phone)
                return root
            current = current['left']
        elif name > current['name']:
            if current['right'] is None:
                current['right'] = bst_create_node(name, phone)
                return root
            current = current['right']
        else:
            current['phone'] = phone
            return root

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

def bst_find_min(node):
    current = node
    while current['left'] is not None:
        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)
        return root
    elif name > root['name']:
        root['right'] = bst_delete(root['right'], name)
        return root
    
    if root['left'] is None:
        return root['right']
    elif root['right'] is None:
        return root['left']
    
    min_node = bst_find_min(root['right'])
    root['name'] = min_node['name']
    root['phone'] = min_node['phone']
    root['right'] = bst_delete(root['right'], min_node['name'])
    
    return root

def bst_inorder_collect(root, records):
    stack = []
    current = root
    while stack or current:
        while current is not None:
            stack.append(current)
            current = current['left']
        current = stack.pop()
        records.append((current['name'], current['phone']))
        current = current['right']

def bst_list_all(root):
    records = []
    bst_inorder_collect(root, records)
    return records

def generate_test_data(n=1000):
    names = []
    for i in range(n):
        name = f"User_{i:06d}"
        phone = f"+7-999-{random.randint(1000000, 9999999)}"
        names.append((name, phone))
    
    shuffled = names.copy()
    random.shuffle(shuffled)
    
    sorted_records = sorted(names, key=lambda x: x[0])
    
    return shuffled, sorted_records

def run_insert_benchmark(struct_type, data, struct_params=None):
    if struct_type == "LinkedList":
        head = None
        start = time.perf_counter()
        for name, phone in data:
            head = ll_insert(head, name, phone)
        end = time.perf_counter()
        return end - start, head
    
    elif struct_type == "HashTable":
        size = struct_params.get('size', 1000) if struct_params else 1000
        buckets = create_hash_table(size)
        start = time.perf_counter()
        for name, phone in data:
            buckets = ht_insert(buckets, name, phone)
        end = time.perf_counter()
        return end - start, buckets
    
    elif struct_type == "BST":
        root = None
        start = time.perf_counter()
        for name, phone in data:
            root = bst_insert(root, name, phone)
        end = time.perf_counter()
        return end - start, root

def run_find_benchmark(struct, struct_type, existing_names, non_existing_names):
    start = time.perf_counter()
    
    for name in existing_names:
        if struct_type == "LinkedList":
            result = ll_find(struct, name)
        elif struct_type == "HashTable":
            result = ht_find(struct, name)
        elif struct_type == "BST":
            result = bst_find(struct, name)
    
    for name in non_existing_names:
        if struct_type == "LinkedList":
            result = ll_find(struct, name)
        elif struct_type == "HashTable":
            result = ht_find(struct, name)
        elif struct_type == "BST":
            result = bst_find(struct, name)
    
    end = time.perf_counter()
    return end - start

def run_delete_benchmark(struct, struct_type, names_to_delete):
    start = time.perf_counter()
    
    for name in names_to_delete:
        if struct_type == "LinkedList":
            struct = ll_delete(struct, name)
        elif struct_type == "HashTable":
            struct = ht_delete(struct, name)
        elif struct_type == "BST":
            struct = bst_delete(struct, name)
    
    end = time.perf_counter()
    return end - start, struct

def run_experiment(n_records=1000, n_find=100, n_delete=50, n_repeats=3):
    print("Генерация тестовых данных...")
    shuffled_data, sorted_data = generate_test_data(n_records)
    
    all_names = [name for name, _ in shuffled_data]
    find_names = random.sample(all_names, min(n_find, len(all_names)))
    delete_names = random.sample(all_names, min(n_delete, len(all_names)))
    
    non_existing = [f"None_{i}" for i in range(10)]
    
    results = []
    
    structures = ["LinkedList", "HashTable", "BST"]
    modes = ["random", "sorted"]
    
    print("\nНачало эксперимента...")
    print("=" * 80)
    
    for struct_type in structures:
        for mode in modes:
            data = shuffled_data if mode == "random" else sorted_data
            mode_rus = "случайный" if mode == "random" else "отсортированный"
            
            print(f"\nТестирование: {struct_type}, режим: {mode_rus}")
            
            for repeat in range(n_repeats):
                print(f"  Повторение {repeat + 1}/{n_repeats}")
                
                insert_time, struct = run_insert_benchmark(
                    struct_type, data, 
                    {'size': n_records} if struct_type == "HashTable" else None
                )
                results.append([
                    struct_type, mode_rus, "вставка", repeat + 1, insert_time
                ])
                
                find_time = run_find_benchmark(
                    struct, struct_type, find_names, non_existing
                )
                results.append([
                    struct_type, mode_rus, "поиск", repeat + 1, find_time
                ])
                
                delete_time, struct = run_delete_benchmark(
                    struct, struct_type, delete_names
                )
                results.append([
                    struct_type, mode_rus, "удаление", repeat + 1, delete_time
                ])
    
    return results

def save_results_to_csv(results, filename="docs/data/results.csv"):
    os.makedirs("docs/data", exist_ok=True)
    
    with open(filename, 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        writer.writerow(["Структура", "Режим", "Операция", "Повторение", "Время (сек)"])
        writer.writerows(results)
    
    print(f"\nРезультаты сохранены в {filename}")

def print_statistics(results):
    print("\n" + "=" * 80)
    print("СТАТИСТИКА РЕЗУЛЬТАТОВ")
    print("=" * 80)
    
    stats = {}
    for row in results:
        struct, mode, op, _, time_val = row
        key = (struct, mode, op)
        if key not in stats:
            stats[key] = []
        stats[key].append(time_val)
    
    for (struct, mode, op), times in stats.items():
        avg_time = sum(times) / len(times)
        min_time = min(times)
        max_time = max(times)
        
        print(f"\n{struct} - {mode} - {op}:")
        print(f"  Среднее: {avg_time:.6f} сек")
        print(f"  Мин:     {min_time:.6f} сек")
        print(f"  Макс:    {max_time:.6f} сек")

def verify_correctness():
    print("\n" + "=" * 80)
    print("ПРОВЕРКА КОРРЕКТНОСТИ РАБОТЫ")
    print("=" * 80)
    
    test_data = [
        ("Alice", "123-456"),
        ("Bob", "789-012"),
        ("Charlie", "345-678"),
        ("Alice", "999-999"),
    ]
    
    structures = {
        "LinkedList": (None, ll_insert, ll_find, ll_delete, ll_list_all),
        "HashTable": (create_hash_table(10), ht_insert, ht_find, ht_delete, ht_list_all),
        "BST": (None, bst_insert, bst_find, bst_delete, bst_list_all)
    }
    
    for name, (struct, insert_func, find_func, delete_func, list_func) in structures.items():
        print(f"\n{name}:")
        
        for n, p in test_data:
            struct = insert_func(struct, n, p)
        
        print(f"  Поиск Alice: {find_func(struct, 'Alice')}")
        print(f"  Поиск Bob: {find_func(struct, 'Bob')}")
        print(f"  Поиск Unknown: {find_func(struct, 'Unknown')}")
        
        struct = delete_func(struct, "Bob")
        print(f"  После удаления Bob: {find_func(struct, 'Bob')}")
        
        all_records = list_func(struct)
        print(f"  Все записи: {all_records}")

def main():
    print("ТЕЛЕФОННЫЙ СПРАВОЧНИК - СРАВНЕНИЕ СТРУКТУР ДАННЫХ")
    print("=" * 80)
    
    verify_correctness()
    
    print("\n" + "=" * 80)
    print("ЗАПУСК ЭКСПЕРИМЕНТА")
    print("=" * 80)
    
    N_RECORDS = 1000
    N_FIND = 100
    N_DELETE = 50
    N_REPEATS = 3
    
    print(f"\nПараметры эксперимента:")
    print(f"  Количество записей: {N_RECORDS}")
    print(f"  Поиск: {N_FIND} существующих + 10 отсутствующих")
    print(f"  Удаление: {N_DELETE} записей")
    print(f"  Повторений: {N_REPEATS}")
    
    results = run_experiment(N_RECORDS, N_FIND, N_DELETE, N_REPEATS)
    
    save_results_to_csv(results)
    
    print_statistics(results)
    
    print("\n" + "=" * 80)
    print("ЭКСПЕРИМЕНТ ЗАВЕРШЕН")
    print("=" * 80)

if __name__ == "__main__":
    main()