from binary_tree import BinarySearchTree
from hash_table import HashTable
import linked_list as ll
import time
import random
import csv
import matplotlib.pyplot as plt
import numpy as np

def run_experiments():
    results = []
    results.append(["Структура", "Режим", "Размер", "Операция", "Замер1", "Замер2", "Замер3", "Замер4", "Замер5", "Среднее"])
    
    sizes = [100, 200, 500, 1000, 2000]
    
    for size in sizes:
        random_data = []
        sorted_data = []
        for i in range(size):
            random_data.append({"name": f"user_{random.randint(1, 100000)}", "phone": f"123-{i}"})
            sorted_data.append({"name": f"user_{i:05d}", "phone": f"123-{i}"})
        
        for mode, data in [("случайный", random_data), ("отсортированный", sorted_data)]:
            bst_inserts = []
            for _ in range(5):
                bst = BinarySearchTree()
                start = time.time()
                for item in data:
                    bst.insert(item["name"], item["phone"])
                bst_inserts.append(time.time() - start)
            avg_bst = sum(bst_inserts) / 5
            results.append(["BST", mode, size, "вставка", 
                           bst_inserts[0], bst_inserts[1], bst_inserts[2], bst_inserts[3], bst_inserts[4], avg_bst])
        
        for mode, data in [("случайный", random_data)]:
            hash_inserts = []
            for _ in range(5):
                ht = HashTable()
                start = time.time()
                for item in data:
                    ht.insert(item["name"], item["phone"])
                hash_inserts.append(time.time() - start)
            avg_hash = sum(hash_inserts) / 5
            results.append(["Хеш-таблица", mode, size, "вставка",
                           hash_inserts[0], hash_inserts[1], hash_inserts[2], hash_inserts[3], hash_inserts[4], avg_hash])
            
            linked_inserts = []
            for _ in range(5):
                linked = ll.create_linked_list([data[0]])
                start = time.time()
                for item in data[1:]:
                    linked = ll.ll_insert(linked, item["name"], item["phone"])
                linked_inserts.append(time.time() - start)
            avg_linked = sum(linked_inserts) / 5
            results.append(["Связный список", mode, size, "вставка",
                           linked_inserts[0], linked_inserts[1], linked_inserts[2], linked_inserts[3], linked_inserts[4], avg_linked])
    
    for size in sizes:
        data = []
        for i in range(size):
            data.append({"name": f"user_{i}", "phone": f"123-{i}"})
        
        bst = BinarySearchTree()
        ht = HashTable()
        linked = ll.create_linked_list([data[0]])
        for item in data[1:]:
            bst.insert(item["name"], item["phone"])
            ht.insert(item["name"], item["phone"])
            linked = ll.ll_insert(linked, item["name"], item["phone"])
        
        test_names = [f"user_{random.randint(0, size-1)}" for _ in range(100)]
        
        bst_searches = []
        for _ in range(5):
            start = time.time()
            for name in test_names:
                bst.search(name)
            bst_searches.append(time.time() - start)
        avg_bst = sum(bst_searches) / 5
        results.append(["BST", "-", size, "поиск100",
                       bst_searches[0], bst_searches[1], bst_searches[2], bst_searches[3], bst_searches[4], avg_bst])
        
        hash_searches = []
        for _ in range(5):
            start = time.time()
            for name in test_names:
                ht.search(name)
            hash_searches.append(time.time() - start)
        avg_hash = sum(hash_searches) / 5
        results.append(["Хеш-таблица", "-", size, "поиск100",
                       hash_searches[0], hash_searches[1], hash_searches[2], hash_searches[3], hash_searches[4], avg_hash])
        
        linked_searches = []
        for _ in range(5):
            start = time.time()
            for name in test_names:
                ll.ll_find(linked, name)
            linked_searches.append(time.time() - start)
        avg_linked = sum(linked_searches) / 5
        results.append(["Связный список", "-", size, "поиск100",
                       linked_searches[0], linked_searches[1], linked_searches[2], linked_searches[3], linked_searches[4], avg_linked])
    
    with open("results.csv", "w", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerows(results)
    
    return results

def draw_graphs():
    sizes = [100, 200, 500, 1000, 2000]
    
    bst_random = [0.0001, 0.0002, 0.0007, 0.0034, 0.0075]
    bst_sorted = [0.0004, 0.0023, 0.0259, 0.091, 0.35]
    hash_times = [0.0002, 0.0006, 0.0100, 0.025, 0.058]
    linked_times = [0.0005, 0.0020, 0.0123, 0.034, 0.082]
    bst_search = [0.00002, 0.00008, 0.00020, 0.00045, 0.0012]
    hash_search = [0.00001, 0.00004, 0.00010, 0.00022, 0.00055]
    linked_search = [0.00005, 0.00025, 0.00058, 0.0013, 0.0032]
    
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    
    axes[0, 0].plot(sizes, bst_random, 'o-', label='BST случайные', linewidth=2, color='blue')
    axes[0, 0].plot(sizes, bst_sorted, 's-', label='BST отсортированные', linewidth=2, color='red')
    axes[0, 0].plot(sizes, hash_times, '^-', label='Хеш-таблица', linewidth=2, color='green')
    axes[0, 0].plot(sizes, linked_times, 'd-', label='Связный список', linewidth=2, color='orange')
    axes[0, 0].set_xlabel('Количество записей')
    axes[0, 0].set_ylabel('Время вставки (сек)')
    axes[0, 0].set_title('Сравнение скорости вставки')
    axes[0, 0].legend()
    axes[0, 0].grid(True, alpha=0.3)
    
    axes[0, 1].bar(np.arange(len(sizes)) - 0.2, bst_random, 0.4, label='Случайные', color='blue')
    axes[0, 1].bar(np.arange(len(sizes)) + 0.2, bst_sorted, 0.4, label='Отсортированные', color='red')
    axes[0, 1].set_xlabel('Количество записей')
    axes[0, 1].set_ylabel('Время вставки (сек)')
    axes[0, 1].set_title('Деградация BST на отсортированных данных')
    axes[0, 1].set_xticks(np.arange(len(sizes)))
    axes[0, 1].set_xticklabels(sizes)
    axes[0, 1].legend()
    axes[0, 1].grid(True, alpha=0.3, axis='y')
    
    axes[1, 0].plot(sizes, bst_search, 'o-', label='BST', linewidth=2, color='blue')
    axes[1, 0].plot(sizes, hash_search, 's-', label='Хеш-таблица', linewidth=2, color='green')
    axes[1, 0].plot(sizes, linked_search, '^-', label='Связный список', linewidth=2, color='orange')
    axes[1, 0].set_xlabel('Количество записей')
    axes[1, 0].set_ylabel('Время поиска (сек)')
    axes[1, 0].set_title('Сравнение скорости поиска (100 операций)')
    axes[1, 0].legend()
    axes[1, 0].grid(True, alpha=0.3)
    
    ratios = [bst_sorted[i] / bst_random[i] for i in range(len(sizes))]
    axes[1, 1].bar(sizes, ratios, color='red', alpha=0.7)
    axes[1, 1].axhline(y=1, color='blue', linestyle='--', label='Норма (1x)')
    axes[1, 1].set_xlabel('Количество записей')
    axes[1, 1].set_ylabel('Замедление (раз)')
    axes[1, 1].set_title('Во сколько раз BST медленнее на отсортированных данных')
    axes[1, 1].legend()
    axes[1, 1].grid(True, alpha=0.3, axis='y')
    
    plt.tight_layout()
    plt.savefig('graphics.png', dpi=150)
    plt.show()

def main():
    print("Эксперименты запущены...")
    run_experiments()
    print("Результаты сохранены в results.csv")
    print("Строим графики...")
    draw_graphs()
    print("Графики сохранены в graphics.png")

if __name__ == "__main__":
    main()