import random
import time
import csv
import os

# --------------------- Реализация бинарного дерева поиска (итеративная) ---------------------
def bst_insert(root, name, phone):
#Итеративная вставка. Возвращает корень.
    new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
    if root is None:
        return new_node

    current = root
    while True:
        if name < current['name']:
            if current['left'] is None:
                current['left'] = new_node
                break
            else:
                current = current['left']
        elif name > current['name']:
            if current['right'] is None:
                current['right'] = new_node
                break
            else:
                current = current['right']
        else:  # имя уже существует — обновляем телефон
            current['phone'] = phone
            break
    return root

def bst_find(root, name):
#Итеративный поиск. Возвращает phone или None.
    current = root
    while current:
        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):
#Возвращает узел с минимальным ключом в поддереве.
    while node['left']:
        node = node['left']
    return node

def bst_delete(root, name):
#Итеративное удаление. Возвращает новый корень.
    # Сначала найдём удаляемый узел и его родителя
    parent = None
    current = root
    while current and current['name'] != name:
        parent = current
        if name < current['name']:
            current = current['left']
        else:
            current = current['right']
    if current is None:  # узел не найден
        return root

    # Случай 1: нет левого потомка
    if current['left'] is None:
        child = current['right']
    # Случай 2: нет правого потомка
    elif current['right'] is None:
        child = current['left']
    # Случай 3: два потомка
    else:
        # Находим минимальный узел в правом поддереве (преемник)
        min_parent = current
        min_node = current['right']
        while min_node['left']:
            min_parent = min_node
            min_node = min_node['left']
        # Копируем данные из min_node в current
        current['name'], current['phone'] = min_node['name'], min_node['phone']
        # Удаляем min_node (у него нет левого потомка)
        if min_parent['left'] == min_node:
            min_parent['left'] = min_node['right']
        else:
            min_parent['right'] = min_node['right']
        return root

    # Подсоединяем child к parent
    if parent is None:
        return child
    if parent['left'] == current:
        parent['left'] = child
    else:
        parent['right'] = child
    return root

def bst_list_all(root):
#Итеративный симметричный обход (inorder) без рекурсии, используя стек.
    result = []
    stack = []
    current = root
    while stack or current:
        while current:
            stack.append(current)
            current = current['left']
        current = stack.pop()
        result.append((current['name'], current['phone']))
        current = current['right']
    return result

# --------------------- Функции измерений  ---------------------
def generate_data(N=10000):
    records = []
    for i in range(N):
        name = f"User_{i:05d}"
        phone = f"8{random.randint(9000000000, 9999999999)}"
        records.append((name, phone))
    return records

def measure_insert(records):
    root = None
    start = time.perf_counter()
    for name, phone in records:
        root = bst_insert(root, name, phone)
    end = time.perf_counter()
    return end - start

def measure_find(records, test_names):
    root = None
    for name, phone in records:
        root = bst_insert(root, name, phone)
    start = time.perf_counter()
    for name in test_names:
        bst_find(root, name)
    end = time.perf_counter()
    return end - start

def measure_delete(records, delete_names):
    times = []
    for name in delete_names:
        root = None
        for n, p in records:
            root = bst_insert(root, n, p)
        start = time.perf_counter()
        root = bst_delete(root, name)
        end = time.perf_counter()
        times.append(end - start)
    return sum(times) / len(times)

def main():
    N = 10000
    records = generate_data(N)

    records_shuffled = records.copy()
    random.shuffle(records_shuffled)
    records_sorted = sorted(records, key=lambda x: x[0])

    existing_names = random.sample([rec[0] for rec in records], 100)
    non_existing = [f"None_{i}" for i in range(10)]
    test_names = existing_names + non_existing

    delete_names = random.sample([rec[0] for rec in records], 50)

    insert_shuffled_avg = 0.0
    insert_sorted_avg = 0.0
    find_avg = 0.0
    delete_avg = 0.0

    repeats = 5
    for _ in range(repeats):
        insert_shuffled_avg += measure_insert(records_shuffled)
        insert_sorted_avg += measure_insert(records_sorted)
        find_avg += measure_find(records, test_names)
        delete_avg += measure_delete(records, delete_names)

    insert_shuffled_avg /= repeats
    insert_sorted_avg /= repeats
    find_avg /= repeats
    delete_avg /= repeats

    new_rows = [
        ["Binary tree", "случайный", "вставка (корень)", insert_shuffled_avg],
        ["Binary tree", "отсортированный", "вставка (корень)", insert_sorted_avg],
        ["Binary tree", "любой", "поиск 110 записей", find_avg],
        ["Binary tree", "любой", "удаление 50 записей (среднее)", delete_avg]
    ]

    csv_filename = "results.csv"
    file_exists = os.path.isfile(csv_filename)
    need_header = False
    if file_exists:
        with open(csv_filename, 'r', encoding='utf-8-sig') as f:
            first_line = f.readline()
            if not first_line.startswith("Структура"):
                need_header = True
    else:
        need_header = True

    with open(csv_filename, 'a', newline='', encoding='utf-8-sig') as f:
        writer = csv.writer(f, delimiter=';')
        if need_header:
            writer.writerow(["Структура", "Режим", "Операция", "Время (сек)"])
        writer.writerows(new_rows)

    print("Результаты для двоичного дерева поиска добавлены в", csv_filename)
    print(f"Среднее время вставки (случ. порядок): {insert_shuffled_avg:.6f} сек")
    print(f"Среднее время вставки (отсорт.):    {insert_sorted_avg:.6f} сек")
    print(f"Среднее время поиска 110 записей:    {find_avg:.6f} сек")
    print(f"Среднее время удаления 50 записей:   {delete_avg:.6f} сек")

if __name__ == "__main__":
    main()